Issues (141)

src/Service/Util.php (43 issues)

1
<?php
2
3
namespace CILogon\Service;
4
5
require_once 'DB.php';
6
7
use CILogon\Service\CSRF;
8
use CILogon\Service\Loggit;
9
use CILogon\Service\IdpList;
10
use CILogon\Service\DBService;
11
use CILogon\Service\SessionMgr;
12
use CILogon\Service\Skin;
13
use CILogon\Service\TimeIt;
14
use CILogon\Service\PortalCookie;
15
use PEAR;
16
use DB;
17
18
/**
19
 * Util
20
 *
21
 * This class contains a bunch of static (class) utility
22
 * methods, for example getting and setting server environment
23
 * variables and handling cookies. See the header for each function for
24
 * detailed description.
25
 */
26
class Util
27
{
28
    /**
29
     * @var array $ini_array Read the cilogon.ini file into an array
30
     */
31
    public static $ini_array = null;
32
33
    /**
34
     * @var TimeIt $timeit Initialize by calling static::startTiming() in
35
     * init().
36
     */
37
    public static $timeit;
38
39
    /**
40
     * @var IdPList $idplist A 'global' IdpList object since dplist.xml is
41
     *      large and expensive to create multiple times.
42
     */
43
    public static $idplist = null;
44
45
    /**
46
     * @var CSRF $csrf A 'global' CSRF token object to set the CSRF cookie
47
     * and print the hidden CSRF form element. Needs to be set only once
48
     * to keep the same CSRF value through the session.
49
     */
50
    public static $csrf = null;
51
52
    /**
53
     * @var Skin $skin A 'global' Skin object for skin configuration.
54
     */
55
    public static $skin = null;
56
57
    /**
58
     * @var array $oauth2idps An array of OAuth2 Identity Providers.
59
     */
60
    public static $oauth2idps = ['Google', 'GitHub', 'ORCID'];
61
62
63
    /**
64
     * getIdPList
65
     *
66
     * This function initializes the class $idplist object (if not yet
67
     * created) and returns it. This allows for a single 'global'
68
     * $idplist to be used by other classes (since creating an IdPList
69
     * object is expensive).
70
     *
71
     * @return IdPList|null The class instantiated IdPList object.
72
     **/
73
    public static function getIdpList()
74
    {
75
        if (is_null(static::$idplist)) {
76
            static::$idplist = new IdpList();
77
        }
78
        return static::$idplist;
79
    }
80
81
    /**
82
     * getCsrf
83
     *
84
     * This function initializes the class $csrf object (if not yet
85
     * created) and returns it. This allows for a single 'global'
86
     * $csrf to be used by other classes (since we want the CSRV value
87
     * to be consistent for the current page load).
88
     *
89
     * @return CSRF|null The class instantiated CSRF object.
90
     */
91
    public static function getCsrf()
92
    {
93
        if (is_null(static::$csrf)) {
94
            static::$csrf = new CSRF();
95
        }
96
        return static::$csrf;
97
    }
98
99
    /**
100
     * getSkin
101
     *
102
     * This function initializes the class $skin object (if not yet
103
     * created) and returns it. This allows for a single 'global'
104
     * $skin to be used by other classes (since loading the skin is
105
     * potentially expensive).
106
     *
107
     * @return Skin|null The class instantiated Skin object.
108
     */
109
    public static function getSkin()
110
    {
111
        if (is_null(static::$skin)) {
112
            static::$skin = new Skin();
113
        }
114
        return static::$skin;
115
    }
116
117
    /**
118
     * startTiming
119
     *
120
     * This function initializes the class variable $timeit which is
121
     * used for timing/benchmarking purposes.
122
     */
123
    public static function startTiming()
124
    {
125
        static::$timeit = new TimeIt(TimeIt::DEFAULTFILENAME, true);
126
    }
127
128
    /**
129
     * getServerVar
130
     *
131
     * This function queries a given $_SERVER variable (which is set
132
     * by the Apache server) and returns the value.
133
     *
134
     * @param string $serv The $_SERVER variable to query.
135
     * @return string The value of the $_SERVER variable or empty string
136
     *         if that variable is not set.
137
     */
138
    public static function getServerVar($serv)
139
    {
140
        $retval = '';
141
        if (isset($_SERVER[$serv])) {
142
            $retval = $_SERVER[$serv];
143
        }
144
        return $retval;
145
    }
146
147
    /**
148
     * getGetVar
149
     *
150
     * This function queries a given $_GET parameter (which is set in
151
     * the URL via a '?parameter=value' parameter) and returns the
152
     * value.
153
     *
154
     * @param string $get The $_GET variable to query.
155
     * @return string The value of the $_GET variable or empty string if
156
     *         that variable is not set.
157
     */
158
    public static function getGetVar($get)
159
    {
160
        $retval = '';
161
        if (isset($_GET[$get])) {
162
            $retval = $_GET[$get];
163
        }
164
        return $retval;
165
    }
166
167
    /**
168
     * getPostVar
169
     *
170
     * This function queries a given $_POST variable (which is set when
171
     * the user submits a form, for example) and returns the value.
172
     *
173
     * @param string $post The $_POST variable to query.
174
     * @return string The value of the $_POST variable or empty string if
175
     *         that variable is not set.
176
     */
177
    public static function getPostVar($post)
178
    {
179
        $retval = '';
180
        if (isset($_POST[$post])) {
181
            $retval = $_POST[$post];
182
        }
183
        return $retval;
184
    }
185
186
    /**
187
     * getGetOrPostVar
188
     *
189
     * This function looks for a $_GET or $_POST variable, with
190
     * preference given to $_GET if both are present.
191
     *
192
     * @param string $var The $_GET or $_POST variable to query.
193
     * @return string The value of the $_GET or $_POST variable
194
     *         if present. Empty string if variable is not set.
195
     */
196
    public static function getGetOrPostVar($var)
197
    {
198
        $retval = static::getGetVar($var);
199
        if (empty($retval)) {
200
            $retval = static::getPostVar($var);
201
        }
202
        return $retval;
203
    }
204
205
    /**
206
     * getCookieVar
207
     *
208
     * This function returns the value of a given cookie.
209
     *
210
     * @param string $cookie he $_COOKIE variable to query.
211
     * @return string The value of the $_COOKIE variable or empty string
212
     *         if that variable is not set.
213
     */
214
    public static function getCookieVar($cookie)
215
    {
216
        $retval = '';
217
        if (isset($_COOKIE[$cookie])) {
218
            $retval = $_COOKIE[$cookie];
219
        }
220
        return $retval;
221
    }
222
223
    /**
224
     * setCookieVar
225
     *
226
     * This function sets a cookie.
227
     *
228
     * @param string $cookie The name of the cookie to set.
229
     * @param string $value (Optional) The value to set for the cookie.
230
     *        Defaults to empty string.
231
     * @param int $exp The future expiration time (in seconds) of the
232
     *        cookie. Defaults to 1 year from now. If set to 0,
233
     *        the cookie expires at the end of the session.
234
     */
235
    public static function setCookieVar($cookie, $value = '', $exp = 31536000)
236
    {
237
        if ($exp > 0) {
238
            $exp += time();
239
        }
240
        setcookie($cookie, $value, $exp, '/', '.' . static::getDN(), true);
241
        $_COOKIE[$cookie] = $value;
242
    }
243
244
    /**
245
     * unsetCookieVar
246
     *
247
     * This function unsets a cookie. Strictly speaking, the cookie is
248
     * not removed, rather it is set to an empty value with an expired
249
     * time.
250
     *
251
     * @param string $cookie The name of the cookie to unset (delete).
252
     */
253
    public static function unsetCookieVar($cookie)
254
    {
255
        setcookie($cookie, '', 1, '/', '.' . static::getDN(), true);
256
        unset($_COOKIE[$cookie]);
257
    }
258
259
    /**
260
     * getPortalOrCookieVar
261
     *
262
     * This is a convenience function which first checks if there is a
263
     * OAuth 1.0a ('delegate') or OIDC ('authorize') session active.
264
     * If so, it attempts to get the requested cookie from the
265
     * associated portalcookie. If there is not an OAuth/OIDC session
266
     * active, it looks for a 'normal' cookie. If you need a
267
     * portalcookie object to do multiple get/set method calls from
268
     * one function, it is probably better NOT to use this method since
269
     * creating the portalcookie object is potentially expensive.
270
     *
271
     * @param string $cookie The name of the cookie to get.
272
     * @return string The cookie value from either the portalcookie
273
     *         (in the case of an active OAuth session) or the
274
     *         'normal' cookie. Return empty string if no matching
275
     *         cookie in either place.
276
     */
277
    public static function getPortalOrCookieVar($cookie)
278
    {
279
        $retval = '';
280
        $pc = new PortalCookie();
281
        $pn = $pc->getPortalName();
282
        if (strlen($pn) > 0) {
283
            $retval = $pc->get($cookie);
284
        } else {
285
            $retval = static::getCookieVar($cookie);
286
        }
287
        return $retval;
288
    }
289
290
    /**
291
     * getSessionVar
292
     *
293
     * This function returns the value of a given PHP Session variable.
294
     *
295
     * @param string $sess The $_SESSION variable to query.
296
     * @return string The value of the $_SESSION variable or empty string
297
     *         if that variable is not set.
298
     */
299
    public static function getSessionVar($sess)
300
    {
301
        $retval = '';
302
        if (isset($_SESSION[$sess])) {
303
            $retval = $_SESSION[$sess];
304
        }
305
        return $retval;
306
    }
307
308
    /**
309
     * setSessionVar
310
     *
311
     * This function can set or unset a given PHP session variable.
312
     * The first parameter is the PHP session variable to set/unset.
313
     * If the second parameter is the empty string, then the session
314
     * variable is unset.  Otherwise, the session variable is set to
315
     * the second parameter.  The function returns true if the session
316
     * variable was set to a non-empty value, false otherwise.
317
     * Normally, the return value can be ignored.
318
     *
319
     * @param string $key The name of the PHP session variable to set
320
     *        (or unset).
321
     * @param string $value (Optional) The value of the PHP session variable
322
     *        (to set), or empty string (to unset). Defaults to empty
323
     *        string (implies unset the session variable).
324
     * @return bool True if the PHP session variable was set to a
325
     *         non-empty string, false if variable was unset or if
326
     *         the specified session variable was not previously set.
327
     */
328
    public static function setSessionVar($key, $value = '')
329
    {
330
        $retval = false;  // Assume we want to unset the session variable
331
        if (strlen($key) > 0) {  // Make sure session var name was passed in
332
            if (strlen($value) > 0) {
333
                $_SESSION[$key] = $value;
334
                $retval = true;
335
            } else {
336
                static::unsetSessionVar($key);
337
            }
338
        }
339
        return $retval;
340
    }
341
342
    /**
343
     * unsetSessionVar
344
     *
345
     * This function clears the given PHP session variable by first
346
     * setting it to null and then unsetting it entirely.
347
     *
348
     * @param string $sess The $_SESSION variable to erase.
349
     */
350
    public static function unsetSessionVar($sess)
351
    {
352
        if (isset($_SESSION[$sess])) {
353
            $_SESSION[$sess] = null;
354
            unset($_SESSION[$sess]);
355
        }
356
    }
357
358
    /**
359
     * removeShibCookies
360
     *
361
     * This function removes all '_shib*' cookies currently in the
362
     * user's browser session. In effect, this logs the user out of
363
     * any IdP. Note that you must call this before you output any
364
     * HTML. Strictly speaking, the cookies are not removed, rather
365
     * they are set to empty values with expired times.
366
     */
367
    public static function removeShibCookies()
368
    {
369
        foreach ($_COOKIE as $key => $value) {
370
            if (strncmp($key, '_shib', strlen('_shib')) == 0) {
371
                static::unsetCookieVar($key);
372
            }
373
        }
374
    }
375
376
    /**
377
     * startPHPSession
378
     *
379
     * This function starts a secure PHP session and should be called
380
     * at the beginning of each script before any HTML is output.  It
381
     * does a trick of setting a 'lastaccess' time so that the
382
     * $_SESSION variable does not expire without warning.
383
     *
384
     * @param string $storetype (Optional) Storage location of the PHP
385
     *        session data, one of 'file' or 'mysql'. Defaults to null,
386
     *        which means use the value of STORAGE_PHPSESSIONS from the
387
     *        config.php file, or 'file' if no such parameter configured.
388
     */
389
    public static function startPHPSession($storetype = null)
390
    {
391
        // No parameter given? Use the value read in from cilogon.ini file.
392
        if (is_null($storetype)) {
393
            $storetype = STORAGE_PHPSESSIONS;
0 ignored issues
show
The constant CILogon\Service\STORAGE_PHPSESSIONS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
394
        }
395
396
        if (preg_match('/^mysql/', $storetype)) {
397
            // If STORAGE_PHPSESSIONS == 'mysqli', create a sessionmgr().
398
            $sessionmgr = new SessionMgr();
0 ignored issues
show
The assignment to $sessionmgr is dead and can be removed.
Loading history...
399
        } elseif ($storetype == 'file') {
400
            // If storing PHP sessions to file, check if an optional directory
401
            // for storage has been set. If so, create it if necessary.
402
            if ((defined('STORAGE_PHPSESSIONS_DIR')) && (!empty(STORAGE_PHPSESSIONS_DIR))) {
0 ignored issues
show
The constant CILogon\Service\STORAGE_PHPSESSIONS_DIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
403
                if (!is_dir(STORAGE_PHPSESSIONS_DIR)) {
404
                    mkdir(STORAGE_PHPSESSIONS_DIR, 0770, true);
405
                }
406
407
                if (is_dir(STORAGE_PHPSESSIONS_DIR)) {
408
                    ini_set('session.save_path', STORAGE_PHPSESSIONS_DIR);
409
                }
410
            }
411
        }
412
413
        ini_set('session.cookie_secure', true);
0 ignored issues
show
true of type true is incompatible with the type string expected by parameter $value of ini_set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

413
        ini_set('session.cookie_secure', /** @scrutinizer ignore-type */ true);
Loading history...
414
        ini_set('session.cookie_domain', '.' . static::getDN());
415
        session_start();
416
        if (
417
            (!isset($_SESSION['lastaccess']) ||
418
            (time() - $_SESSION['lastaccess']) > 60)
419
        ) {
420
            $_SESSION['lastaccess'] = time();
421
        }
422
    }
423
424
    /**
425
     * getScriptDir
426
     *
427
     * This function returns the directory (or full url) of the script
428
     * that is currently running.  The returned directory/url is
429
     * terminated by a '/' character (unless the second parameter is
430
     * set to true). This function is useful for those scripts named
431
     * index.php where we don't want to actually see 'index.php' in the
432
     * address bar (again, unless the second parameter is set to true).
433
     *
434
     * @param bool $prependhttp (Optional) Boolean to prepend 'http(s)://' to
435
     *        the script name. Defaults to false.
436
     * @param bool $stripfile (Optional) Boolean to strip off the trailing
437
     *        filename (e.g. index.php) from the path.
438
     *        Defaults to true (i.e., defaults to directory
439
     *        only without the trailing filename).
440
     * @return string The directory or url of the current script, with or
441
     *         without the trailing .php filename.
442
     */
443
    public static function getScriptDir($prependhttp = false, $stripfile = true)
444
    {
445
        $retval = static::getServerVar('SCRIPT_NAME');
446
        if ($stripfile) {
447
            $retval = dirname($retval);
448
        }
449
        if ($retval == '.') {
450
            $retval = '';
451
        }
452
        if (
453
            (strlen($retval) == 0) ||
454
            ($stripfile && ($retval[strlen($retval) - 1] != '/'))
455
        ) {
456
            $retval .= '/';  // Append a slash if necessary
457
        }
458
        if ($prependhttp) {  // Prepend http(s)://hostname
459
            $retval = 'http' .
460
                      ((strtolower(static::getServerVar('HTTPS')) == 'on') ? 's' : '') .
461
                      '://' . static::getServerVar('HTTP_HOST') . $retval;
462
        }
463
        return $retval;
464
    }
465
466
    /**
467
     * tempDir
468
     *
469
     * This function creates a temporary subdirectory within the
470
     * specified subdirectory. The new directory name is composed of
471
     * 16 hexadecimal letters, plus any prefix if you specify one. The
472
     * full path of the the newly created directory is returned.
473
     *
474
     * @param string $dir The full path to the containing directory.
475
     * @param string $prefix (Optional) A prefix for the new temporary
476
     *        directory. Defaults to empty string.
477
     * @param int $mode (Optional) Access permissions for the new
478
     *        temporary directory. Defaults to 0775.
479
     * @return string Full path to the newly created temporary directory.
480
     */
481
    public static function tempDir($dir, $prefix = '', $mode = 0775)
482
    {
483
        if (substr($dir, -1) != '/') {
484
            $dir .= '/';
485
        }
486
487
        $path = '';
0 ignored issues
show
The assignment to $path is dead and can be removed.
Loading history...
488
        do {
489
            $path = $dir . $prefix . sprintf("%08X%08X", mt_rand(), mt_rand());
490
        } while (!mkdir($path, $mode, true));
491
492
        return $path;
493
    }
494
495
    /**
496
     * deleteDir
497
     *
498
     * This function deletes a directory and all of its contents.
499
     *
500
     * @param string $dir The (possibly non-empty) directory to delete.
501
     * @param bool $shred (Optional) Shred the file before deleting?
502
     *        Defaults to false.
503
     */
504
    public static function deleteDir($dir, $shred = false)
505
    {
506
        if (is_dir($dir)) {
507
            $objects = scandir($dir);
508
            foreach ($objects as $object) {
509
                if ($object != "." && $object != "..") {
510
                    if (filetype($dir . "/" . $object) == "dir") {
511
                        static::deleteDir($dir . "/" . $object);
512
                    } else {
513
                        if ($shred) {
514
                            @exec('/bin/env /usr/bin/shred -u -z ' . $dir . "/" . $object);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for exec(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

514
                            /** @scrutinizer ignore-unhandled */ @exec('/bin/env /usr/bin/shred -u -z ' . $dir . "/" . $object);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
515
                        } else {
516
                            @unlink($dir . "/" . $object);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

516
                            /** @scrutinizer ignore-unhandled */ @unlink($dir . "/" . $object);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
517
                        }
518
                    }
519
                }
520
            }
521
            reset($objects);
522
            @rmdir($dir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rmdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

522
            /** @scrutinizer ignore-unhandled */ @rmdir($dir);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
523
        }
524
    }
525
526
    /**
527
     * htmlent
528
     *
529
     * This method is necessary since htmlentities() does not seem to
530
     * obey the default arguments as documented in the PHP manual, and
531
     * instead encodes accented characters incorrectly. By specifying
532
     * the flags and encoding, the problem is solved.
533
     *
534
     * @param string $str : A string to process with htmlentities().
535
     * @return string The input string processed by htmlentities with
536
     *         specific options.
537
     */
538
    public static function htmlent($str)
539
    {
540
        return htmlentities($str, ENT_COMPAT | ENT_HTML401, 'UTF-8');
541
    }
542
543
    /**
544
     * sendErrorAlert
545
     *
546
     * Use this function to send an error message. The $summary should
547
     * be a short description of the error since it is placed in the
548
     * subject of the email. Put a more verbose description of the
549
     * error in the $detail parameter. Any session variables available
550
     * are appended to the body of the message.
551
     *
552
     * @param string $summary A brief summary of the error (in email subject)
553
     * @param string $detail A detailed description of the error (in the
554
     *        email body)
555
     * @param string $mailto (Optional) The destination email address.
556
     *        Defaults to EMAIL_ALERTS (defined in the top-level
557
     *        config.php file as 'alerts@' . DEFAULT_HOSTNAME).
558
     */
559
    public static function sendErrorAlert(
560
        $summary,
561
        $detail,
562
        $mailto = EMAIL_ALERTS
0 ignored issues
show
The constant CILogon\Service\EMAIL_ALERTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
563
    ) {
564
        $sessionvars = array(
565
            'idp'                => 'IdP ID',
566
            'idp_display_name'   => 'IdP Name',
567
            'user_uid'           => 'User UID',
568
            'distinguished_name' => 'Cert DN',
569
            'first_name'         => 'First Name',
570
            'last_name'          => 'Last Name',
571
            'display_name'       => 'Display Name',
572
            'eppn'               => 'ePPN',
573
            'eptid'              => 'ePTID',
574
            'open_id'            => 'OpenID ID',
575
            'oidc'               => 'OIDC ID',
576
            'subject_id'         => 'Subject ID',
577
            'pairwise_id'        => 'Pairwise ID',
578
            'loa'                => 'LOA',
579
            'affiliation'        => 'Affiliation',
580
            'ou'                 => 'OU',
581
            'member_of'          => 'MemberOf',
582
            'acr'                => 'AuthnContextClassRef',
583
            'amr'                => 'AuthnMethodRef',
584
            'entitlement'        => 'Entitlement',
585
            'itrustuin'          => 'iTrustUIN',
586
            'cilogon_skin'       => 'Skin Name',
587
            'authntime'          => 'Authn Time'
588
        );
589
590
        $remoteaddr = static::getServerVar('REMOTE_ADDR');
591
        $remotehost = gethostbyaddr($remoteaddr);
592
        $mailfrom = 'From: ' . EMAIL_ALERTS . "\r\n" .
593
                    'X-Mailer: PHP/' . phpversion();
594
        $mailsubj = 'CILogon Service on ' . php_uname('n') .
595
                    ' - ' . $summary;
596
        $mailmsg  = '
597
CILogon Service - ' . $summary . '
598
-----------------------------------------------------------
599
' . $detail . '
600
601
Session Variables
602
-----------------
603
Timestamp     = ' . date(DATE_ATOM) . '
604
Server Host   = ' . static::getHN() . '
605
Remote Address= ' . $remoteaddr . '
606
' . (($remotehost !== false) ? "Remote Host   = $remotehost" : '') . '
607
';
608
609
        foreach ($sessionvars as $svar => $sname) {
610
            if (strlen($val = static::getSessionVar($svar)) > 0) {
611
                $mailmsg .= sprintf("%-14s= %s\n", $sname, $val);
612
            }
613
        }
614
615
        mail($mailto, $mailsubj, $mailmsg, $mailfrom);
616
    }
617
618
    /**
619
     * getHN
620
     *
621
     * This function calculates and returns the 'hostname' for the
622
     * server. It first checks HTTP_HOST. If not set OR if not a
623
     * FQDN (with at least one '.'), it returns DEFAULT_HOSTNAME.
624
     * This is needed by command line scripts.
625
     *
626
     * @return string The 'Hostname' for the web server.
627
     */
628
    public static function getHN()
629
    {
630
        $thehostname = static::getServerVar('HTTP_HOST');
631
        if (
632
            (strlen($thehostname) == 0) ||
633
            (strpos($thehostname, '.') === false)
634
        ) {
635
            $thehostname = DEFAULT_HOSTNAME;
0 ignored issues
show
The constant CILogon\Service\DEFAULT_HOSTNAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
636
        }
637
        return $thehostname;
638
    }
639
640
    /**
641
     * getDN
642
     *
643
     * This function calculates and returns the 'domainname' for the
644
     * server. It uses the hostname value calculated by getHN() and
645
     * uses the last two segments.
646
     *
647
     * @return string The 'Domainname' for the web server.
648
     */
649
    public static function getDN()
650
    {
651
        $thedomainname = static::getHN();
652
        if (preg_match('/[^\.]+\.[^\.]+$/', $thedomainname, $matches)) {
653
            $thedomainname = $matches[0];
654
        }
655
        return $thedomainname;
656
    }
657
658
    /**
659
     * getAuthzUrl
660
     *
661
     * This funtion takes in the name of an IdP (e.g., 'Google') and
662
     * returns the assoicated OAuth2 authorization URL.
663
     *
664
     * @param string $idp The name of an OAuth2 Identity Provider.
665
     * @return string The authorization URL for the given IdP.
666
     */
667
    public static function getAuthzUrl($idp)
668
    {
669
        $url = null;
670
        $idptourl = array(
671
            'Google' => 'https://accounts.google.com/o/oauth2/auth',
672
            'GitHub' => 'https://github.com/login/oauth/authorize',
673
            'ORCID'  => 'https://orcid.org/oauth/authorize',
674
        );
675
        if (array_key_exists($idp, $idptourl)) {
676
            $url = $idptourl[$idp];
677
        }
678
        return $url;
679
    }
680
681
    /**
682
     * getAuthzIdP
683
     *
684
     * This function takes in the OAuth2 authorization URL and returns
685
     * the associated pretty-print name of the IdP.
686
     *
687
     * @param string $url The authorization URL of an OAuth2 Identity Provider.
688
     * @return string The name of the IdP.
689
     */
690
    public static function getAuthzIdP($url)
691
    {
692
        $idp = null;
693
        $urltoidp = array(
694
            'https://accounts.google.com/o/oauth2/auth' => 'Google',
695
            'https://github.com/login/oauth/authorize'  => 'GitHub',
696
            'https://orcid.org/oauth/authorize'         => 'ORCID',
697
        );
698
        if (array_key_exists($url, $urltoidp)) {
699
            $idp = $urltoidp[$url];
700
        }
701
        return $idp;
702
    }
703
704
    /**
705
     * saveUserToDataStore
706
     *
707
     * This function is called when a user logs on to save identity
708
     * information to the datastore. As it is used by both Shibboleth
709
     * and OpenID Identity Providers, some parameters passed in may
710
     * be blank (empty string). If the function verifies that the minimal
711
     * sets of parameters are valid, the dbservice servlet is called
712
     * to save the user info. Then various session variables are set
713
     * for use by the program later on. In case of error, an email
714
     * alert is sent showing the missing parameters.
715
     *
716
     * @param mixed $args Variable number of parameters, the same as those
717
     *        in DBService::$user_attrs
718
     */
719
    public static function saveUserToDataStore(...$args)
720
    {
721
        $dbs = new DBService();
722
723
        // Save the passed-in variables to the session for later use
724
        // (e.g., by the error handler in handleGotUser). Then get these
725
        // session variables into local vars for ease of use.
726
        static::setUserAttributeSessionVars(...$args);
727
728
        // This bit of trickery sets local variables from the PHP session
729
        // that was just populated, using the names in the $user_attrs array.
730
        foreach (DBService::$user_attrs as $value) {
731
            $$value = static::getSessionVar($value);
732
        }
733
734
        // For the new Google OAuth 2.0 endpoint, we want to keep the
735
        // old Google OpenID endpoint URL in the database (so user does
736
        // not get a new certificate subject DN). Change the idp
737
        // and idp_display_name to the old Google OpenID values.
738
        if (
739
            ($idp_display_name == 'Google+') ||
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $idp_display_name seems to be never defined.
Loading history...
740
            ($idp == static::getAuthzUrl('Google'))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $idp seems to be never defined.
Loading history...
741
        ) {
742
            $idp_display_name = 'Google';
743
            $idp = 'https://www.google.com/accounts/o8/id';
744
        }
745
746
        // In the database, keep a consistent ProviderId format: only
747
        // allow 'http' (not 'https') and remove any 'www.' prefix.
748
        if ($loa == 'openid') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $loa seems to be never defined.
Loading history...
749
            $idp = preg_replace('%^https://(www\.)?%', 'http://', $idp);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $idp does not seem to be defined for all execution paths leading up to this point.
Loading history...
750
        }
751
752
        // Call the dbService to get the user using IdP attributes.
753
        $result = $dbs->getUser(
754
            $remote_user,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $remote_user seems to be never defined.
Loading history...
755
            $idp,
756
            $idp_display_name,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $idp_display_name does not seem to be defined for all execution paths leading up to this point.
Loading history...
757
            $first_name,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $first_name seems to be never defined.
Loading history...
758
            $last_name,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $last_name seems to be never defined.
Loading history...
759
            $display_name,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $display_name does not exist. Did you maybe mean $idp_display_name?
Loading history...
760
            $email,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $email seems to be never defined.
Loading history...
761
            $loa,
762
            $eppn,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $eppn seems to be never defined.
Loading history...
763
            $eptid,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $eptid seems to be never defined.
Loading history...
764
            $open_id,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $open_id seems to be never defined.
Loading history...
765
            $oidc,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $oidc seems to be never defined.
Loading history...
766
            $subject_id,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $subject_id seems to be never defined.
Loading history...
767
            $pairwise_id,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pairwise_id seems to be never defined.
Loading history...
768
            $affiliation,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $affiliation seems to be never defined.
Loading history...
769
            $ou,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ou seems to be never defined.
Loading history...
770
            $member_of,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $member_of seems to be never defined.
Loading history...
771
            $acr,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $acr seems to be never defined.
Loading history...
772
            $amr,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $amr seems to be never defined.
Loading history...
773
            $entitlement,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $entitlement seems to be never defined.
Loading history...
774
            $itrustuin
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $itrustuin seems to be never defined.
Loading history...
775
        );
776
        if ($result) {
777
            static::setSessionVar('user_uid', $dbs->user_uid);
778
            static::setSessionVar('distinguished_name', $dbs->distinguished_name);
779
            static::setSessionVar('status', $dbs->status);
780
        } else {
781
            static::sendErrorAlert(
782
                'dbService Error',
783
                'Error calling dbservice action "getUser" in ' .
784
                'saveUserToDatastore() method.'
785
            );
786
            static::unsetSessionVar('user_uid');
787
            static::unsetSessionVar('distinguished_name');
788
            static::setSessionVar('status', DBService::$STATUS['STATUS_INTERNAL_ERROR']);
789
        }
790
791
        // If 'status' is not STATUS_OK*, then send an error email
792
        $status = static::getSessionVar('status');
793
        if ($status & 1) { // Bad status codes are odd
794
            // For missing parameter errors, log an error message
795
            if (
796
                $status ==
797
                DBService::$STATUS['STATUS_MISSING_PARAMETER_ERROR']
798
            ) {
799
                $log = new Loggit();
800
                $log->error('STATUS_MISSING_PARAMETER_ERROR', true);
801
            }
802
803
            // For other dbservice errors OR for any error involving
804
            // LIGO (e.g., missing parameter error), send email alert.
805
            if (
806
                ($status !=
807
                    DBService::$STATUS['STATUS_MISSING_PARAMETER_ERROR']) ||
808
                (preg_match('/ligo\.org/', $idp))
809
            ) {
810
                $mailto = EMAIL_ALERTS;
0 ignored issues
show
The constant CILogon\Service\EMAIL_ALERTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
811
812
                // CIL-205 - Notify LIGO about IdP login errors.
813
                // Set DISABLE_LIGO_ALERTS to true in the top-level
814
                // config.php file to stop LIGO failures
815
                // from being sent to EMAIL_ALERTS, but still
816
                // sent to '[email protected]'.
817
                if (preg_match('/ligo\.org/', $idp)) {
818
                    if (DISABLE_LIGO_ALERTS) {
0 ignored issues
show
The constant CILogon\Service\DISABLE_LIGO_ALERTS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
819
                        $mailto = '';
820
                    }
821
                    $mailto .= ((strlen($mailto) > 0) ? ',' : '') .
822
                        '[email protected]';
823
                }
824
825
                static::sendErrorAlert(
826
                    'Failure in ' .
827
                        (($loa == 'openid') ? '' : '/secure') . '/getuser/',
828
                    'Remote_User   = ' . ((strlen($remote_user) > 0) ?
829
                        $remote_user : '<MISSING>') . "\n" .
830
                    'IdP ID        = ' . ((strlen($idp) > 0) ?
831
                        $idp : '<MISSING>') . "\n" .
832
                    'IdP Name      = ' . ((strlen($idp_display_name) > 0) ?
833
                        $idp_display_name : '<MISSING>') . "\n" .
834
                    'First Name    = ' . ((strlen($first_name) > 0) ?
835
                        $first_name : '<MISSING>') . "\n" .
836
                    'Last Name     = ' . ((strlen($last_name) > 0) ?
837
                        $last_name : '<MISSING>') . "\n" .
838
                    'Display Name  = ' . ((strlen($display_name) > 0) ?
839
                        $display_name : '<MISSING>') . "\n" .
840
                    'Email Address = ' . ((strlen($email) > 0) ?
841
                        $email : '<MISSING>') . "\n" .
842
                    'LOA           = ' . ((strlen($loa) > 0) ?
843
                        $loa : '<MISSING>') . "\n" .
844
                    'ePPN          = ' . ((strlen($eppn) > 0) ?
845
                        $eppn : '<MISSING>') . "\n" .
846
                    'ePTID         = ' . ((strlen($eptid) > 0) ?
847
                        $eptid : '<MISSING>') . "\n" .
848
                    'OpenID ID     = ' . ((strlen($open_id) > 0) ?
849
                        $open_id : '<MISSING>') . "\n" .
850
                    'OIDC ID       = ' . ((strlen($oidc) > 0) ?
851
                        $oidc : '<MISSING>') . "\n" .
852
                    'Subject ID    = ' . ((strlen($subject_id) > 0) ?
853
                        $subject_id : '<MISSING>') . "\n" .
854
                    'Pairwise ID   = ' . ((strlen($pairwise_id) > 0) ?
855
                        $pairwise_id : '<MISSING>') . "\n" .
856
                    'Affiliation   = ' . ((strlen($affiliation) > 0) ?
857
                        $affiliation : '<MISSING>') . "\n" .
858
                    'OU            = ' . ((strlen($ou) > 0) ?
859
                        $ou : '<MISSING>') . "\n" .
860
                    'MemberOf      = ' . ((strlen($member_of) > 0) ?
861
                        $member_of : '<MISSING>') . "\n" .
862
                    'ACR           = ' . ((strlen($acr) > 0) ?
863
                        $acr : '<MISSING>') . "\n" .
864
                    'AMR           = ' . ((strlen($amr) > 0) ?
865
                        $amr : '<MISSING>') . "\n" .
866
                    'Entitlement   = ' . ((strlen($entitlement) > 0) ?
867
                        $entitlement : '<MISSING>') . "\n" .
868
                    'iTrustUIN     = ' . ((strlen($itrustuin) > 0) ?
869
                        $itrustuin : '<MISSING>') . "\n" .
870
                    'User UID      = ' . ((strlen(
871
                        $i = static::getSessionVar('user_uid')
872
                    ) > 0) ?  $i : '<MISSING>') . "\n" .
873
                    'Status Code   = ' . ((strlen(
874
                        $i = array_search(
875
                            $status,
876
                            DBService::$STATUS
877
                        )
878
                    ) > 0) ?  $i : '<MISSING>'),
879
                    $mailto
880
                );
881
            }
882
            static::unsetSessionVar('authntime');
883
        } else {
884
            // Success! We need to overwrite current session vars with values
885
            // returned by the DBService, e.g., in case attributes were set
886
            // previously but not this time. Skip 'idp' since the PHP code
887
            // transforms 'https://' to 'http://' for database consistency.
888
            // Also skip 'loa' since that is not saved in the database.
889
            foreach (DBService::$user_attrs as $value) {
890
                if (($value != 'idp') && ($value != 'loa')) {
891
                    static::setSessionVar($value, $dbs->$value);
892
                }
893
            }
894
        }
895
    }
896
897
    /**
898
     * setUserAttributeSessionVars
899
     *
900
     * This method is called by saveUserToDatastore to put the passsed-in
901
     * variables into the PHP session for later use.
902
     *
903
     * @param mixed $args Variable number of user attribute paramters
904
     *        ordered as shown in the DBService::$user_attrs array.
905
     */
906
    public static function setUserAttributeSessionVars(...$args)
907
    {
908
        // Loop through the list of user_attrs. First, unset any previous
909
        // value for the attribute, then set the passed-in attribute value.
910
        $numattrs = count(DBService::$user_attrs);
911
        $numargs = count($args);
912
        for ($i = 0; $i < $numattrs; $i++) {
913
            static::unsetSessionVar(DBService::$user_attrs[$i]);
914
            if ($i < $numargs) {
915
                static::setSessionVar(DBService::$user_attrs[$i], $args[$i]);
916
            }
917
        }
918
919
        static::setSessionVar('status', '0');
920
        static::setSessionVar('submit', static::getSessionVar('responsesubmit'));
921
        static::setSessionVar('authntime', time());
922
        static::unsetSessionVar('responsesubmit');
923
        static::getCsrf()->setCookieAndSession();
924
    }
925
926
    /**
927
     * unsetClientSessionVars
928
     *
929
     * This function removes all of the PHP session variables related to
930
     * the client session.
931
     */
932
    public static function unsetClientSessionVars()
933
    {
934
        static::unsetSessionVar('submit');
935
936
        // Specific to 'Download Certificate' page
937
        static::unsetSessionVar('p12');
938
        static::unsetSessionVar('p12lifetime');
939
        static::unsetSessionVar('p12multiplier');
940
941
        // Specific to OAuth 1.0a flow
942
        static::unsetSessionVar('portalstatus');
943
        static::unsetSessionVar('callbackuri');
944
        static::unsetSessionVar('successuri');
945
        static::unsetSessionVar('failureuri');
946
        static::unsetSessionVar('portalname');
947
        static::unsetSessionVar('tempcred');
948
949
        // Specific to OIDC flow
950
        static::unsetSessionVar('clientparams');
951
    }
952
953
    /**
954
     * unsetUserSessionVars
955
     *
956
     * This function removes all of the PHP session variables related to
957
     * the user's session.  This will force the user to log on (again)
958
     * with their IdP and call the 'getuser' script to repopulate the PHP
959
     * session.
960
     */
961
    public static function unsetUserSessionVars()
962
    {
963
        foreach (DBService::$user_attrs as $value) {
964
            static::unsetSessionVar($value);
965
        }
966
        static::unsetSessionVar('status');
967
        static::unsetSessionVar('user_uid');
968
        static::unsetSessionVar('distinguished_name');
969
        static::unsetSessionVar('authntime');
970
        static::unsetSessionVar('cilogon_skin');
971
    }
972
973
    /**
974
     * unsetAllUserSessionVars
975
     *
976
     * This is a convenience method to clear all session variables related
977
     * to the client and the user.
978
     */
979
    public static function unsetAllUserSessionVars()
980
    {
981
        static::unsetClientSessionVars();
982
        static::unsetUserSessionVars();
983
    }
984
985
    /**
986
     * verifySessionAndCall
987
     *
988
     * This function is a convenience method called by several cases in the
989
     * main 'switch' call at the top of the index.php file. I noticed
990
     * a pattern where verifyCurrentUserSession() was called to verify the
991
     * current user session. Upon success, one or two functions were called
992
     * to continue program, flow. Upon failure, cookies and session
993
     * variables were cleared, and the main Logon page was printed. This
994
     * function encapsulates that pattern. If the user's session is valid,
995
     * the passed-in $func is called, possibly with parameters passed in as
996
     * an array. The function returns true if the session is verified, so
997
     * that other functions may be called upon return.
998
     *
999
     * @param callable $func The function to call if the current session is
1000
     *        successfully verified.
1001
     * @param array $params (Optional) An array of parameters to pass to the
1002
     *        function. Defaults to empty array, meaning zero parameters.
1003
     */
1004
    public static function verifySessionAndCall($func, $params = array())
1005
    {
1006
        $retval = false;
1007
        if (Content::verifyCurrentUserSession()) { // Verify PHP session is valid
1008
            $retval = true;
1009
            call_user_func_array($func, $params);
1010
        } else {
1011
            printLogonPage(true); // Clear cookies and session vars too
0 ignored issues
show
The function printLogonPage was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1011
            /** @scrutinizer ignore-call */ 
1012
            printLogonPage(true); // Clear cookies and session vars too
Loading history...
1012
        }
1013
        return $retval;
1014
    }
1015
1016
    /**
1017
     * isEduGAINAndGetCert
1018
     *
1019
     * This function checks to see if the current session IdP is an
1020
     * eduGAIN IdP (i.e., not Registered By InCommon) and the IdP does not
1021
     * have both the REFEDS R&S and SIRTFI extensions in metadata. If so,
1022
     * check to see if the transaction could be used to fetch a
1023
     * certificate. (The only time the transaction is not used to fetch
1024
     * a cert is during OIDC without the 'getcert' scope.) If all that is
1025
     * true, then return true. Otherwise return false.
1026
     *
1027
     * @param string $idp (optional) The IdP entityID. If empty, read value
1028
     *        from PHP session.
1029
     * @param string $idp_display_name (optional) The IdP display name. If empty,
1030
     *        read value from PHP session.
1031
     * @return bool True if the current IdP is an eduGAIN IdP without
1032
     *         both REFEDS R&S and SIRTFI, AND the session could be
1033
     *         used to get a certificate.
1034
     */
1035
    public static function isEduGAINAndGetCert($idp = '', $idp_display_name = '')
1036
    {
1037
        $retval = false; // Assume not eduGAIN IdP and getcert
1038
1039
        // If $idp or $idp_display_name not passed in, get from current session.
1040
        if (strlen($idp) == 0) {
1041
            $idp = static::getSessionVar('idp');
1042
        }
1043
        if (strlen($idp_display_name) == 0) {
1044
            $idp_display_name = static::getSessionVar('idp_display_name');
1045
        }
1046
1047
        // Check if this was an OIDC transaction, and if the
1048
        // 'getcert' scope was requested.
1049
        $oidcscopegetcert = false;
1050
        $oidctrans = false;
1051
        $clientparams = json_decode(static::getSessionVar('clientparams'), true);
1052
        if (isset($clientparams['scope'])) {
1053
            $oidctrans = true;
1054
            if (
1055
                preg_match(
1056
                    '/edu.uiuc.ncsa.myproxy.getcert/',
1057
                    $clientparams['scope']
1058
                )
1059
            ) {
1060
                $oidcscopegetcert = true;
1061
            }
1062
        }
1063
1064
        // First, make sure $idp was set and is not an OAuth2 IdP.
1065
        $idplist = static::getIdpList();
1066
        if (
1067
            ((strlen($idp) > 0) &&
1068
            (strlen($idp_display_name) > 0) &&
1069
            (!in_array($idp_display_name, static::$oauth2idps))) &&
1070
                (
1071
                // Next, check for eduGAIN without REFEDS R&S and SIRTFI
1072
                ((!$idplist->isRegisteredByInCommon($idp)) &&
1073
                       ((!$idplist->isREFEDSRandS($idp)) ||
1074
                        (!$idplist->isSIRTFI($idp))
1075
                       )
1076
                ) &&
1077
                // Next, check if user could get X509 cert,
1078
                // i.e., OIDC getcert scope, or a non-OIDC
1079
                // transaction such as PKCS12, JWS, or OAuth 1.0a
1080
                ($oidcscopegetcert || !$oidctrans)
1081
                )
1082
        ) {
1083
            $retval = true;
1084
        }
1085
        return $retval;
1086
    }
1087
1088
    /**
1089
     * setPortalOrCookieVar
1090
     *
1091
     * This is a convenience function for a set of operations that is done
1092
     * a few times in Content.php. It first checks if the name of the portal
1093
     * in the PortalCookie is empty. If not, then it sets the PortalCookie
1094
     * key/value pair. Otherwise, it sets the 'normal' cookie key/value
1095
     * pair.
1096
     *
1097
     * @param PortalCookie $pc The PortalCookie to read/write. If the portal
1098
     *        name is empty, then use the 'normal' cookie instead.
1099
     * @param string $key The key of the PortalCookie or 'normal' cookie to
1100
     *        set.
1101
     * @param string $value The value to set for the $key.
1102
     * @param bool $save (optional) If set to true, attempt to write the
1103
     *        PortalCookie. Defaults to false.
1104
     */
1105
    public static function setPortalOrCookieVar($pc, $key, $value, $save = false)
1106
    {
1107
        $pn = $pc->getPortalName();
1108
        // If the portal name is valid, then set the PortalCookie key/value
1109
        if (strlen($pn) > 0) {
1110
            $pc->set($key, $value);
1111
            if ($save) {
1112
                $pc->write();
1113
            }
1114
        } else { // If portal name is not valid, then use the 'normal' cookie
1115
            if (strlen($value) > 0) {
1116
                Util::setCookieVar($key, $value);
1117
            } else { // If $value is empty, then UNset the 'normal' cookie
1118
                Util::unsetCookieVar($key);
1119
            }
1120
        }
1121
    }
1122
1123
    /**
1124
     * getOIDCClientParams
1125
     *
1126
     * This function addresses CIL-618 and reads OIDC client information
1127
     * directly from the database. It is a replacement for
1128
     * $dbs->getClient($clientparams['client_id']) which calls
1129
     * '/dbService?action=getClient&client_id=...'. This gives the PHP
1130
     * '/authorize' endpoint access to additional OIDC client parameters
1131
     * without having to rewrite the '/dbService?action=getClient' endpoint.
1132
     *
1133
     * @param array $clientparams An array of client parameters which gets
1134
     *              stored in the PHP session. The keys of the array are
1135
     *              the column names of the 'client' table in the 'ciloa2'
1136
     *              database, prefixed by 'client_'.
1137
     * @return bool True if database query was successful. False otherwise.
1138
     */
1139
    public static function getOIDCClientParams(&$clientparams)
1140
    {
1141
        $retval = false;
1142
        if (strlen(@$clientparams['client_id']) > 0) {
1143
            $dsn = array(
1144
                'phptype'  => 'mysqli',
1145
                'username' => MYSQLI_USERNAME,
0 ignored issues
show
The constant CILogon\Service\MYSQLI_USERNAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1146
                'password' => MYSQLI_PASSWORD,
0 ignored issues
show
The constant CILogon\Service\MYSQLI_PASSWORD was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1147
                'database' => MYSQLI_DATABASE,
0 ignored issues
show
The constant CILogon\Service\MYSQLI_DATABASE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1148
                'hostspec' => MYSQLI_HOSTSPEC
0 ignored issues
show
The constant CILogon\Service\MYSQLI_HOSTSPEC was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1149
            );
1150
1151
            $opts = array(
1152
                'persistent'  => true,
1153
                'portability' => DB_PORTABILITY_ALL
1154
            );
1155
1156
            $db = DB::connect($dsn, $opts);
1157
            if (!PEAR::isError($db)) {
1158
                $data = $db->getRow(
1159
                    'SELECT name,home_url,callback_uri,scopes from clients WHERE client_id = ?',
1160
                    array($clientparams['client_id']),
1161
                    DB_FETCHMODE_ASSOC
1162
                );
1163
                if (!DB::isError($data)) {
1164
                    if (!empty($data)) {
1165
                        foreach ($data as $key => $value) {
1166
                            $clientparams['client_' . $key] = $value;
1167
                        }
1168
                        $clientparams['clientstatus'] = DBService::$STATUS['STATUS_OK'];
1169
                        $retval = true;
1170
                    }
1171
                }
1172
                $db->disconnect();
1173
            }
1174
        }
1175
        return $retval;
1176
    }
1177
1178
    /**
1179
     * getMinMaxLifetimes
1180
     *
1181
     * This function checks the skin's configuration to see if either or
1182
     * both of minlifetime and maxlifetime in the specified config.xml
1183
     * block have been set. If not, default to minlifetime of 1 (hour) and
1184
     * the specified defaultmaxlifetime.
1185
     *
1186
     * @param string $section The XML section block from which to read the
1187
     *        minlifetime and maxlifetime values. Can be one of the
1188
     *        following: 'pkcs12' or 'delegate'.
1189
     * @param int $defaultmaxlifetime Default maxlifetime (in hours) for the
1190
     *        credential.
1191
     * @return array An array consisting of two entries: the minimum and
1192
     *         maximum lifetimes (in hours) for a credential.
1193
     */
1194
    public static function getMinMaxLifetimes($section, $defaultmaxlifetime)
1195
    {
1196
        $minlifetime = 1;    // Default minimum lifetime is 1 hour
1197
        $maxlifetime = $defaultmaxlifetime;
1198
        $skin = Util::getSkin();
1199
        $skinminlifetime = $skin->getConfigOption($section, 'minlifetime');
1200
        // Read the skin's minlifetime value from the specified section
1201
        if ((!is_null($skinminlifetime)) && ((int)$skinminlifetime > 0)) {
1202
            $minlifetime = max($minlifetime, (int)$skinminlifetime);
1203
            // Make sure $minlifetime is less than $maxlifetime;
1204
            $minlifetime = min($minlifetime, $maxlifetime);
1205
        }
1206
        // Read the skin's maxlifetime value from the specified section
1207
        $skinmaxlifetime = $skin->getConfigOption($section, 'maxlifetime');
1208
        if ((!is_null($skinmaxlifetime)) && ((int)$skinmaxlifetime) > 0) {
1209
            $maxlifetime = min($maxlifetime, (int)$skinmaxlifetime);
1210
            // Make sure $maxlifetime is greater than $minlifetime
1211
            $maxlifetime = max($minlifetime, $maxlifetime);
1212
        }
1213
1214
        return array($minlifetime, $maxlifetime);
1215
    }
1216
1217
    /**
1218
     * isLOASilver
1219
     *
1220
     * This function returns true if the 'loa' (level of assurance)
1221
     * should be http://incommonfederation.org/assurance/silver .
1222
     * As specified in CACC-238, this is when both of the following are true:
1223
     * (1) loa contains  https://refeds.org/assurance/profile/cappuccino
1224
     * (2) acr is either https://refeds.org/profile/sfa or
1225
     *                   https://refeds.org/profile/mfa
1226
     *
1227
     * @return bool True if level of assurance is 'silver'.
1228
     */
1229
    public static function isLOASilver()
1230
    {
1231
        $retval = false;
1232
        if (
1233
            (preg_match('%https://refeds.org/assurance/profile/cappuccino%', static::getSessionVar('loa'))) &&
1234
            (preg_match('%https://refeds.org/profile/[ms]fa%', static::getSessionVar('acr')))
1235
        ) {
1236
            $retval = true;
1237
        }
1238
        return $retval;
1239
    }
1240
1241
    /**
1242
     * getLOA
1243
     *
1244
     * This function is a bit of a hack. Once upon a time, the level of
1245
     * assurance (loa) was one of empty string (which implied 'basic
1246
     * CA'), 'openid' (which implied 'openid CA'), or
1247
     * 'http://incommonfederation.org/assurance/silver' (which implied
1248
     * 'silver CA'). Then things got more complex when the silver
1249
     * assurance was replaced by cappuccino (see CACC-238). But parts of the
1250
     * PHP code still depeneded on the InCommon silver string.
1251
     *
1252
     * This function transforms the assurance attribute asserted by an IdP
1253
     * (which is stored in the 'loa' session variable) into one of
1254
     * empty string (for 'basic CA'), 'openid', or
1255
     * 'http://incommonfederation.org/assurance/silver' for use by those
1256
     * PHP functions which expect the 'loa' in this format.
1257
     *
1258
     * @return string One of empty string, 'openid', or
1259
     *         'http://incommonfederation.org/assurance/silver'
1260
     */
1261
    public static function getLOA()
1262
    {
1263
        $retval = '';
1264
        if (static::isLOASilver()) {
1265
            $retval = 'http://incommonfederation.org/assurance/silver';
1266
        } else {
1267
            $retval = static::getSessionVar('loa');
1268
        }
1269
        return $retval;
1270
    }
1271
1272
    /**
1273
     * getLOAPort
1274
     *
1275
     * This function returns the port to be used for MyProxy based on the
1276
     * level of assurance.
1277
     *     Basic  CA = 7512
1278
     *     Silver CA = 7514
1279
     *     OpenID CA = 7516
1280
     *
1281
     * @return int The MyProxy port number to be used based on the 'level
1282
     *         of assurance' (basic, silver, openid).
1283
     */
1284
    public static function getLOAPort()
1285
    {
1286
        $port = 7512; // Basic
1287
        if (Util::isLOASilver()) {
1288
            $port = 7514;
1289
        } elseif (Util::getSessionVar('loa') == 'openid') {
1290
            $port = 7516;
1291
        }
1292
        return $port;
1293
    }
1294
1295
    /**
1296
     * getFirstAndLastName
1297
     *
1298
     * This function attempts to get the first and last name of a user
1299
     * extracted from the 'full name' (displayName) of the user.
1300
     * Simply pass in all name info (full, first, and last) and the
1301
     * function first tries to break up the full name into first/last.
1302
     * If this is not sufficient, the function checks first and last
1303
     * name. Finally, if either first or last is blank, the function
1304
     * duplicates first <=> last so both names have the same value.
1305
     * Note that even with all this, you still need to check if the
1306
     * returned (first,last) names are blank.
1307
     *
1308
     * @param string $full The 'full name' of the user
1309
     * @param string $first (Optional) The 'first name' of the user
1310
     * @param string $last (Optional) The 'last name' of the user
1311
     * @return array An array 'list(firstname,lastname)'
1312
     */
1313
    public static function getFirstAndLastName($full, $first = '', $last = '')
1314
    {
1315
        $firstname = '';
1316
        $lastname = '';
1317
1318
        # Try to split the incoming $full name into first and last names
1319
        if (strlen($full) > 0) {
1320
            if (preg_match('/,/', $full)) { // Split on comma if present
1321
                $names = preg_split('/,/', $full, 2);
1322
                $lastname =  trim(@$names[0]);
1323
                $firstname = trim(@$names[1]);
1324
            } else {
1325
                $names = preg_split('/\s+/', $full, 2);
1326
                $firstname = trim(@$names[0]);
1327
                $lastname =  trim(@$names[1]);
1328
            }
1329
        }
1330
1331
        # If either first or last name blank, then use incoming $first and $last
1332
        if (strlen($firstname) == 0) {
1333
            $firstname = $first;
1334
        }
1335
        if (strlen($lastname) == 0) {
1336
            $lastname = $last;
1337
        }
1338
1339
        # Finally, if only a single name, copy first name <=> last name
1340
        if (strlen($lastname) == 0) {
1341
            $lastname = $firstname;
1342
        }
1343
        if (strlen($firstname) == 0) {
1344
            $firstname = $lastname;
1345
        }
1346
1347
        # Return both names as an array (i.e., use list($first,last)=...)
1348
        return array($firstname,$lastname);
1349
    }
1350
1351
    /**
1352
     * cleanupPKCS12
1353
     *
1354
     * This function scans the DEFAULT_PKCS12_DIR and removes any
1355
     * directories (and contained files) that are older than 10 minutes.
1356
     * This function is used by the /cleancerts/ endpoint which can
1357
     * be called by a cronjob.
1358
     *
1359
     * @return int The number of PKCS12 dirs/files removed.
1360
     */
1361
    public static function cleanupPKCS12()
1362
    {
1363
        $numdel = 0;
1364
1365
        $pkcs12dir = DEFAULT_PKCS12_DIR;
0 ignored issues
show
The constant CILogon\Service\DEFAULT_PKCS12_DIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1366
        if (is_dir($pkcs12dir)) {
1367
            $files = scandir($pkcs12dir);
1368
            foreach ($files as $f) {
1369
                if (($f != '.') && ($f != '..')) {
1370
                    $tempdir = $pkcs12dir . $f;
1371
                    if ((filetype($tempdir) == 'dir') && ($f != '.git')) {
1372
                        if (time() > (600 + filemtime($tempdir))) {
1373
                            static::deleteDir($tempdir, true);
1374
                            $numdel++;
1375
                        }
1376
                    }
1377
                }
1378
            }
1379
        }
1380
        return $numdel;
1381
    }
1382
1383
    /**
1384
     * logXSEDEUsage
1385
     *
1386
     * This function writes the XSEDE USAGE message to a CSV file. See
1387
     * CIL-938 and CIL-507 for background. This function first checks if the
1388
     * XSEDE_USAGE_DIR config value is not empty and that the referenced
1389
     * directory exists on the filesystem. If so, a CSV file is created/
1390
     * appended using today's date. If the CSV file is new, a header
1391
     * line is written. Then the actual USAGE line is output in the
1392
     * following format:
1393
     *
1394
     *     cilogon,GMT_date,client_name,email_address
1395
     *
1396
     * @param string $client The name of the client. One of 'ECP', 'PKCS12',
1397
     *        or the name of the OAuth1/OAuth2/OIDC client/portal.
1398
     * @param string $email The email address of the user.
1399
     */
1400
    public static function logXSEDEUsage($client, $email)
1401
    {
1402
        if (
1403
            (defined('XSEDE_USAGE_DIR')) &&
1404
            (!empty(XSEDE_USAGE_DIR)) &&
0 ignored issues
show
The constant CILogon\Service\XSEDE_USAGE_DIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1405
            (is_writable(XSEDE_USAGE_DIR))
1406
        ) {
1407
            $error = ''; // Was there an error to be reported?
1408
1409
            // Get the date strings for filename and CSV line output.
1410
            // Filename uses local time zone; log lines use GMT.
1411
            // Save the current default timezone and restore it later.
1412
            $deftz = date_default_timezone_get();
1413
            $now = time();
1414
            $datestr = gmdate('Y-m-d\TH:i:s\Z', $now);
1415
            date_default_timezone_set(LOCAL_TIMEZONE);
0 ignored issues
show
The constant CILogon\Service\LOCAL_TIMEZONE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1416
            $filename = date('Ymd', $now) . '.upload.csv';
1417
1418
            // Open and lock the file
1419
            $fp = fopen(XSEDE_USAGE_DIR . DIRECTORY_SEPARATOR . $filename, 'c');
1420
            if ($fp !== false) {
1421
                if (flock($fp, LOCK_EX)) {
1422
                    // Move file pointer to the end of the file.
1423
                    if (fseek($fp, 0, SEEK_END) == 0) { // Note 0 = success
1424
                        $endpos = ftell($fp);
1425
                        // If the position is at the beginning of the file (0),
1426
                        // then the file is new, so output the HEADER line.
1427
                        if (($endpos !== false) && ($endpos == 0)) {
1428
                            fwrite($fp, "USED_COMPONENT,USE_TIMESTAMP,USE_CLIENT,USE_USER\n");
1429
                        }
1430
                        // Write the actual USAGE data line
1431
                        fwrite($fp, "cilogon,$datestr,$client,$email\n");
1432
                        fflush($fp);
1433
                    } else {
1434
                        $error = 'Unable to seek to end of file.';
1435
                    }
1436
                    flock($fp, LOCK_UN);
1437
                } else { // Problem writing file
1438
                    $error = 'Unable to lock file.';
1439
                }
1440
                fclose($fp);
1441
            } else {
1442
                $error = 'Unable to open file.';
1443
            }
1444
1445
            // Restore previous default timezone
1446
            date_default_timezone_set($deftz);
1447
1448
            // If got an error while opening/writing file, log it.
1449
            if (strlen($error) > 0) {
1450
                $log = new Loggit();
1451
                $log->error("Error writing XSEDE USAGE file $filename: $error");
1452
            }
1453
        }
1454
    }
1455
}
1456