Subversion Repositories oidplus

Rev

Rev 1050 | Rev 1116 | 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
 
188 daniel-mar 28
        private static function split_maskcodes($maskcodes) {
288 daniel-mar 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
                //
38
                // "[S]AAA(BBB)+CCC(DDD)"   ==> array(
39
                //                                 array(array("S"),"AAA(BBB)"),
40
                //                                 array(array("S"),"CCC(DDD)")
41
                //                              )
42
                // "[S]AAA(B+BB)+CCC(DDD)"  ==> array(
43
                //                                 array(array("S"),"AAA(B+BB)"),
44
                //                                 array(array("S"),"CCC(DDD)")
45
                //                              )
46
                // "[S]AAA(B\)BB)+CCC(DDD)" ==> array(
47
                //                                 array(array("S"),"AAA(B\)BB)"),
48
                //                                 array(array("S"),"CCC(DDD)")
49
                //                              )
289 daniel-mar 50
 
188 daniel-mar 51
                $out = array();
288 daniel-mar 52
                $sevs = array(); // Note: The severity block will repeat for the next components if not changed explicitly
289 daniel-mar 53
 
188 daniel-mar 54
                $code = '';
288 daniel-mar 55
                $sev = '';
188 daniel-mar 56
                $bracket_level = 0;
288 daniel-mar 57
                $is_escaping = false;
58
                $inside_severity_block = false;
188 daniel-mar 59
                for ($i=0; $i<strlen($maskcodes); $i++) {
60
                        $char = $maskcodes[$i];
289 daniel-mar 61
 
288 daniel-mar 62
                        if ($inside_severity_block) {
63
                                // Severity block (optional)
64
                                // e.g.  [?WARN/!OK] ==> $sevs = array("?WARN", "!OK")
65
                                if ($char == '\\') {
66
                                        if ($is_escaping) {
67
                                                $is_escaping = false;
68
                                                $sev .= $char;
69
                                        } else {
70
                                                $is_escaping = true;
71
                                        }
72
                                }
73
                                else if ($char == '[') {
74
                                        if ($is_escaping) {
75
                                                $is_escaping = false;
76
                                        } else {
77
                                                $bracket_level++;
78
                                        }
79
                                        $sev .= $char;
80
                                }
81
                                else if ($char == ']') {
82
                                        if ($is_escaping) {
83
                                                $is_escaping = false;
84
                                                $sev .= $char;
85
                                        } else {
86
                                                $bracket_level--;
87
                                                if ($bracket_level < 0) return false;
88
                                                if ($bracket_level == 0) {
89
                                                        $inside_severity_block = false;
90
                                                        if ($sev != '') $sevs[] = $sev;
91
                                                        $sev = '';
92
                                                } else {
93
                                                        $sev .= $char;
94
                                                }
95
                                        }
96
                                }
97
                                else if ((($char == '/')) && ($bracket_level == 1)) {
98
                                        if ($is_escaping) {
99
                                                $is_escaping = false;
100
                                                $sev .= $char;
101
                                        } else {
102
                                                if ($sev != '') $sevs[] = $sev;
103
                                                $sev = '';
104
                                        }
105
                                } else {
106
                                        if ($is_escaping) {
107
                                                // This would actually be an error, because we cannot escape this
108
                                                $is_escaping = false;
109
                                                $sev .= '\\' . $char;
110
                                        } else {
111
                                                $sev .= $char;
112
                                        }
113
                                }
188 daniel-mar 114
                        } else {
288 daniel-mar 115
                                // Normal data (after the severity block)
116
                                if (($char == '[') && ($code == '')) {
117
                                        $inside_severity_block = true;
118
                                        $bracket_level++;
119
                                        $sevs = array();
120
                                }
121
                                else if ($char == '\\') {
122
                                        if ($is_escaping) {
123
                                                $is_escaping = false;
124
                                                $code .= $char;
125
                                        } else {
126
                                                $is_escaping = true;
127
                                        }
128
                                }
129
                                else if ($char == '(') {
130
                                        if ($is_escaping) {
131
                                                $is_escaping = false;
132
                                        } else {
133
                                                $bracket_level++;
134
                                        }
135
                                        $code .= $char;
136
                                }
137
                                else if ($char == ')') {
138
                                        if ($is_escaping) {
139
                                                $is_escaping = false;
140
                                        } else {
141
                                                $bracket_level--;
142
                                                if ($bracket_level < 0) return false;
143
                                        }
144
                                        $code .= $char;
145
                                }
146
                                else if ((($char == '+') || ($char == '/')) && ($bracket_level == 0)) {
147
                                        if ($is_escaping) {
148
                                                $is_escaping = false;
149
                                                $code .= $char;
150
                                        } else {
151
                                                if ($code != '') $out[] = array($sevs,$code);
152
                                                $code = '';
153
                                        }
154
                                } else {
155
                                        if ($is_escaping) {
156
                                                // This would actually be an error, because we cannot escape this
157
                                                $is_escaping = false;
158
                                                $code .= '\\' . $char;
159
                                        } else {
160
                                                $code .= $char;
161
                                        }
162
                                }
188 daniel-mar 163
                        }
164
                }
288 daniel-mar 165
                if ($code != '') $out[] = array($sevs,$code);
166
                if ($inside_severity_block) return false;
188 daniel-mar 167
 
168
                return $out;
169
        }
170
 
825 daniel-mar 171
        private static $missing_plugin_queue = array();
172
 
173
        public static function reLogMissing() {
174
                while (count(self::$missing_plugin_queue) > 0) {
175
                        $item = self::$missing_plugin_queue[0];
176
                        if (!self::log_internal($item[0], $item[1], false)) return false;
177
                        array_shift(self::$missing_plugin_queue);
178
                }
179
                return true;
180
        }
181
 
263 daniel-mar 182
        public static function log($maskcodes, $event) {
825 daniel-mar 183
                self::reLogMissing(); // try to re-log failed requests
184
                return self::log_internal($maskcodes, $event, true);
185
        }
186
 
187
        private static function log_internal($maskcodes, $event, $allow_delayed_log) {
289 daniel-mar 188
                $loggerPlugins = OIDplus::getLoggerPlugins();
825 daniel-mar 189
                if (count($loggerPlugins) == 0) {
190
                        // The plugin might not be initialized in OIDplus::init()
191
                        // yet. Remember the log entries for later submission during
192
                        // OIDplus::init();
193
                        if ($allow_delayed_log) self::$missing_plugin_queue[] = array($maskcodes, $event);
194
                        return false;
195
                }
289 daniel-mar 196
 
288 daniel-mar 197
                // What is a mask code?
198
                // A mask code gives information about the log event:
199
                // 1. The severity (info, warning, error)
200
                // 2. In which logbook(s) the event shall be placed
201
                // Example:
202
                // The event would be:
203
                // "Person 'X' moves from house 'A' to house 'B'"
204
                // This event would affect the person X and the two houses,
205
                // so, instead of logging into 3 logbooks separately,
206
                // you would create a mask code that tells the system
207
                // to put the message into the logbooks of person X,
208
                // house A and house B.
115 daniel-mar 209
 
210
                $users = array();
211
                $objects = array();
212
 
288 daniel-mar 213
                // A mask code with multiple components is split into single codes
214
                // using '+' or '/', e.g. "OID(x)+RA(x)" would be split to "OID(x)" and "RA(x)"
215
                // which would result in the message being placed in the logbook of OID x,
216
                // and the logbook of the RA owning OID x.
188 daniel-mar 217
                $maskcodes_ary = self::split_maskcodes($maskcodes);
218
                if ($maskcodes_ary === false) {
360 daniel-mar 219
                        throw new OIDplusException(_L('Invalid maskcode "%1" (failed to split)',$maskcodes));
188 daniel-mar 220
                }
288 daniel-mar 221
                foreach ($maskcodes_ary as list($sevs,$maskcode)) {
222
                        // At the beginning of each mask code, you can define a severity.
223
                        // If you have a mask code with multiple components, you don't have to place the
224
                        // severity for each component. You can just leave it at the beginning.
225
                        // e.g. "[WARN]OID(x)+RA(x)" is equal to "[WARN]OID(x)+[WARN]RA(x)"
226
                        // You can also put different severities for the components:
227
                        // e.g. "[INFO]OID(x)+[WARN]RA(x)" would be a info for the OID, but a warning for the RA.
228
                        // If you want to make the severity dependent on wheather the user is logged in or not,
229
                        // prepend "?" or "!" and use '/' as delimiter
230
                        // 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
231
                        $severity = 0; // default severity = none
289 daniel-mar 232
                        $severity_online = 0;
288 daniel-mar 233
                        foreach ($sevs as $sev) {
234
                                switch (strtoupper($sev)) {
235
                                        // [OK]   = Success
236
                                        //          Numeric value: 1
237
                                        //          Rule of thumb: YOU have done something and it was successful
238
                                        case '?OK':
239
                                                $severity_online = 1;
240
                                                break;
241
                                        case '!OK':
242
                                        case  'OK':
243
                                                $severity = 1;
244
                                                break;
245
                                        // [INFO] = Informational
246
                                        //          Numeric value: 2
247
                                        //          Rule of thumb: Someone else has done something (that affects you) and it was successful
248
                                        case '?INFO':
249
                                                $severity_online = 2;
250
                                                break;
251
                                        case '!INFO':
252
                                        case  'INFO':
253
                                                $severity = 2;
254
                                                break;
255
                                        // [WARN] = Warning
256
                                        //          Numeric value: 3
257
                                        //          Rule of thumb: Something happened (probably someone did something) and it affects you
258
                                        case '?WARN':
259
                                                $severity_online = 3;
260
                                                break;
261
                                        case '!WARN':
262
                                        case  'WARN':
263
                                                $severity = 3;
264
                                                break;
265
                                        // [ERR]  = Error
266
                                        //          Numeric value: 4
267
                                        //          Rule of thumb: Something failed (probably someone did something) and it affects you
268
                                        case '?ERR':
269
                                                $severity_online = 4;
270
                                                break;
271
                                        case '!ERR':
272
                                        case  'ERR':
273
                                                $severity = 4;
274
                                                break;
275
                                        // [CRIT] = Critical
276
                                        //          Numeric value: 5
277
                                        //          Rule of thumb: Something happened (probably someone did something) which is not an error,
278
                                        //          but some critical situation (e.g. hardware failure), and it affects you
279
                                        case '?CRIT':
280
                                                $severity_online = 5;
281
                                                break;
282
                                        case '!CRIT':
283
                                        case  'CRIT':
284
                                                $severity = 5;
285
                                                break;
286
                                        default:
360 daniel-mar 287
                                                throw new OIDplusException(_L('Invalid maskcode "%1" (Unknown severity "%2")',$maskcodes,$sev));
288 daniel-mar 288
                                }
289
                        }
289 daniel-mar 290
 
115 daniel-mar 291
                        // OID(x)       Save log entry into the logbook of: Object "x"
419 daniel-mar 292
                        $m = array();
115 daniel-mar 293
                        if (preg_match('@^OID\((.+)\)$@ismU', $maskcode, $m)) {
294
                                $object_id = $m[1];
288 daniel-mar 295
                                $objects[] = array($severity, $object_id);
360 daniel-mar 296
                                if ($object_id == '') throw new OIDplusException(_L('OID logger mask requires OID'));
115 daniel-mar 297
                        }
298
 
288 daniel-mar 299
                        // SUPOID(x)    Save log entry into the logbook of: Parent of object "x"
300
                        else if (preg_match('@^SUPOID\((.+)\)$@ismU', $maskcode, $m)) {
301
                                $object_id         = $m[1];
360 daniel-mar 302
                                if ($object_id == '') throw new OIDplusException(_L('SUPOID logger mask requires OID'));
288 daniel-mar 303
                                $obj = OIDplusObject::parse($object_id);
304
                                if ($obj) {
419 daniel-mar 305
                                        if ($objParent = $obj->getParent()) {
306
                                                $parent = $objParent->nodeId();
307
                                                $objects[] = array($severity, $parent);
308
                                        } else {
309
                                                //throw new OIDplusException(_L('%1 has no parent',$object_id));
310
                                        }
288 daniel-mar 311
                                } else {
360 daniel-mar 312
                                        throw new OIDplusException(_L('SUPOID logger mask: Invalid object %1',$object_id));
288 daniel-mar 313
                                }
314
                        }
315
 
115 daniel-mar 316
                        // OIDRA(x)?    Save log entry into the logbook of: Logged in RA of object "x"
288 daniel-mar 317
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 318
                        else if (preg_match('@^OIDRA\((.+)\)([\?\!])$@ismU', $maskcode, $m)) {
319
                                $object_id         = $m[1];
116 daniel-mar 320
                                $ra_need_login     = $m[2] == '?';
360 daniel-mar 321
                                if ($object_id == '') throw new OIDplusException(_L('OIDRA logger mask requires OID'));
115 daniel-mar 322
                                $obj = OIDplusObject::parse($object_id);
116 daniel-mar 323
                                if ($obj) {
324
                                        if ($ra_need_login) {
325
                                                foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
288 daniel-mar 326
                                                        if ($obj->userHasWriteRights($ra)) $users[] = array($severity_online, $ra->raEmail());
116 daniel-mar 327
                                                }
328
                                        } else {
288 daniel-mar 329
                                                // $users[] = array($severity, $obj->getRa()->raEmail());
116 daniel-mar 330
                                                foreach (OIDplusRA::getAllRAs() as $ra) {
288 daniel-mar 331
                                                        if ($obj->userHasWriteRights($ra)) $users[] = array($severity, $ra->raEmail());
116 daniel-mar 332
                                                }
115 daniel-mar 333
                                        }
288 daniel-mar 334
                                } else {
360 daniel-mar 335
                                        throw new OIDplusException(_L('OIDRA logger mask: Invalid object "%1"',$object_id));
115 daniel-mar 336
                                }
337
                        }
338
 
339
                        // SUPOIDRA(x)? Save log entry into the logbook of: Logged in RA that owns the superior object of "x"
288 daniel-mar 340
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 341
                        else if (preg_match('@^SUPOIDRA\((.+)\)([\?\!])$@ismU', $maskcode, $m)) {
342
                                $object_id         = $m[1];
116 daniel-mar 343
                                $ra_need_login     = $m[2] == '?';
360 daniel-mar 344
                                if ($object_id == '') throw new OIDplusException(_L('SUPOIDRA logger mask requires OID'));
115 daniel-mar 345
                                $obj = OIDplusObject::parse($object_id);
116 daniel-mar 346
                                if ($obj) {
347
                                        if ($ra_need_login) {
348
                                                foreach (OIDplus::authUtils()->loggedInRaList() as $ra) {
288 daniel-mar 349
                                                        if ($obj->userHasParentalWriteRights($ra)) $users[] = array($severity_online, $ra->raEmail());
116 daniel-mar 350
                                                }
351
                                        } else {
419 daniel-mar 352
                                                if ($objParent = $obj->getParent()) {
353
                                                        // $users[] = array($severity, $objParent->getRa()->raEmail());
354
                                                        foreach (OIDplusRA::getAllRAs() as $ra) {
355
                                                                if ($obj->userHasParentalWriteRights($ra)) $users[] = array($severity, $ra->raEmail());
356
                                                        }
357
                                                } else {
358
                                                        //throw new OIDplusException(_L('%1 has no parent, therefore also no parent RA',$object_id));
116 daniel-mar 359
                                                }
115 daniel-mar 360
                                        }
288 daniel-mar 361
                                } else {
360 daniel-mar 362
                                        throw new OIDplusException(_L('SUPOIDRA logger mask: Invalid object "%1"',$object_id));
115 daniel-mar 363
                                }
364
                        }
365
 
366
                        // RA(x)?       Save log entry into the logbook of: Logged in RA "x"
288 daniel-mar 367
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
368
                        else if (preg_match('@^RA\((.*)\)([\?\!])$@ismU', $maskcode, $m)) {
115 daniel-mar 369
                                $ra_email          = $m[1];
116 daniel-mar 370
                                $ra_need_login     = $m[2] == '?';
288 daniel-mar 371
                                if (!empty($ra_email)) {
372
                                        if ($ra_need_login && OIDplus::authUtils()->isRaLoggedIn($ra_email)) {
373
                                                $users[] = array($severity_online, $ra_email);
374
                                        } else if (!$ra_need_login) {
375
                                                $users[] = array($severity, $ra_email);
376
                                        }
115 daniel-mar 377
                                }
378
                        }
379
 
380
                        // A?   Save log entry into the logbook of: A logged in admin
288 daniel-mar 381
                        // Remove or replace "?" by "!" if the entity does not need to be logged in
115 daniel-mar 382
                        else if (preg_match('@^A([\?\!])$@ismU', $maskcode, $m)) {
116 daniel-mar 383
                                $admin_need_login = $m[1] == '?';
115 daniel-mar 384
                                if ($admin_need_login && OIDplus::authUtils()->isAdminLoggedIn()) {
288 daniel-mar 385
                                        $users[] = array($severity_online, 'admin');
115 daniel-mar 386
                                } else if (!$admin_need_login) {
288 daniel-mar 387
                                        $users[] = array($severity, 'admin');
115 daniel-mar 388
                                }
389
                        }
390
 
391
                        // Unexpected
392
                        else {
360 daniel-mar 393
                                throw new OIDplusException(_L('Unexpected logger component "%1" in mask code "%2"',$maskcode,$maskcodes));
115 daniel-mar 394
                        }
116 daniel-mar 395
                }
115 daniel-mar 396
 
117 daniel-mar 397
                // Now write the log message
398
 
289 daniel-mar 399
                $result = false;
117 daniel-mar 400
 
289 daniel-mar 401
                foreach ($loggerPlugins as $plugin) {
402
                        $reason = '';
403
                        if ($plugin->available($reason)) {
404
                                $result |= $plugin->log($event, $users, $objects);
405
                        }
117 daniel-mar 406
                }
407
 
289 daniel-mar 408
                return $result;
115 daniel-mar 409
        }
730 daniel-mar 410
}