Subversion Repositories oidplus

Rev

Rev 1200 | Rev 1267 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
115 daniel-mar 1
<?php
2
 
3
/*
4
 * OIDplus 2.0
1086 daniel-mar 5
 * Copyright 2019 - 2023 Daniel Marschall, ViaThinkSoft
115 daniel-mar 6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
 
1050 daniel-mar 20
namespace ViaThinkSoft\OIDplus;
511 daniel-mar 21
 
1086 daniel-mar 22
// phpcs:disable PSR1.Files.SideEffects
23
\defined('INSIDE_OIDPLUS') or die;
24
// phpcs:enable PSR1.Files.SideEffects
25
 
730 daniel-mar 26
class OIDplusLogger extends OIDplusBaseClass {
115 daniel-mar 27
 
1116 daniel-mar 28
        /**
29
         * This function splits a mask code containing multiple components
30
         * (delimited by '+' or '/') in single components
31
         * It takes care that '+' and '/' inside brackets won't be used to split the codes
32
         * Also, brackets can be escaped.
33
         * The severity block (optional, must be standing in front of a component)
34
         * is handled too. Inside the severity block, you may only use '/' to split components.
35
         * The severity block will be implicitly repeated from the previous components if a component
36
         * does not feature one.
37
         *
1197 daniel-mar 38
         * "[ERR]AAA(BBB)+CCC(DDD)"   ==> array(
39
         *                                 array(array("ERR"),"AAA(BBB)"),
40
         *                                 array(array("ERR"),"CCC(DDD)")
1116 daniel-mar 41
         *                              )
1197 daniel-mar 42
         * "[INFO]AAA(B+BB)+[WARN]CCC(DDD)"  ==> array(
43
         *                                 array(array("INFO"),"AAA(B+BB)"),
44
         *                                 array(array("WARN"),"CCC(DDD)")
1116 daniel-mar 45
         *                              )
1197 daniel-mar 46
         * "[?WARN/!OK] AAA(B\)BB)+CCC(DDD)" ==> array(
47
         *                                 array(array("?WARN", "!OK"),"AAA(B\)BB)"),
48
         *                                 array(array("?WARN", "!OK"),"CCC(DDD)")
1116 daniel-mar 49
         *                              )
50
         * @param string $maskcodes
51
         * @return array|false
52
         */
1186 daniel-mar 53
        private function split_maskcodes(string $maskcodes) {
188 daniel-mar 54
                $out = array();
288 daniel-mar 55
                $sevs = array(); // Note: The severity block will repeat for the next components if not changed explicitly
289 daniel-mar 56
 
188 daniel-mar 57
                $code = '';
288 daniel-mar 58
                $sev = '';
188 daniel-mar 59
                $bracket_level = 0;
288 daniel-mar 60
                $is_escaping = false;
61
                $inside_severity_block = false;
188 daniel-mar 62
                for ($i=0; $i<strlen($maskcodes); $i++) {
63
                        $char = $maskcodes[$i];
289 daniel-mar 64
 
288 daniel-mar 65
                        if ($inside_severity_block) {
66
                                // Severity block (optional)
67
                                // e.g.  [?WARN/!OK] ==> $sevs = array("?WARN", "!OK")
68
                                if ($char == '\\') {
69
                                        if ($is_escaping) {
70
                                                $is_escaping = false;
71
                                                $sev .= $char;
72
                                        } else {
73
                                                $is_escaping = true;
74
                                        }
75
                                }
76
                                else if ($char == '[') {
77
                                        if ($is_escaping) {
78
                                                $is_escaping = false;
79
                                        } else {
80
                                                $bracket_level++;
81
                                        }
82
                                        $sev .= $char;
83
                                }
84
                                else if ($char == ']') {
85
                                        if ($is_escaping) {
86
                                                $is_escaping = false;
87
                                                $sev .= $char;
88
                                        } else {
89
                                                $bracket_level--;
90
                                                if ($bracket_level < 0) return false;
91
                                                if ($bracket_level == 0) {
92
                                                        $inside_severity_block = false;
93
                                                        if ($sev != '') $sevs[] = $sev;
94
                                                        $sev = '';
95
                                                } else {
96
                                                        $sev .= $char;
97
                                                }
98
                                        }
99
                                }
100
                                else if ((($char == '/')) && ($bracket_level == 1)) {
101
                                        if ($is_escaping) {
102
                                                $is_escaping = false;
103
                                                $sev .= $char;
104
                                        } else {
105
                                                if ($sev != '') $sevs[] = $sev;
106
                                                $sev = '';
107
                                        }
108
                                } else {
109
                                        if ($is_escaping) {
110
                                                // This would actually be an error, because we cannot escape this
111
                                                $is_escaping = false;
112
                                                $sev .= '\\' . $char;
113
                                        } else {
114
                                                $sev .= $char;
115
                                        }
116
                                }
188 daniel-mar 117
                        } else {
288 daniel-mar 118
                                // Normal data (after the severity block)
119
                                if (($char == '[') && ($code == '')) {
120
                                        $inside_severity_block = true;
121
                                        $bracket_level++;
122
                                        $sevs = array();
123
                                }
124
                                else if ($char == '\\') {
125
                                        if ($is_escaping) {
126
                                                $is_escaping = false;
127
                                                $code .= $char;
128
                                        } else {
129
                                                $is_escaping = true;
130
                                        }
131
                                }
132
                                else if ($char == '(') {
133
                                        if ($is_escaping) {
134
                                                $is_escaping = false;
135
                                        } else {
136
                                                $bracket_level++;
137
                                        }
138
                                        $code .= $char;
139
                                }
140
                                else if ($char == ')') {
141
                                        if ($is_escaping) {
142
                                                $is_escaping = false;
143
                                        } else {
144
                                                $bracket_level--;
145
                                                if ($bracket_level < 0) return false;
146
                                        }
147
                                        $code .= $char;
148
                                }
149
                                else if ((($char == '+') || ($char == '/')) && ($bracket_level == 0)) {
150
                                        if ($is_escaping) {
151
                                                $is_escaping = false;
152
                                                $code .= $char;
153
                                        } else {
154
                                                if ($code != '') $out[] = array($sevs,$code);
155
                                                $code = '';
156
                                        }
157
                                } else {
158
                                        if ($is_escaping) {
159
                                                // This would actually be an error, because we cannot escape this
160
                                                $is_escaping = false;
161
                                                $code .= '\\' . $char;
162
                                        } else {
163
                                                $code .= $char;
164
                                        }
165
                                }
188 daniel-mar 166
                        }
167
                }
288 daniel-mar 168
                if ($code != '') $out[] = array($sevs,$code);
169
                if ($inside_severity_block) return false;
188 daniel-mar 170
 
171
                return $out;
172
        }
173
 
1185 daniel-mar 174
        private $missing_plugin_queue = array();
825 daniel-mar 175
 
1116 daniel-mar 176
        /**
177
         * @return bool
178
         * @throws OIDplusException
179
         */
1185 daniel-mar 180
        public function reLogMissing(): bool {
181
                while (count($this->missing_plugin_queue) > 0) {
182
                        $item = $this->missing_plugin_queue[0];
183
                        if (!$this->log_internal($item[0], $item[1], false)) return false;
184
                        array_shift($this->missing_plugin_queue);
825 daniel-mar 185
                }
186
                return true;
187
        }
188
 
1116 daniel-mar 189
        /**
1207 daniel-mar 190
         * @param string $maskcodes A description of the mask-codes can be found in doc/developer_notes/logger_maskcodes.md
1199 daniel-mar 191
         * @param string $message The message of the event
192
         * @param mixed ...$sprintfArgs If used, %1..%n in $maskcodes and $message will be replaced, like _L() does.
1116 daniel-mar 193
         * @return bool
194
         * @throws OIDplusException
195
         */
1199 daniel-mar 196
        public function log(string $maskcodes, string $message, ...$sprintfArgs): bool {
1185 daniel-mar 197
                $this->reLogMissing(); // try to re-log failed requests
1199 daniel-mar 198
 
199
                $maskcodes = my_vsprintf($maskcodes, $sprintfArgs);
200
                $message = my_vsprintf($message, $sprintfArgs);
201
 
1200 daniel-mar 202
                if (strpos(str_replace('%%','',$maskcodes),'%') !== false) {
1199 daniel-mar 203
                        throw new OIDplusException(_L('Unresolved wildcards in logging maskcode'));
204
                }
205
 
206
                return $this->log_internal($maskcodes, $message, true);
825 daniel-mar 207
        }
208
 
1116 daniel-mar 209
        /**
210
         * @param string $maskcodes
1197 daniel-mar 211
         * @param string $message
1116 daniel-mar 212
         * @param bool $allow_delayed_log
213
         * @return bool
214
         * @throws OIDplusException
215
         */
1197 daniel-mar 216
        private function log_internal(string $maskcodes, string $message, bool $allow_delayed_log): bool {
289 daniel-mar 217
                $loggerPlugins = OIDplus::getLoggerPlugins();
825 daniel-mar 218
                if (count($loggerPlugins) == 0) {
219
                        // The plugin might not be initialized in OIDplus::init()
220
                        // yet. Remember the log entries for later submission during
221
                        // OIDplus::init();
1197 daniel-mar 222
                        if ($allow_delayed_log) $this->missing_plugin_queue[] = array($maskcodes, $message);
825 daniel-mar 223
                        return false;
224
                }
289 daniel-mar 225
 
288 daniel-mar 226
                // What is a mask code?
227
                // A mask code gives information about the log event:
228
                // 1. The severity (info, warning, error)
229
                // 2. In which logbook(s) the event shall be placed
230
                // Example:
231
                // The event would be:
232
                // "Person 'X' moves from house 'A' to house 'B'"
233
                // This event would affect the person X and the two houses,
234
                // so, instead of logging into 3 logbooks separately,
235
                // you would create a mask code that tells the system
236
                // to put the message into the logbooks of person X,
1116 daniel-mar 237
                // house A, and house B.
115 daniel-mar 238
 
1197 daniel-mar 239
                $logEvent = new OIDplusLogEvent($message);
115 daniel-mar 240
 
288 daniel-mar 241
                // A mask code with multiple components is split into single codes
242
                // using '+' or '/', e.g. "OID(x)+RA(x)" would be split to "OID(x)" and "RA(x)"
243
                // which would result in the message being placed in the logbook of OID x,
244
                // and the logbook of the RA owning OID x.
1186 daniel-mar 245
                $maskcodes_ary = $this->split_maskcodes($maskcodes);
188 daniel-mar 246
                if ($maskcodes_ary === false) {
360 daniel-mar 247
                        throw new OIDplusException(_L('Invalid maskcode "%1" (failed to split)',$maskcodes));
188 daniel-mar 248
                }
288 daniel-mar 249
                foreach ($maskcodes_ary as list($sevs,$maskcode)) {
1207 daniel-mar 250
                        // At the beginning of each mask code, you must define a severity.
288 daniel-mar 251
                        // If you have a mask code with multiple components, you don't have to place the
252
                        // severity for each component. You can just leave it at the beginning.
253
                        // e.g. "[WARN]OID(x)+RA(x)" is equal to "[WARN]OID(x)+[WARN]RA(x)"
254
                        // You can also put different severities for the components:
255
                        // e.g. "[INFO]OID(x)+[WARN]RA(x)" would be a info for the OID, but a warning for the RA.
256
                        // If you want to make the severity dependent on wheather the user is logged in or not,
257
                        // prepend "?" or "!" and use '/' as delimiter
258
                        // Example: "[?WARN/!OK]RA(x)" means: If RA is not logged in, it is a warning; if it is logged in, it is an success
259
                        $severity = 0; // default severity = none
289 daniel-mar 260
                        $severity_online = 0;
288 daniel-mar 261
                        foreach ($sevs as $sev) {
262
                                switch (strtoupper($sev)) {
263
                                        // [OK]   = Success
264
                                        //          Numeric value: 1
265
                                        //          Rule of thumb: YOU have done something and it was successful
266
                                        case '?OK':
267
                                                $severity_online = 1;
268
                                                break;
269
                                        case '!OK':
270
                                        case  'OK':
271
                                                $severity = 1;
272
                                                break;
273
                                        // [INFO] = Informational
274
                                        //          Numeric value: 2
275
                                        //          Rule of thumb: Someone else has done something (that affects you) and it was successful
276
                                        case '?INFO':
277
                                                $severity_online = 2;
278
                                                break;
279
                                        case '!INFO':
280
                                        case  'INFO':
281
                                                $severity = 2;
282
                                                break;
283
                                        // [WARN] = Warning
284
                                        //          Numeric value: 3
285
                                        //          Rule of thumb: Something happened (probably someone did something) and it affects you
286
                                        case '?WARN':
287
                                                $severity_online = 3;
288
                                                break;
289
                                        case '!WARN':
290
                                        case  'WARN':
291
                                                $severity = 3;
292
                                                break;
293
                                        // [ERR]  = Error
294
                                        //          Numeric value: 4
295
                                        //          Rule of thumb: Something failed (probably someone did something) and it affects you
296
                                        case '?ERR':
297
                                                $severity_online = 4;
298
                                                break;
299
                                        case '!ERR':
300
                                        case  'ERR':
301
                                                $severity = 4;
302
                                                break;
303
                                        // [CRIT] = Critical
304
                                        //          Numeric value: 5
305
                                        //          Rule of thumb: Something happened (probably someone did something) which is not an error,
306
                                        //          but some critical situation (e.g. hardware failure), and it affects you
307
                                        case '?CRIT':
308
                                                $severity_online = 5;
309
                                                break;
310
                                        case '!CRIT':
311
                                        case  'CRIT':
312
                                                $severity = 5;
313
                                                break;
314
                                        default:
360 daniel-mar 315
                                                throw new OIDplusException(_L('Invalid maskcode "%1" (Unknown severity "%2")',$maskcodes,$sev));
288 daniel-mar 316
                                }
317
                        }
289 daniel-mar 318
 
115 daniel-mar 319
                        // OID(x)       Save log entry into the logbook of: Object "x"
419 daniel-mar 320
                        $m = array();
115 daniel-mar 321
                        if (preg_match('@^OID\((.+)\)$@ismU', $maskcode, $m)) {
322
                                $object_id = $m[1];
1197 daniel-mar 323
                                $logEvent->addTarget(new OIDplusLogTargetObject($severity, $object_id));
360 daniel-mar 324
                                if ($object_id == '') throw new OIDplusException(_L('OID logger mask requires OID'));
115 daniel-mar 325
                        }
326
 
288 daniel-mar 327
                        // SUPOID(x)    Save log entry into the logbook of: Parent of object "x"
328
                        else if (preg_match('@^SUPOID\((.+)\)$@ismU', $maskcode, $m)) {
329
                                $object_id         = $m[1];
360 daniel-mar 330
                                if ($object_id == '') throw new OIDplusException(_L('SUPOID logger mask requires OID'));
288 daniel-mar 331
                                $obj = OIDplusObject::parse($object_id);
332
                                if ($obj) {
419 daniel-mar 333
                                        if ($objParent = $obj->getParent()) {
334
                                                $parent = $objParent->nodeId();
1197 daniel-mar 335
                                                $logEvent->addTarget(new OIDplusLogTargetObject($severity, $parent));
419 daniel-mar 336
                                        } else {
337
                                                //throw new OIDplusException(_L('%1 has no parent',$object_id));
338
                                        }
288 daniel-mar 339
                                } else {
360 daniel-mar 340
                                        throw new OIDplusException(_L('SUPOID logger mask: Invalid object %1',$object_id));
288 daniel-mar 341
                                }
342
                        }
343
 
115 daniel-mar 344
                        // OIDRA(x)?    Save log entry into the logbook of: Logged in RA of object "x"
288 daniel-mar 345
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 346
                        else if (preg_match('@^OIDRA\((.+)\)([\?\!])$@ismU', $maskcode, $m)) {
347
                                $object_id         = $m[1];
116 daniel-mar 348
                                $ra_need_login     = $m[2] == '?';
360 daniel-mar 349
                                if ($object_id == '') throw new OIDplusException(_L('OIDRA logger mask requires OID'));
115 daniel-mar 350
                                $obj = OIDplusObject::parse($object_id);
116 daniel-mar 351
                                if ($obj) {
352
                                        if ($ra_need_login) {
353
                                                foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
1197 daniel-mar 354
                                                        if ($obj->userHasWriteRights($ra)) $logEvent->addTarget(new OIDplusLogTargetUser($severity_online, $ra->raEmail()));
116 daniel-mar 355
                                                }
356
                                        } else {
1197 daniel-mar 357
                                                // $logEvent->addTarget(new OIDplusLogTargetUser($severity, $obj->getRa()->raEmail()));
116 daniel-mar 358
                                                foreach (OIDplusRA::getAllRAs() as $ra) {
1197 daniel-mar 359
                                                        if ($obj->userHasWriteRights($ra)) $logEvent->addTarget(new OIDplusLogTargetUser($severity, $ra->raEmail()));
116 daniel-mar 360
                                                }
115 daniel-mar 361
                                        }
288 daniel-mar 362
                                } else {
360 daniel-mar 363
                                        throw new OIDplusException(_L('OIDRA logger mask: Invalid object "%1"',$object_id));
115 daniel-mar 364
                                }
365
                        }
366
 
367
                        // SUPOIDRA(x)? Save log entry into the logbook of: Logged in RA that owns the superior object of "x"
288 daniel-mar 368
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 369
                        else if (preg_match('@^SUPOIDRA\((.+)\)([\?\!])$@ismU', $maskcode, $m)) {
370
                                $object_id         = $m[1];
116 daniel-mar 371
                                $ra_need_login     = $m[2] == '?';
360 daniel-mar 372
                                if ($object_id == '') throw new OIDplusException(_L('SUPOIDRA logger mask requires OID'));
115 daniel-mar 373
                                $obj = OIDplusObject::parse($object_id);
116 daniel-mar 374
                                if ($obj) {
375
                                        if ($ra_need_login) {
376
                                                foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
1197 daniel-mar 377
                                                        if ($obj->userHasParentalWriteRights($ra)) $logEvent->addTarget(new OIDplusLogTargetUser($severity_online, $ra->raEmail()));
116 daniel-mar 378
                                                }
379
                                        } else {
419 daniel-mar 380
                                                if ($objParent = $obj->getParent()) {
1197 daniel-mar 381
                                                        // $logEvent->addTarget(new OIDplusLogTargetUser($severity, $objParent->getRa()->raEmail()));
419 daniel-mar 382
                                                        foreach (OIDplusRA::getAllRAs() as $ra) {
1197 daniel-mar 383
                                                                if ($obj->userHasParentalWriteRights($ra)) $logEvent->addTarget(new OIDplusLogTargetUser($severity, $ra->raEmail()));
419 daniel-mar 384
                                                        }
385
                                                } else {
386
                                                        //throw new OIDplusException(_L('%1 has no parent, therefore also no parent RA',$object_id));
116 daniel-mar 387
                                                }
115 daniel-mar 388
                                        }
288 daniel-mar 389
                                } else {
360 daniel-mar 390
                                        throw new OIDplusException(_L('SUPOIDRA logger mask: Invalid object "%1"',$object_id));
115 daniel-mar 391
                                }
392
                        }
393
 
394
                        // RA(x)?       Save log entry into the logbook of: Logged in RA "x"
288 daniel-mar 395
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
396
                        else if (preg_match('@^RA\((.*)\)([\?\!])$@ismU', $maskcode, $m)) {
115 daniel-mar 397
                                $ra_email          = $m[1];
116 daniel-mar 398
                                $ra_need_login     = $m[2] == '?';
288 daniel-mar 399
                                if (!empty($ra_email)) {
400
                                        if ($ra_need_login && OIDplus::authUtils()->isRaLoggedIn($ra_email)) {
1197 daniel-mar 401
                                                $logEvent->addTarget(new OIDplusLogTargetUser($severity_online, $ra_email));
288 daniel-mar 402
                                        } else if (!$ra_need_login) {
1197 daniel-mar 403
                                                $logEvent->addTarget(new OIDplusLogTargetUser($severity, $ra_email));
288 daniel-mar 404
                                        }
115 daniel-mar 405
                                }
406
                        }
407
 
408
                        // A?   Save log entry into the logbook of: A logged in admin
288 daniel-mar 409
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
1140 daniel-mar 410
                        else if (preg_match('@^A([\?\!])$@imU', $maskcode, $m)) {
116 daniel-mar 411
                                $admin_need_login = $m[1] == '?';
115 daniel-mar 412
                                if ($admin_need_login && OIDplus::authUtils()->isAdminLoggedIn()) {
1197 daniel-mar 413
                                        $logEvent->addTarget(new OIDplusLogTargetUser($severity_online, 'admin'));
115 daniel-mar 414
                                } else if (!$admin_need_login) {
1197 daniel-mar 415
                                        $logEvent->addTarget(new OIDplusLogTargetUser($severity, 'admin'));
115 daniel-mar 416
                                }
417
                        }
418
 
419
                        // Unexpected
420
                        else {
360 daniel-mar 421
                                throw new OIDplusException(_L('Unexpected logger component "%1" in mask code "%2"',$maskcode,$maskcodes));
115 daniel-mar 422
                        }
116 daniel-mar 423
                }
115 daniel-mar 424
 
117 daniel-mar 425
                // Now write the log message
426
 
289 daniel-mar 427
                $result = false;
117 daniel-mar 428
 
289 daniel-mar 429
                foreach ($loggerPlugins as $plugin) {
430
                        $reason = '';
431
                        if ($plugin->available($reason)) {
1197 daniel-mar 432
                                $result |= $plugin->log($logEvent);
289 daniel-mar 433
                        }
117 daniel-mar 434
                }
435
 
289 daniel-mar 436
                return $result;
115 daniel-mar 437
        }
730 daniel-mar 438
}