Passed
Push — master ( 70d62c...8bd0f3 )
by Terrence
15:21 queued 11s
created

Content::handleGotUser()   F

Complexity

Conditions 19
Paths 49

Size

Total Lines 137
Code Lines 98

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 380

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 19
eloc 98
c 6
b 1
f 0
nc 49
nop 0
dl 0
loc 137
ccs 0
cts 70
cp 0
crap 380
rs 3.6569

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\Util;
6
use CILogon\Service\MyProxy;
7
use CILogon\Service\PortalCookie;
8
use CILogon\Service\DBService;
9
use CILogon\Service\OAuth2Provider;
10
use CILogon\Service\Loggit;
11
use Net_LDAP2_Util;
12
13
/**
14
 * Content
15
 */
16
class Content
17
{
18
    /**
19
     * printHeader
20
     *
21
     * This function should be called to print out the main HTML header
22
     * block for each web page.  This gives a consistent look to the site.
23
     * Any style changes should go in the cilogon.css file.
24
     *
25
     * @param string $title The text in the window's titlebar
26
     * @param bool $csrfcookie Set the CSRF cookie. Defaults to true.
27
     */
28
    public static function printHeader($title = '', $csrfcookie = true)
29
    {
30
        if ($csrfcookie) {
31
            $csrf = Util::getCsrf();
32
            $csrf->setTheCookie();
33
        }
34
35
        // Find the 'Powered By CILogon' image if specified by the skin
36
        $poweredbyimg = "/images/poweredbycilogon.png";
37
        $skin = Util::getSkin();
38
        $skinpoweredbyimg = (string)$skin->getConfigOption('poweredbyimg');
39
        if (
40
            (strlen($skinpoweredbyimg) > 0) &&
41
            (is_readable('/var/www/html' . $skinpoweredbyimg))
42
        ) {
43
            $poweredbyimg = $skinpoweredbyimg;
44
        }
45
46
        echo '<!doctype html>
47
<html lang="en">
48
  <head>
49
    <!-- Required meta tags -->
50
    <meta charset="utf-8" />
51
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
52
53
    <title>', $title, '</title>
54
55
    <!-- Font Awesome CSS -->
56
    <link rel="stylesheet"
57
          href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"
58
          integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN"
59
          crossorigin="anonymous">
60
    <!-- Bootstrap CSS -->
61
    <link rel="stylesheet"
62
          href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
63
          integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
64
          crossorigin="anonymous" />
65
    <!-- Bootstrap-Select CSS -->
66
    <link rel="stylesheet"
67
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-select.min.css" />
68
    <!-- CILogon-specific CSS -->
69
    <link rel="stylesheet" href="/include/cilogon.css" />
70
        ';
71
72
        $skin->printSkinLink();
73
74
        echo '
75
  </head>
76
77
  <body>
78
    <div class="skincilogonlogo">
79
      <a target="_blank" href="http://www.cilogon.org/faq/"><img
80
      src="', $poweredbyimg , '" alt="CILogon" title="CILogon Service" /></a>
81
    </div>
82
83
    <div class="logoheader">
84
       <h1><span>[CILogon Service]</span></h1>
85
    </div>
86
87
    <div class="mt-4 container-fluid"> <!-- Main Bootstrap Container -->
88
    ';
89
90
        static::printNoScript();
91
92
        if ((defined('BANNER_TEXT')) && (!empty(BANNER_TEXT))) {
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\BANNER_TEXT was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
93
            echo '
94
      <div class="alert alert-warning alert-dismissible fade show" role="alert">
95
      ', BANNER_TEXT , '
96
        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
97
          <span aria-hidden="true">&times;</span>
98
        </button>
99
      </div>
100
101
';
102
        }
103
    }
104
105
    /**
106
     * printFooter
107
     *
108
     * This function should be called to print out the closing HTML block
109
     * for each web page.
110
     *
111
     * @param string $footer Optional extra text to be output before the
112
     * closing footer div.
113
     */
114
    public static function printFooter($footer = '')
115
    {
116
        if (strlen($footer) > 0) {
117
            echo $footer;
118
        }
119
120
        echo '
121
    </div> <!-- Close Main Bootstrap Container -->
122
    <footer class="footer">
123
      <p>For questions about this site, please see the <a target="_blank"
124
        href="http://www.cilogon.org/faq">FAQs</a> or send email to <a
125
        href="mailto:[email protected]">[email protected]</a>.</p>
126
      <p>Know <a target="_blank"
127
        href="http://ca.cilogon.org/responsibilities">your responsibilities</a>
128
        for using the CILogon Service.</p>
129
      <p>See <a target="_blank"
130
        href="http://ca.cilogon.org/acknowledgements">acknowledgements</a> of
131
        support for this site.</p>
132
    </footer>
133
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
134
            integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
135
            crossorigin="anonymous"></script>
136
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.bundle.min.js"
137
            integrity="sha384-xrRywqdh3PHs8keKZN+8zzc5TX0GRTLCcmivcbNJWm2rs5C8PRhcEn3czEjhAO9o"
138
            crossorigin="anonymous"></script>
139
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap-select.min.js"></script>
140
    <script>$(document).ready(function(){ $(\'[data-toggle="popover"]\').popover(); });</script>
141
    <script>$("#collapse-gencert").on(\'shown.bs.collapse\', function(){ $("#password1").focus() });</script>
142
    <script src="/include/cilogon.js"></script>
143
  </body>
144
</html>';
145
146
        session_write_close();
147
    }
148
149
    /**
150
     * printFormHead
151
     *
152
     * This function prints out the opening <form> tag for displaying
153
     * submit buttons.  The first parameter is used for the 'action' value
154
     * of the <form>.  If omitted, getScriptDir() is called to get the
155
     * location of the current script.  This function outputs a hidden csrf
156
     * field in the form block.
157
     *
158
     * @param string $action (Optional) The value of the form's 'action'
159
     *        parameter. Defaults to getScriptDir().
160
     * @param string $method (Optional) The <form> 'method', one of 'get' or
161
     *        'post'. Defaults to 'post'.
162
     */
163
    public static function printFormHead($action = '', $method = 'post')
164
    {
165
        static $formnum = 0;
166
167
        if (strlen($action) == 0) {
168
            $action = Util::getScriptDir();
169
        }
170
171
        echo '
172
          <form action="', $action , '" method="', $method , '"
173
          autocomplete="off" id="form', sprintf("%02d", ++$formnum), '"
174
          class="needs-validation" novalidate="novalidate">
175
        ';
176
        $csrf = Util::getCsrf();
177
        echo $csrf->hiddenFormElement();
178
    }
179
180
    /**
181
     * printWAYF
182
     *
183
     * This function prints the list of IdPs in a <select> form element
184
     * which can be printed on the main login page to allow the user to
185
     * select 'Where Are You From?'.  This function checks to see if a
186
     * cookie for the 'providerId' had been set previously, so that the
187
     * last used IdP is selected in the list.
188
     *
189
     * @param bool $showremember (Optional) Show the 'Remember this
190
     *        selection' checkbox? Defaults to true.
191
     */
192
    public static function printWAYF($showremember = true)
193
    {
194
        $idps = static::getCompositeIdPList();
195
        $skin = Util::getSkin();
196
197
        $rememberhelp = 'Check this box to bypass the welcome page on ' .
198
            'subsequent visits and proceed directly to the selected ' .
199
            'identity provider. You will need to clear your browser\'s ' .
200
            'cookies to return here.';
201
        $selecthelp = '<p>
202
            CILogon facilitates secure access to CyberInfrastructure (CI).
203
            In order to use the CILogon Service, you must first select
204
            an identity provider. An identity provider (IdP) is an
205
            organization where you have an account and can log on
206
            to gain access to online services.
207
        </p>
208
        <p>
209
            If you are a faculty, staff, or student member of a university
210
            or college, please select it for your identity provider.
211
            If your school is not listed, please contact <a
212
            href=\'mailto:[email protected]\'>[email protected]</a>, and we will
213
            try to add your school in the future.
214
        </p>
215
        ';
216
217
        $googleauthz = Util::getAuthzUrl('Google');
218
        if (isset($idps[$googleauthz])) {
219
            $selecthelp .= '<p>If you have a <a target=\'_blank\'
220
            href=\'https://myaccount.google.com\'>Google</a> account,
221
            you can select it for authenticating to the CILogon Service.</p>
222
            ';
223
        }
224
        $githubauthz = Util::getAuthzUrl('GitHub');
225
        if (isset($idps[$githubauthz])) {
226
            $selecthelp .= '<p> If you have a <a target=\'_blank\'
227
            href=\'https://github.com/settings/profile\'>GitHub</a> account,
228
            you can select it for authenticating to the CILogon Service.</p>
229
            ';
230
        }
231
        $orcidauthz = Util::getAuthzUrl('ORCID');
232
        if (isset($idps[$orcidauthz])) {
233
            $selecthelp .= '<p> If you have a <a target=\'_blank\'
234
            href=\'https://orcid.org/my-orcid\'>ORCID</a> account,
235
            you can select it for authenticating to the CILogon Service.</p>
236
            ';
237
        }
238
239
        // Check if the user had previously selected an IdP from the list.
240
        // First, check the portalcookie, then the 'normal' cookie.
241
        $keepidp = '';
242
        $providerId = '';
243
        $pc = new PortalCookie();
244
        $pn = $pc->getPortalName();
245
        if (strlen($pn) > 0) {
246
            $keepidp    = $pc->get('keepidp');
247
            $providerId = $pc->get('providerId');
248
        } else {
249
            $keepidp    = Util::getCookieVar('keepidp');
250
            $providerId = Util::getCookieVar('providerId');
251
        }
252
253
        // Make sure previously selected IdP is in list of available IdPs.
254
        if ((strlen($providerId) > 0) && (!isset($idps[$providerId]))) {
255
            $providerId = '';
256
        }
257
258
        // If no previous providerId, get from skin, or default to Google.
259
        if (strlen($providerId) == 0) {
260
            $initialidp = (string)$skin->getConfigOption('initialidp');
261
            if ((strlen($initialidp) > 0) && (isset($idps[$initialidp]))) {
262
                $providerId = $initialidp;
263
            } else {
264
                $providerId = Util::getAuthzUrl('Google');
265
            }
266
        }
267
268
        // Check if an OIDC client selected an IdP for the transaction.
269
        // If so, verify that the IdP is in the list of available IdPs.
270
        $useselectedidp = false;
271
        $idphintlist = static::getIdphintList($idps);
272
        if (!empty($idphintlist)) {
273
            $useselectedidp = true;
274
            $providerId = $idphintlist[0];
275
            $newidps = array();
276
            // Update the IdP selection list to show just the idphintlist.
277
            foreach ($idphintlist as $value) {
278
                $newidps[$value] = $idps[$value];
279
            }
280
            $idps = $newidps;
281
            // Re-sort the $idps by Display_Name for correct alphabetization.
282
            uasort($idps, function ($a, $b) {
283
                return strcasecmp(
284
                    $a['Display_Name'],
285
                    $b['Display_Name']
286
                );
287
            });
288
        }
289
290
        echo '
291
      <div class="card text-center col-lg-6 offset-lg-3 col-md-8 offset-md-2 col-sm-10 offset-sm-1 mt-3">
292
        <h4 class="card-header">',
293
        ($useselectedidp ? 'Selected' : 'Select an'),
294
        ' Identity Provider</h4>
295
        <div class="card-body">
296
          <form action="', Util::getScriptDir(), '" method="post">
297
            <div class="form-group">
298
            <select name="providerId" id="providerId"
299
                autofocus="autofocus"
300
                class="selectpicker mw-100"
301
                data-size="20" data-width="fit"
302
                data-live-search="true"
303
                data-live-search-placeholder="Type to search"
304
                data-live-search-normalize="true"
305
                >';
306
307
308
        foreach ($idps as $entityId => $names) {
309
            echo '
310
                <option data-tokens="', $entityId , '" value="',
311
                $entityId , '"',
312
                (($entityId == $providerId) ? ' selected="selected"' : ''),
313
            '>', Util::htmlent($names['Display_Name']), '</option>';
314
        }
315
316
        echo '
317
            </select>
318
            <a href="#" tabindex="0" data-trigger="hover click"
319
            class="helpcursor"
320
            data-toggle="popover" data-html="true"
321
            title="Selecting an Identity Provider"
322
            data-content="', $selecthelp, '"><i class="fa
323
            fa-question-circle"></i></a>
324
            </div> <!-- end div form-group -->
325
            ';
326
327
        if ($showremember) {
328
            echo '
329
            <div class="form-group">
330
              <div class="form-check">
331
                <input class="form-check-input" type="checkbox"
332
                id="keepidp" name="keepidp" ',
333
                ((strlen($keepidp) > 0) ? 'checked="checked" ' : ''), ' />
334
                <label class="form-check-label"
335
                for="keepidp">Remember this selection</label>
336
                <a href="#" tabindex="0" data-trigger="hover click"
337
                class="helpcursor"
338
                data-toggle="popover" data-html="true"
339
                data-content="', $rememberhelp, '"><i class="fa
340
                fa-question-circle"></i></a>
341
              </div> <!-- end div form-check -->
342
            </div> <!-- end div form-group -->
343
            ';
344
        }
345
346
        echo Util::getCsrf()->hiddenFormElement();
347
        echo '<input type="hidden" name="previouspage" value="WAYF" />';
348
        $lobtext = static::getLogOnButtonText();
349
350
        echo '
351
            <div class="form-group">
352
              <div class="form-row align-items-center justify-content-center">
353
                <div class="col-auto">
354
                  <input type="submit" name="submit"
355
                  class="btn btn-primary submit"
356
                  value="', $lobtext , '" id="wayflogonbutton" />
357
                </div> <!-- end col-auto -->
358
        ';
359
360
        $wayfcancelbutton = Util::getSkin()->getConfigOption('wayfcancelbutton');
361
        if ((!is_null($wayfcancelbutton)) && ((int)$wayfcancelbutton == 1)) {
362
            echo '
363
                <div class="col-auto">
364
                  <input type="submit" name="submit"
365
                  class="btn btn-primary submit"
366
                  title="Cancel authentication and navigate away from this site."
367
                  value="Cancel" id="wayfcancelbutton" />
368
                </div>
369
            ';
370
        }
371
372
        echo '
373
              </div> <!-- end div form-row align-items-center -->
374
            </div> <!-- end div form-group -->
375
        ';
376
377
        $logonerror = Util::getSessionVar('logonerror');
378
        Util::unsetSessionVar('logonerror');
379
        if (strlen($logonerror) > 0) {
380
            echo '<div class="alert alert-danger" role="alert">', $logonerror, '</div>';
381
        }
382
383
        echo '
384
        <p class="privacypolicy">
385
        By selecting "', $lobtext , '", you agree to <a target="_blank"
386
        href="http://ca.cilogon.org/policy/privacy">CILogon\'s privacy
387
        policy</a>.
388
        </p>
389
390
          </form>
391
392
        </div> <!-- End card-body -->
393
      </div> <!-- End card -->
394
        ';
395
    }
396
397
    /**
398
     * printGetCertificate
399
     *
400
     * This function prints the 'Get New Certificate' box on the main page.
401
     * If the 'p12' PHP session variable is valid, it is read and a link for
402
     * the usercred.p12 file is presented to the user.
403
     */
404
    public static function printGetCertificate()
405
    {
406
        // Check if PKCS12 downloading is disabled. If so, print out message.
407
        $skin = Util::getSkin();
408
        $pkcs12disabled = $skin->getConfigOption('pkcs12', 'disabled');
409
        $disabledbyskin = ((!is_null($pkcs12disabled)) && ((int)$pkcs12disabled == 1));
410
        $disabledbyconf = ((!defined('MYPROXY_LOGON')) || (empty(MYPROXY_LOGON)));
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\MYPROXY_LOGON was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
411
412
        if ($disabledbyskin || $disabledbyconf) {
413
            $disabledmsg = 'Downloading PKCS12 certificates is disabled.';
414
            if ($disabledbyskin) {
415
                $disabledmsg = $skin->getConfigOption('pkcs12', 'disabledmessage');
416
                if (!is_null($disabledmsg)) {
417
                    $disabledmsg = trim(html_entity_decode($disabledmsg));
418
                }
419
                if (strlen($disabledmsg) == 0) {
420
                    $disabledmsg = 'Downloading PKCS12 certificates is ' .
421
                        'restricted. Please try another method or log on ' .
422
                        'with a different Identity Provider.';
423
                }
424
            }
425
426
            echo '<div class="alert alert-danger role="alert">';
427
            echo $disabledmsg;
428
            echo '</div>';
429
        } else { // PKCS12 downloading is okay
430
            $p12linktext = "Left-click this link to import the certificate " .
431
                "into your broswer / operating system. (Firefox users see " .
432
                "the FAQ.) Right-click this link and select 'Save As...' to " .
433
                "save the certificate to your desktop.";
434
            $passwordtext1 = 'Enter a password of at least 12 characters to protect your certificate.';
435
            $passwordtext2 = 'Re-enter your password for verification.';
436
437
            // Get the 'p12' session variable, which contains the time until
438
            // the "Download Certificate" link expires concatenated with the
439
            // download link (separated by a space). If either the time or
440
            // the link is blank, or the time is 0:00, then do not show the
441
            // link/time and unset the 'p12' session variable.
442
            $p12expire = '';
443
            $p12link = '';
444
            $p12linkisactive = false;
445
            $p12 = Util::getSessionVar('p12');
446
            if (preg_match('/([^\s]*)\s(.*)/', $p12, $match)) {
447
                $p12expire = $match[1];
448
                $p12link = $match[2];
449
            }
450
451
            if (
452
                (strlen($p12expire) > 0) &&
453
                (strlen($p12link) > 0) &&
454
                ($p12expire > 0) &&
455
                ($p12expire >= time())
456
            ) {
457
                $p12linkisactive = true;
458
                $expire = $p12expire - time();
459
                $minutes = floor($expire % 3600 / 60);
460
                $seconds = $expire % 60;
461
                $p12expire = 'Link Expires: ' .
462
                    sprintf("%02dm:%02ds", $minutes, $seconds);
463
                $p12link = '<a class="btn btn-primary" title="' .
464
                    $p12linktext . '" href="' . $p12link .
465
                    '">Download Your Certificate</a>';
466
            } else {
467
                $p12expire = '';
468
                $p12link = '';
469
                Util::unsetSessionVar('p12');
470
            }
471
472
            $p12lifetime = Util::getSessionVar('p12lifetime');
473
            if ((strlen($p12lifetime) == 0) || ($p12lifetime == 0)) {
474
                $p12lifetime = Util::getCookieVar('p12lifetime');
475
            }
476
            $p12multiplier = Util::getSessionVar('p12multiplier');
477
            if ((strlen($p12multiplier) == 0) || ($p12multiplier == 0)) {
478
                $p12multiplier = Util::getCookieVar('p12multiplier');
479
            }
480
481
            // Try to read the skin's intiallifetime if not yet set
482
            if ((strlen($p12lifetime) == 0) || ($p12lifetime <= 0)) {
483
                // See if the skin specified an initial value
484
                $skinlife = $skin->getConfigOption('pkcs12', 'initiallifetime', 'number');
485
                $skinmult = $skin->getConfigOption('pkcs12', 'initiallifetime', 'multiplier');
486
                if (
487
                    (!is_null($skinlife)) && (!is_null($skinmult)) &&
488
                    ((int)$skinlife > 0) && ((int)$skinmult > 0)
489
                ) {
490
                    $p12lifetime = (int)$skinlife;
491
                    $p12multiplier = (int)$skinmult;
492
                } else {
493
                    $p12lifetime = 13;      // Default to 13 months
494
                    $p12multiplier = 732;
495
                }
496
            }
497
            if ((strlen($p12multiplier) == 0) || ($p12multiplier <= 0)) {
498
                $p12multiplier = 732;   // Default to months
499
                if ($p12lifetime > 13) {
500
                    $p12lifetime = 13;
501
                }
502
            }
503
504
            // Make sure lifetime is within [minlifetime,maxlifetime]
505
            list($minlifetime, $maxlifetime) =
506
                Util::getMinMaxLifetimes('pkcs12', 9516);
507
            if (($p12lifetime * $p12multiplier) < $minlifetime) {
508
                $p12lifetime = $minlifetime;
509
                $p12multiplier = 1; // In hours
510
            } elseif (($p12lifetime * $p12multiplier) > $maxlifetime) {
511
                $p12lifetime = $maxlifetime;
512
                $p12multiplier = 1; // In hours
513
            }
514
515
            $lifetimetext = "Certificate lifetime is between $minlifetime " .
516
                "and $maxlifetime hours" .
517
                (($maxlifetime > 732) ?
518
                " ( = " . round(($maxlifetime / 732), 2) . " months)." : "."
519
                );
520
521
            $p12error = Util::getSessionVar('p12error');
522
            $expandcreatecert = (int)$skin->getConfigOption('expandcreatecert');
523
524
            static::printCollapseBegin(
525
                'gencert',
526
                'Create Password-Protected Certificate',
527
                !($p12linkisactive || (strlen($p12error) > 0) || $expandcreatecert)
528
            );
529
530
            echo '
531
          <div class="card-body col-lg-6 offset-lg-3 col-md-8 offset-md-2 col-sm-10 offset-sm-1">';
532
533
            static::printFormHead();
534
535
            if (strlen($p12error) > 0) {
536
                echo '<div class="alert alert-danger alert-dismissable fade show" role="alert">';
537
                echo $p12error;
538
                echo '
539
                      <button type="button" class="close" data-dismiss="alert"
540
                      aria-label="Close"><span aria-hidden="true">&times;</span>
541
                      </button>
542
                  </div>';
543
                Util::unsetSessionVar('p12error');
544
            }
545
546
            echo '
547
            <div class="form-group">
548
              <label for="password1">Enter Password</label>
549
              <div class="form-row">
550
                <div class="col-11">
551
                  <input type="password" name="password1" id="password1"
552
                  minlength="12" required="required"
553
                  autocomplete="new-password"
554
                  class="form-control" aria-describedby="password1help"
555
                  onkeyup="checkPassword()"/>
556
                  <div class="invalid-tooltip">
557
                    Please enter a password of at least 12 characters.
558
                  </div>
559
                </div>
560
                <div class="col">
561
                  <i id="pw1icon" class="fa fa-fw"></i>
562
                </div>
563
              </div>
564
              <div class="form-row">
565
                <div class="col-11">
566
                  <small id="password1help" class="form-text text-muted">',
567
                  $passwordtext1, '
568
                  </small>
569
                </div>
570
              </div>
571
            </div> <!-- end form-group -->
572
573
            <div class="form-group">
574
              <label for="password2">Confirm Password</label>
575
              <div class="form-row">
576
                <div class="col-11">
577
                  <input type="password" name="password2" id="password2"
578
                  minlength="12" required="required"
579
                  autocomplete="new-password"
580
                  class="form-control" aria-describedby="password2help"
581
                  onkeyup="checkPassword()"/>
582
                  <div class="invalid-tooltip">
583
                    Please ensure entered passwords match.
584
                  </div>
585
                </div>
586
                <div class="col">
587
                  <i id="pw2icon" class="fa fa-fw"></i>
588
                </div>
589
              </div>
590
              <div class="form-row">
591
                <div class="col-11">
592
                  <small id="password2help" class="form-text text-muted">',
593
                  $passwordtext2, '
594
                  </small>
595
                </div>
596
              </div>
597
            </div> <!-- end form-group -->
598
599
            <div class="form-row p12certificatelifetime">
600
              <div class="form-group col-8">
601
              <label for="p12lifetime">Lifetime</label>
602
                <input type="number" name="p12lifetime" id="p12lifetime" ',
603
                'value="', $p12lifetime, '" min="', $minlifetime, '" max="',
604
                $maxlifetime, '" class="form-control" required="required"
605
                aria-describedby="lifetime1help" />
606
                <div class="invalid-tooltip">
607
                  Please enter a valid lifetime for the certificate.
608
                </div>
609
                <small id="lifetime1help" class="form-text text-muted">',
610
                $lifetimetext, '
611
                </small>
612
              </div>
613
              <div class="form-group col-4">
614
                <label for="p12multiplier">&nbsp;</label>
615
                <select id="p12multiplier" name="p12multiplier"
616
                class="form-control">
617
                  <option value="1"',
618
                    (($p12multiplier == 1) ? ' selected="selected"' : ''),
619
                    '>hours</option>
620
                  <option value="24"',
621
                    (($p12multiplier == 24) ? ' selected="selected"' : ''),
622
                    '>days</option>
623
                  <option value="732"',
624
                    (($p12multiplier == 732) ? ' selected="selected"' : ''),
625
                    '>months</option>
626
                </select>
627
              </div>
628
            </div> <!-- end form-row -->
629
630
            <div class="form-group">
631
              <div class="form-row align-items-center">
632
                <div class="col text-center">
633
                  <input type="submit" name="submit"
634
                  class="btn btn-primary submit"
635
                  value="Get New Certificate" onclick="showHourglass(\'p12\')"/>
636
                  <div class="spinner-border"
637
                  style="width: 32px; height: 32px;"
638
                  role="status" id="p12hourglass">
639
                    <span class="sr-only">Generating...</span>
640
                  </div> <!-- spinner-border -->
641
                </div>
642
              </div>
643
            </div>
644
645
            <div class="form-group">
646
              <div class="form-row align-items-center">
647
                <div class="col text-center" id="p12value">
648
                ', $p12link, '
649
                </div>
650
              </div>
651
              <div class="form-row align-items-center">
652
                <div class="col text-center" id="p12expire">
653
                ', $p12expire, '
654
                </div>
655
              </div>
656
            </div>
657
            </form>
658
          </div> <!-- end card-body -->';
659
            static::printCollapseEnd();
660
        }
661
    }
662
663
    /**
664
     * printCertInfo
665
     *
666
     * This function prints information related to the X.509 certificate
667
     * such as DN (distinguished name) and LOA (level of assurance).
668
     */
669
    public static function printCertInfo()
670
    {
671
        $dn = Util::getSessionVar('dn');
672
        // Strip off the email address from the pseudo-DN.
673
        $dn = static::reformatDN(preg_replace('/\s+email=.+$/', '', $dn));
674
675
        static::printCollapseBegin('certinfo', 'Certificate Information');
676
        echo '
677
          <div class="card-body">
678
            <table class="table table-striped table-sm">
679
            <tbody>
680
              <tr>
681
                <th>Certificate Subject:</th>
682
                <td>', Util::htmlent($dn), '</td>
683
              </tr>
684
              <tr>
685
                <th>Identity Provider:</th>
686
                <td>', Util::getSessionVar('idpname'), '</td>
687
              </tr>
688
              <tr>
689
                <th><a target="_blank"
690
                  href="http://ca.cilogon.org/loa">Level of Assurance:</a></th>
691
                  <td>';
692
693
        $loa = Util::getSessionVar('loa');
694
        if ($loa == 'openid') {
695
            echo '<a href="http://ca.cilogon.org/policy/openid"
696
                  target="_blank">OpenID</a>';
697
        } elseif ($loa == 'http://incommonfederation.org/assurance/silver') {
698
            echo '<a href="http://ca.cilogon.org/policy/silver"
699
                  target="_blank">Silver</a>';
700
        } else {
701
            echo '<a href="http://ca.cilogon.org/policy/basic"
702
                  target="_blank">Basic</a>';
703
        }
704
705
        echo '</td>
706
              </tr>
707
              </tbody>
708
            </table>
709
          </div> <!-- end card-body -->';
710
        static::printCollapseEnd();
711
    }
712
713
    /**
714
     * printUserAttributes
715
     *
716
     * This function shows the user the attributes released by their
717
     * selected IdP and saved in the PHP session.
718
     */
719
    public static function printUserAttributes()
720
    {
721
        $idplist = Util::getIdpList();
722
        $idp = Util::getSessionVar('idp');
723
        $samlidp = ((!empty($idp)) && (!$idplist->isOAuth2($idp)));
724
725
        // Set various booleans for warning/error messages early so that we
726
        // can display a "general" warning/error icon on the card title.
727
        $warnings = array();
728
        $errors = array();
729
730
        // CIL-416 Show warning for missing ePPN
731
        if (($samlidp) && (empty(Util::getSessionVar('ePPN')))) {
732
            if (empty(Util::getSessionVar('ePTID'))) {
733
                $errors['no_eppn_or_eptid'] = true;
734
            } else {
735
                $warnings['no_eppn'] = true;
736
            }
737
        }
738
739
        if (empty($idp)) {
740
            $errors['no_entityID'] = true;
741
        } else {
742
            if ((!$samlidp) && (empty(Util::getSessionVar('oidcID')))) {
743
                $errors['no_oidcID'] = true;
744
            }
745
        }
746
747
        if ((empty(Util::getSessionVar('firstname'))) && (empty(Util::getSessionVar('displayname')))) {
748
            $errors['no_firstname'] = true;
749
        }
750
751
        if ((empty(Util::getSessionVar('lastname'))) && (empty(Util::getSessionVar('displayname')))) {
752
            $errors['no_lastname'] = true;
753
        }
754
755
        if (
756
            (empty(Util::getSessionVar('displayname'))) &&
757
            ((empty(Util::getSessionVar('firstname'))) ||
758
            (empty(Util::getSessionVar('lastname'))))
759
        ) {
760
            $errors['no_displayname'] = true;
761
        }
762
763
        $emailvalid = filter_var(Util::getSessionVar('emailaddr'), FILTER_VALIDATE_EMAIL);
764
        if ((empty(Util::getSessionVar('emailaddr'))) || (!$emailvalid)) {
765
            $errors['no_valid_email'] = true;
766
        }
767
768
        static::printCollapseBegin(
769
            'userattrs',
770
            'User Attributes ' .
771
            (
772
                (!empty($errors)) ? static::getIcon(
773
                    'fa-exclamation-circle',
774
                    'red',
775
                    'One or more missing attributes.'
776
                ) : ((@$warnings['no_eppn']) ?  static::getIcon(
777
                    'fa-exclamation-triangle',
778
                    'gold',
779
                    'Some CILogon clients (e.g., Globus) require ePPN.'
780
                ) : '')
781
            )
782
        );
783
784
        echo '
785
          <div class="card-body">
786
            <table class="table table-striped table-sm">
787
            <tbody>
788
              <tr>
789
                <th>Identity Provider (entityID):</th>
790
                <td>', $idp , '</td>
791
                <td>';
792
793
        if (@$errors['no_entityID']) {
794
            echo static::getIcon(
795
                'fa-exclamation-circle',
796
                'red',
797
                'Missing the entityID of the IdP.'
798
            );
799
        }
800
801
        echo '
802
                </td>
803
              </tr>
804
805
              <tr>
806
                <th>ePTID:</th>
807
                <td>', Util::getSessionVar('ePTID'), '</td>
808
                <td>';
809
810
        if (@$errors['no_eppn_or_eptid']) {
811
            echo static::getIcon(
812
                'fa-exclamation-circle',
813
                'red',
814
                'Must have either ePPN -OR- ePTID.'
815
            );
816
        }
817
818
        echo '
819
                </td>
820
              </tr>
821
822
              <tr>
823
                <th>ePPN:</th>
824
                <td>', Util::getSessionVar('ePPN'), '</td>
825
                <td>';
826
827
        if (@$errors['no_eppn_or_eptid']) {
828
            echo static::getIcon(
829
                'fa-exclamation-circle',
830
                'red',
831
                'Must have either ePPN -OR- ePTID.'
832
            );
833
        } elseif (@$warnings['no_eppn']) {
834
            echo static::getIcon(
835
                'fa-exclamation-triangle',
836
                'gold',
837
                'Some CILogon clients (e.g., Globus) require ePPN.'
838
            );
839
        }
840
841
        echo '
842
                </td>
843
              </tr>
844
845
              <tr>
846
                <th>OpenID:</th>
847
                <td>', Util::getSessionVar('oidcID'), '</td>
848
                <td>';
849
850
        if (@$errors['no_oidcID']) {
851
            echo static::getIcon(
852
                'fa-exclamation-circle',
853
                'red',
854
                'Missing the OpenID identifier.'
855
            );
856
        }
857
858
        echo '
859
                </td>
860
              </tr>
861
862
              <tr>
863
                <th>First Name (givenName):</th>
864
                <td>', Util::getSessionVar('firstname'), '</td>
865
                <td>';
866
867
        if (@$errors['no_firstname']) {
868
            echo static::getIcon(
869
                'fa-exclamation-circle',
870
                'red',
871
                'Must have either givenName + sn -OR- displayName.'
872
            );
873
        }
874
875
        echo '
876
                </td>
877
              </tr>
878
879
              <tr>
880
                <th>Last Name (sn):</th>
881
                <td>', Util::getSessionVar('lastname'), '</td>
882
                <td>';
883
884
        if (@$errors['no_lastname']) {
885
            echo static::getIcon(
886
                'fa-exclamation-circle',
887
                'red',
888
                'Must have either givenName + sn -OR- displayName.'
889
            );
890
        }
891
892
        echo '
893
                </td>
894
              </tr>
895
896
              <tr>
897
                <th>Display Name (displayName):</th>
898
                <td>', Util::getSessionVar('displayname'), '</td>
899
                <td>';
900
901
        if (@$errors['no_displayname']) {
902
            echo static::getIcon(
903
                'fa-exclamation-circle',
904
                'red',
905
                'Must have either displayName -OR- givenName + sn.'
906
            );
907
        }
908
909
        echo '
910
                </td>
911
              </tr>
912
913
              <tr>
914
                <th>Email Address (email):</th>
915
                <td>', Util::getSessionVar('emailaddr'), '</td>
916
                <td>';
917
918
        if (@$errors['no_valid_email']) {
919
            echo static::getIcon(
920
                'fa-exclamation-circle',
921
                'red',
922
                'Missing valid email address.'
923
            );
924
        }
925
926
        echo '
927
                </td>
928
              </tr>
929
930
              <tr>
931
                <th>Level of Assurance (assurance):</th>
932
                <td>', Util::getSessionVar('loa'), '</td>
933
                <td> </td>
934
              </tr>
935
936
              <tr>
937
                <th>AuthnContextClassRef:</th>
938
                <td>', Util::getSessionVar('acr'), '</td>
939
                <td> </td>
940
              </tr>
941
942
              <tr>
943
                <th>Affiliation (affiliation):</th>
944
                <td>', Util::getSessionVar('affiliation'), '</td>
945
                <td> </td>
946
              </tr>
947
948
              <tr>
949
                <th>Entitlement (entitlement):</th>
950
                <td>', Util::getSessionVar('entitlement'), '</td>
951
                <td> </td>
952
              </tr>
953
954
              <tr>
955
                <th>Organizational Unit (ou):</th>
956
                <td>', Util::getSessionVar('ou'), '</td>
957
                <td> </td>
958
              </tr>
959
960
              <tr>
961
                <th>Member (member):</th>
962
                <td>', Util::getSessionVar('memberof'), '</td>
963
                <td> </td>
964
              </tr>
965
966
              <tr>
967
                <th>iTrustUIN (itrustuin):</th>
968
                <td>', Util::getSessionVar('itrustuin'), '</td>
969
                <td> </td>
970
              </tr>
971
972
              <tr>
973
                <th>Subject ID (subject-id):</th>
974
                <td>', Util::getSessionVar('subjectID'), '</td>
975
                <td> </td>
976
              </tr>
977
978
              <tr>
979
                <th>Pairwise ID (pairwise-id):</th>
980
                <td>', Util::getSessionVar('pairwiseID'), '</td>
981
                <td> </td>
982
              </tr>
983
              </tbody>
984
            </table>
985
          </div> <!-- end card-body -->';
986
        static::printCollapseEnd();
987
    }
988
989
    /**
990
     * printIdPMetadata
991
     *
992
     * This function shows the metadata associated with the IdP saved to
993
     * the PHP session.
994
     */
995
    public static function printIdPMetadata()
996
    {
997
        $idplist = Util::getIdpList();
998
        $idp = Util::getSessionVar('idp');
999
        $samlidp = ((!empty($idp)) && (!$idplist->isOAuth2($idp)));
1000
        $shibarray = $idplist->getShibInfo($idp);
1001
1002
        // CIL-416 Check for eduGAIN IdPs without both REFEDS R&S and SIRTFI
1003
        // since these IdPs are not allowed to get certificates.
1004
        $eduGainWithoutRandSandSIRTFI = 0;
1005
        if (
1006
            ($samlidp) &&
1007
            (!$idplist->isRegisteredByInCommon($idp)) &&
1008
            ((!$idplist->isREFEDSRandS($idp)) ||
1009
             (!$idplist->isSIRTFI($idp)))
1010
        ) {
1011
            $eduGainWithoutRandSandSIRTFI = 1;
1012
        }
1013
1014
        static::printCollapseBegin(
1015
            'idpmeta',
1016
            'Identity Provider Attributes ' .
1017
            (
1018
                // CIL-416 Show warning for missing ePPN
1019
                ($eduGainWithoutRandSandSIRTFI) ?
1020
                static::getIcon(
1021
                    'fa-exclamation-triangle',
1022
                    'gold',
1023
                    'This IdP does not support both ' .
1024
                    'REFEDS R&amp;S and SIRTFI. CILogon ' .
1025
                    'functionality may be limited.'
1026
                ) : ''
1027
            )
1028
        );
1029
1030
        echo'
1031
          <div class="card-body">
1032
            <table class="table table-striped table-sm">
1033
            <tbody>
1034
              <tr>
1035
                <th>Organization Name:</th>
1036
                <td>', @$shibarray['Organization Name'] , '</td>
1037
                <td>';
1038
1039
        if (empty(@$shibarray['Organization Name'])) {
1040
            echo static::getIcon(
1041
                'fa-exclamation-circle',
1042
                'red',
1043
                'Could not find ' .
1044
                '&lt;OrganizationDisplayName&gt; in metadata.'
1045
            );
1046
        }
1047
1048
        echo '
1049
                </td>
1050
              </tr>
1051
              <tr>
1052
                <th>Home Page:</th>
1053
                <td><a target="_blank" href="', @$shibarray['Home Page'] , '">',
1054
                @$shibarray['Home Page'] , '</a></td>
1055
                <td> </td>
1056
              </tr>
1057
1058
              <tr>
1059
                <th>Support Contact:</th>';
1060
        if (
1061
            (!empty(@$shibarray['Support Name'])) ||
1062
            (!empty(@$shibarray['Support Address']))
1063
        ) {
1064
            echo '
1065
                <td>', @$shibarray['Support Name'] , ' &lt;',
1066
                        preg_replace('/^mailto:/', '', @$shibarray['Support Address']), '&gt;</td>
1067
                <td> </td>';
1068
        }
1069
        echo '
1070
              </tr>
1071
1072
        ';
1073
1074
        if ($samlidp) {
1075
            echo '
1076
              <tr>
1077
                <th>Technical Contact:</th>';
1078
            if (
1079
                (!empty(@$shibarray['Technical Name'])) ||
1080
                (!empty(@$shibarray['Technical Address']))
1081
            ) {
1082
                echo '
1083
                <td>', @$shibarray['Technical Name'] , ' &lt;',
1084
                        preg_replace('/^mailto:/', '', @$shibarray['Technical Address']), '&gt;</td>
1085
                <td> </td>';
1086
            }
1087
            echo '
1088
              </tr>
1089
1090
              <tr>
1091
                <th>Administrative Contact:</th>';
1092
            if (
1093
                (!empty(@$shibarray['Administrative Name'])) ||
1094
                (!empty(@$shibarray['Administrative Address']))
1095
            ) {
1096
                echo '
1097
                <td>', @$shibarray['Administrative Name'] , ' &lt;',
1098
                        preg_replace('/^mailto:/', '', @$shibarray['Administrative Address']), '&gt;</td>
1099
                <td> </td>';
1100
            }
1101
            echo '
1102
              </tr>
1103
1104
              <tr>
1105
                <th>Registered by InCommon:</th>
1106
                <td>', ($idplist->isRegisteredByInCommon($idp) ? 'Yes' : 'No'), '</td>
1107
                <td> </td>
1108
              </tr>
1109
1110
              <tr>
1111
                <th><a style="text-decoration:underline" target="_blank"
1112
                href="http://id.incommon.org/category/research-and-scholarship">InCommon R
1113
                &amp; S</a>:</th>
1114
                <td>', ($idplist->isInCommonRandS($idp) ? 'Yes' : 'No'), '</td>
1115
                <td> </td>
1116
              </tr>
1117
1118
              <tr>
1119
                <th><a style="text-decoration:underline" target="_blank"
1120
                href="http://refeds.org/category/research-and-scholarship">REFEDS
1121
                R &amp; S</a>:</th>
1122
                <td>', ($idplist->isREFEDSRandS($idp) ? 'Yes' : 'No'), '</td>
1123
                <td>';
1124
1125
            if (
1126
                ($eduGainWithoutRandSandSIRTFI &&
1127
                !$idplist->isREFEDSRandS($idp))
1128
            ) {
1129
                echo static::getIcon(
1130
                    'fa-exclamation-triangle',
1131
                    'gold',
1132
                    'This IdP does not support both ' .
1133
                    'REFEDS R&amp;S and SIRTFI. ' .
1134
                    'CILogon functionality may be limited.'
1135
                );
1136
            }
1137
1138
            echo '
1139
                </td>
1140
              </tr>
1141
1142
              <tr>
1143
                <th><a style="text-decoration:underline" target="_blank"
1144
                       href="https://refeds.org/sirtfi">SIRTFI</a>:</th>
1145
                <td>', ($idplist->isSIRTFI($idp) ? 'Yes' : 'No'), '</td>
1146
                <td>';
1147
1148
            if (
1149
                ($eduGainWithoutRandSandSIRTFI &&
1150
                !$idplist->isSIRTFI($idp))
1151
            ) {
1152
                echo static::getIcon(
1153
                    'fa-exclamation-triangle',
1154
                    'gold',
1155
                    'This IdP does not support both ' .
1156
                    'REFEDS R&amp;S and SIRTFI. ' .
1157
                    'CILogon functionality may be limited.'
1158
                );
1159
            }
1160
1161
            echo '
1162
                </td>
1163
              </tr>
1164
1165
              <tr>
1166
                <th><a style="text-decoration:underline" target="_blank"
1167
                href="http://id.incommon.org/assurance/bronze">InCommon Bronze</a>:</th>
1168
                <td>', ($idplist->isBronze($idp) ? 'Yes' : 'No'), '</td>
1169
                <td> </td>
1170
              </tr>
1171
1172
              <tr>
1173
                <th><a style="text-decoration:underline" target="_blank"
1174
                href="http://id.incommon.org/assurance/silver">InCommon Silver</a>:</th>
1175
                <td>', ($idplist->isSilver($idp) ? 'Yes' : 'No'), '</td>
1176
                <td> </td>
1177
              </tr>
1178
1179
              <tr>
1180
                <th>Entity ID</th>
1181
                <td><a style="text-decoration:underline" target="_blank"
1182
                href="https://met.refeds.org/met/entity/',
1183
                rawurlencode($idp),
1184
                '">', $idp, '</td>
1185
                <td> </td>
1186
              </tr>
1187
            ';
1188
        } // end if ($samlidp)
1189
1190
            echo '
1191
              </tbody>
1192
            </table>
1193
          </div> <!-- end card-body -->';
1194
        static::printCollapseEnd();
1195
    }
1196
1197
    /**
1198
     * getIcon
1199
     *
1200
     * This function returns the HTML for the Font Awesome icons which can
1201
     * appear inline with other information.  This is accomplished via the
1202
     * use of wrapping the image in a <span> tag.
1203
     *
1204
     * @param string $icon The Font Awesome icon to be shown.
1205
     * @param string $color The HMTL color for the icon.
1206
     * @param string $help (Optionals) The popup 'title' help text to be
1207
     *        displayed when the mouse cursor hovers over the icon.
1208
     *        Defaults to empty string.
1209
     * @return string HTML for the icon block to output.
1210
     */
1211
    public static function getIcon($icon, $color, $help = '')
1212
    {
1213
        return '<span style="color: ' . $color . ';
1214
            -webkit-text-stroke-width: 1px;
1215
            -webkit-text-stroke-color: gray;">' .
1216
            ((strlen($help) > 0) ? '<span data-trigger="hover" ' .
1217
            'data-toggle="popover" data-html="true" ' .
1218
            'data-content="' . $help . '">' : '') .
1219
            '<i class="fa nocollapse ' . $icon . '"></i>' .
1220
            ((strlen($help) > 0) ? '</span>' : '') .
1221
            '</span>';
1222
    }
1223
1224
    /**
1225
     * printCollapseBegin
1226
     *
1227
     * This function prints the preamble for a collapsible Bootstrap Card.
1228
     *
1229
     * @param string $name The name to give to the collapse elements which
1230
     *        should be unique among all collapse elements on the page.
1231
     * @param string $title The text for the card-header.
1232
     * @param bool $collapsed (optional) If true, then start with the card
1233
     *        collapsed. If false, start with the card opened.
1234
     */
1235
    public static function printCollapseBegin($name, $title, $collapsed = true)
1236
    {
1237
        echo '
1238
      <div class="card col-sm-10 offset-sm-1">
1239
        <h5 class="card-header text-center">
1240
          <a class="d-block',
1241
            ($collapsed ? ' collapsed' : ''),
1242
            '" data-toggle="collapse"
1243
            href="#collapse-', $name, '" aria-expanded="',
1244
            ($collapsed ? 'false' : "true"),
1245
            '" aria-controls="collapse-', $name, '"
1246
            id="heading-', $name, '">
1247
            <i class="fa fa-chevron-down pull-right"></i>
1248
            ', $title, '
1249
          </a>
1250
        </h5>
1251
        <div id="collapse-',$name, '" class="collapse',
1252
        ($collapsed ? '' : ' show'),
1253
        '" aria-labelledby="heading-', $name , '">';
1254
    }
1255
1256
    /**
1257
     * printCollapseEnd
1258
     *
1259
     * This function prints the closing block corresponding to the
1260
     * printCollapseBegin.
1261
     */
1262
    public static function printCollapseEnd()
1263
    {
1264
        echo '
1265
        </div> <!-- end collapse-... -->
1266
      </div> <!-- end card -->
1267
        ';
1268
    }
1269
1270
    /**
1271
     * printErrorBox
1272
     *
1273
     * This function prints out a bordered box with an error icon and any
1274
     * passed-in error HTML text.  The error icon and text are output to
1275
     * a <table> so as to keep the icon to the left of the error text.
1276
     *
1277
     * @param string $errortext HTML error text to be output
1278
     */
1279
    public static function printErrorBox($errortext)
1280
    {
1281
        echo '
1282
        <div class="alert alert-danger" role="alert">
1283
          <div class="row">
1284
            <div class="col-1 align-self-center text-center">
1285
            ', static::getIcon('fa-exclamation-circle fa-2x', 'red'),'
1286
            </div>
1287
            <div class="col">
1288
            ', $errortext , '
1289
            </div>
1290
          </div>
1291
        </div>
1292
        ';
1293
    }
1294
1295
    /**
1296
     * printNoScript
1297
     *
1298
     * This function prints the <NoScript> block which is displayed if the
1299
     * user's browser does not have JavaScript enabled.
1300
     */
1301
    public static function printNoScript()
1302
    {
1303
        echo'
1304
      <noscript>
1305
        <div class="alert alert-danger alert-dismissible" role="alert">
1306
          <span><strong>Notice: </strong> JavaScript is not enabled.
1307
          The CILogon Service requires JavaScript for functionality.
1308
          <a target="_blank" href="https://enable-javascript.com/"
1309
          class="alert-link">Please Enable JavaScript</a>.</span>
1310
        </div>
1311
      </noscript>
1312
        ';
1313
    }
1314
1315
    /**
1316
     * printLogOff
1317
     *
1318
     * This function prints the Log Of boxes at the bottom of the main page.
1319
     */
1320
    public static function printLogOff()
1321
    {
1322
        $logofftext = 'End your CILogon session and return to the ' .
1323
           'front page. Note that this will not log you out at ' .
1324
            Util::getSessionVar('idpname') . '.';
1325
1326
        static::printFormHead();
1327
        echo '
1328
          <div class="form-group mt-3">
1329
            <div class="form-row align-items-center">
1330
              <div class="col text-center">
1331
              ';
1332
1333
        $logofftextbox = Util::getSkin()->getConfigOption('logofftextbox');
1334
        if ((!is_null($logofftextbox)) && ((int)$logofftextbox == 1)) {
1335
            echo '  <div class="btn btn-primary">To log off,
1336
                please quit your browser.</div>';
1337
        } else {
1338
            echo '  <input type="submit" name="submit"
1339
                class="btn btn-primary submit"
1340
                title="', $logofftext , '" value="Log Off" />';
1341
        }
1342
1343
        echo '
1344
              </div> <!-- end col-auto -->
1345
            </div> <!-- end form-row align-items-center -->
1346
          </div> <!-- end form-group -->
1347
        </form>
1348
        ';
1349
    }
1350
1351
    /**
1352
     * printGeneralErrorPage
1353
     *
1354
     * This is a convenience method called by handleGotUser to print out
1355
     * a general error page to the user.
1356
     *
1357
     * @param string $redirect The url for the <form> element
1358
     * @param string $redirectform Additional hidden input fields for the
1359
     *        <form>.
1360
     */
1361
    public static function printGeneralErrorPage($redirect, $redirectform)
1362
    {
1363
        Util::unsetAllUserSessionVars();
1364
1365
        static::printHeader('Error Logging On');
1366
        static::printCollapseBegin(
1367
            'attributeerror',
1368
            'General Error',
1369
            false
1370
        );
1371
1372
        echo '
1373
              <div class="card-body px-5">';
1374
1375
        static::printErrorBox('An error has occurred. System
1376
            administrators have been notified. This may be a temporary
1377
            error. Please try again later, or contact us at the the email
1378
            address at the bottom of the page.');
1379
1380
        static::printFormHead($redirect, 'get');
1381
1382
        echo '
1383
              <div class="card-text my-2">
1384
                <div class="form-group">
1385
                  <div class="form-row align-items-center
1386
                  justify-content-center">
1387
                    <div class="col-auto">
1388
                      ', $redirectform, '
1389
                      <input type="submit" name="submit"
1390
                      class="btn btn-primary submit form-control"
1391
                      value="Proceed" />
1392
                    </div>
1393
                  </div> <!-- end form-row align-items-center -->
1394
                </div> <!-- end form-group -->
1395
              </div> <!-- end card-text -->
1396
            </form>
1397
            </div> <!-- end card-body -->';
1398
1399
        Content::printCollapseEnd();
1400
        Content::printFooter();
1401
    }
1402
1403
    /**
1404
     * printSAMLAttributeReleaseErrorPage
1405
     *
1406
     * This is a convenience method called by handleGotUser to print out
1407
     * the attribute release error page for SAML IdPs. This can occur when
1408
     * not all attributes were released by the IdP, or when the IdP is an
1409
     * eduGAIN IdP without both R&S and SIRTFI, and the user was trying to
1410
     * get a certificate.
1411
     *
1412
     * @param string $ePPN
1413
     * @param string $ePTID
1414
     * @param string $firstname
1415
     * @param string $lastname
1416
     * @param string $displayname
1417
     * @param string $emailaddr
1418
     * @param string $idp
1419
     * @param string $idpname
1420
     * @param string $affiliation
1421
     * @param string $ou
1422
     * @param string $memberof
1423
     * @param string $acr
1424
     * @param string $entitlement
1425
     * @param string $itrustuin
1426
     * @param string $subjectID
1427
     * @param string $pairwiseID
1428
     * @param string $clientparams
1429
     * @param string $redirect The url for the <form> element
1430
     * @param string $redirectform Additional hidden input fields for the
1431
     *        <form>.
1432
     * @param bool   $edugainandgetcert Is the IdP in eduGAIN without both
1433
     *        R&S and SIRTIF, and the user could get a certificate?
1434
     */
1435
    public static function printSAMLAttributeReleaseErrorPage(
1436
        $ePPN,
1437
        $ePTID,
1438
        $firstname,
1439
        $lastname,
1440
        $displayname,
1441
        $emailaddr,
1442
        $idp,
1443
        $idpname,
1444
        $affiliation,
1445
        $ou,
0 ignored issues
show
Unused Code introduced by
The parameter $ou is not used and could be removed. ( Ignorable by Annotation )

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

1445
        /** @scrutinizer ignore-unused */ $ou,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1446
        $memberof,
0 ignored issues
show
Unused Code introduced by
The parameter $memberof is not used and could be removed. ( Ignorable by Annotation )

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

1446
        /** @scrutinizer ignore-unused */ $memberof,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1447
        $acr,
0 ignored issues
show
Unused Code introduced by
The parameter $acr is not used and could be removed. ( Ignorable by Annotation )

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

1447
        /** @scrutinizer ignore-unused */ $acr,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1448
        $entitlement,
0 ignored issues
show
Unused Code introduced by
The parameter $entitlement is not used and could be removed. ( Ignorable by Annotation )

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

1448
        /** @scrutinizer ignore-unused */ $entitlement,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1449
        $itrustuin,
0 ignored issues
show
Unused Code introduced by
The parameter $itrustuin is not used and could be removed. ( Ignorable by Annotation )

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

1449
        /** @scrutinizer ignore-unused */ $itrustuin,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1450
        $subjectID,
0 ignored issues
show
Unused Code introduced by
The parameter $subjectID is not used and could be removed. ( Ignorable by Annotation )

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

1450
        /** @scrutinizer ignore-unused */ $subjectID,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1451
        $pairwiseID,
0 ignored issues
show
Unused Code introduced by
The parameter $pairwiseID is not used and could be removed. ( Ignorable by Annotation )

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

1451
        /** @scrutinizer ignore-unused */ $pairwiseID,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1452
        $clientparams,
0 ignored issues
show
Unused Code introduced by
The parameter $clientparams is not used and could be removed. ( Ignorable by Annotation )

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

1452
        /** @scrutinizer ignore-unused */ $clientparams,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1453
        $redirect,
1454
        $redirectform,
1455
        $edugainandgetcert
1456
    ) {
1457
        Util::unsetAllUserSessionVars();
1458
1459
        static::printHeader('Error Logging On');
1460
        static::printCollapseBegin(
1461
            'attributeerror',
1462
            'Attribute Release Error',
1463
            false
1464
        );
1465
1466
        echo '
1467
              <div class="card-body px-5">
1468
        ';
1469
1470
        $errorboxstr = '
1471
                <div class="card-text my-2">
1472
                  There was a problem logging on. Your identity provider
1473
                  has not provided CILogon with required information.
1474
                </div> <!-- end card-text -->
1475
                <dl class="row">';
1476
1477
        $missingattrs = '';
1478
        // Show user which attributes are missing
1479
        if ((strlen($ePPN) == 0) && (strlen($ePTID) == 0)) {
1480
            $errorboxstr .= '
1481
                <dt class="col-sm-3">ePTID:</dt>
1482
                <dd class="col-sm-9">MISSING</dd>
1483
                <dt class="col-sm-3">ePPN:</dt>
1484
                <dd class="col-sm-9">MISSING</dd>';
1485
            $missingattrs .= '%0D%0A    eduPersonPrincipalName' .
1486
                             '%0D%0A    eduPersonTargetedID ';
1487
        }
1488
        if ((strlen($firstname) == 0) && (strlen($displayname) == 0)) {
1489
            $errorboxstr .= '
1490
                <dt class="col-sm-3">First Name:</dt>
1491
                <dd class="col-sm-9">MISSING</dd>';
1492
            $missingattrs .= '%0D%0A    givenName (first name)';
1493
        }
1494
        if ((strlen($lastname) == 0) && (strlen($displayname) == 0)) {
1495
            $errorboxstr .= '
1496
                <dt class="col-sm-3">Last Name:</dt>
1497
                <dd class="col-sm-9">MISSING</dd>';
1498
            $missingattrs .= '%0D%0A    sn (last name)';
1499
        }
1500
        if (
1501
            (strlen($displayname) == 0) &&
1502
            ((strlen($firstname) == 0) || (strlen($lastname) == 0))
1503
        ) {
1504
            $errorboxstr .= '
1505
                <dt class="col-sm-3">Display Name:</dt>
1506
                <dd class="col-sm-9">MISSING</dd>';
1507
            $missingattrs .= '%0D%0A    displayName';
1508
        }
1509
        $emailvalid = filter_var($emailaddr, FILTER_VALIDATE_EMAIL);
1510
        if ((strlen($emailaddr) == 0) || (!$emailvalid)) {
1511
            $errorboxstr .= '
1512
                <dt class="col-sm-3">Email Address:</dt>
1513
                <dd class="col-sm-9">' .
1514
            ((strlen($emailaddr) == 0) ? 'MISSING' : 'INVALID') . '</dd>';
1515
            $missingattrs .= '%0D%0A    mail (email address)';
1516
        }
1517
        // CIL-326/CIL-539 - For eduGAIN IdPs attempting to get a cert,
1518
        // print out missing R&S and SIRTFI values
1519
        $idplist = Util::getIdpList();
1520
        if ($edugainandgetcert) {
1521
            if (!$idplist->isREFEDSRandS($idp)) {
1522
                $errorboxstr .= '
1523
                    <dt class="col-sm-3"><a target="_blank"
1524
                    href="http://refeds.org/category/research-and-scholarship">Research
1525
                    and Scholarship</a>:</dt>
1526
                    <dd class="col-sm-9">MISSING</dd>';
1527
                $missingattrs .= '%0D%0A    http://refeds.org/category/research-and-scholarship';
1528
            }
1529
            if (!$idplist->isSIRTFI($idp)) {
1530
                $errorboxstr .= '
1531
                    <dt class="col-sm-3"><a target="_blank"
1532
                    href="https://refeds.org/sirtfi">SIRTFI</a>:</dt>
1533
                    <dd class="col-sm-9">MISSING</dd>';
1534
                $missingattrs .= '%0D%0A    http://refeds.org/sirtfi';
1535
            }
1536
        }
1537
        $student = false;
1538
        $errorboxstr .= '</dl>';
1539
1540
        static::printErrorBox($errorboxstr);
1541
1542
        if (
1543
            (strlen($emailaddr) == 0) &&
1544
            (preg_match('/student@/', $affiliation))
1545
        ) {
1546
            $student = true;
1547
            echo '
1548
                <div class="card-text my-2">
1549
                  <strong>If you are a student</strong>
1550
                  you may need to ask your identity provider
1551
                  to release your email address.
1552
                </div> <!-- end card-text -->
1553
            ';
1554
        }
1555
1556
        // Get contacts from metadata for email addresses
1557
        $shibarray = $idplist->getShibInfo($idp);
1558
        $emailmsg = '?subject=Attribute Release Problem for CILogon' .
1559
        '&[email protected]' .
1560
        '&body=Hello, I am having trouble logging on to ' .
1561
        'https://' . DEFAULT_HOSTNAME . '/ using the ' . $idpname .
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\DEFAULT_HOSTNAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1562
        ' Identity Provider (IdP) ' .
1563
        'due to the following missing attributes:%0D%0A' .
1564
        $missingattrs;
1565
        if ($student) {
1566
            $emailmsg .= '%0D%0A%0D%0ANote that my account is ' .
1567
            'marked "student" and thus my email address may need ' .
1568
            'to be released.';
1569
        }
1570
        $emailmsg .= '%0D%0A%0D%0APlease see ' .
1571
            'http://www.cilogon.org/service/addidp for more ' .
1572
            'details. Thank you for any help you can provide.';
1573
        echo '
1574
                <div class="card-text my-2">
1575
                  Contact your identity provider to let them know you are
1576
                  having having a problem logging on to CILogon.
1577
                </div> <!-- end card-text -->
1578
                <ul>
1579
            ';
1580
1581
        $addrfound = false;
1582
        $name = @$shibarray['Support Name'];
1583
        $addr = @$shibarray['Support Address'];
1584
        $addr = preg_replace('/^mailto:/', '', $addr);
1585
1586
        if (strlen($addr) > 0) {
1587
            $addrfound = true;
1588
            if (strlen($name) == 0) { // Use address if no name given
1589
                $name = $addr;
1590
            }
1591
            echo '
1592
                  <li> Support Contact: ' ,
1593
                  $name , ' <a class="btn btn-primary" href="mailto:' ,
1594
                  $addr , $emailmsg , '">' ,
1595
                  $addr , '</a>
1596
                  </li>';
1597
        }
1598
1599
        if (!$addrfound) {
1600
            $name = @$shibarray['Technical Name'];
1601
            $addr = @$shibarray['Technical Address'];
1602
            $addr = preg_replace('/^mailto:/', '', $addr);
1603
            if (strlen($addr) > 0) {
1604
                $addrfound = true;
1605
                if (strlen($name) == 0) { // Use address if no name given
1606
                    $name = $addr;
1607
                }
1608
                echo '
1609
                      <li> Technical Contact: ' ,
1610
                      $name , ' <a class="btn btn-primary" href="mailto:' ,
1611
                      $addr , $emailmsg , '">' ,
1612
                      $addr , '</a>
1613
                      </li>';
1614
            }
1615
        }
1616
1617
        if (!$addrfound) {
1618
            $name = @$shibarray['Administrative Name'];
1619
            $addr = @$shibarray['Administrative Address'];
1620
            $addr = preg_replace('/^mailto:/', '', $addr);
1621
            if (strlen($addr) > 0) {
1622
                if (strlen($name) == 0) { // Use address if no name given
1623
                    $name = $addr;
1624
                }
1625
                echo '
1626
                      <li>Administrative Contact: ' ,
1627
                      $name , ' <a class="btn btn-primary" href="mailto:' ,
1628
                      $addr , $emailmsg , '">' ,
1629
                      $addr , '</a>
1630
                      </li>';
1631
            }
1632
        }
1633
1634
        echo '
1635
                </ul>
1636
                <div class="card-text my-2">
1637
                  Alternatively, you can contact us at the email address
1638
                  at the bottom of the page.
1639
                </div> <!-- end card-text -->
1640
            ';
1641
1642
        static::printFormHead($redirect, 'get');
1643
1644
        echo '
1645
              <div class="card-text my-2">
1646
                <div class="form-group">
1647
                  <div class="form-row align-items-center
1648
                  justify-content-center">
1649
                    <div class="col-auto">
1650
                      ', $redirectform, '
1651
                      <input type="submit" name="submit"
1652
                      class="btn btn-primary submit form-control"
1653
                      value="Proceed" />
1654
                    </div>
1655
                  </div> <!-- end form-row align-items-center -->
1656
                </div> <!-- end form-group -->
1657
              </div> <!-- end card-text -->
1658
            </form>
1659
            </div> <!-- end card-body -->';
1660
1661
        Content::printCollapseEnd();
1662
        Content::printFooter();
1663
    }
1664
1665
    /**
1666
     * printOAuth2AttributeReleaseErrorPage
1667
     *
1668
     * This function is called by handleGotUser when the IdP did not release
1669
     * all required attributes for the user. In the case of the OAuth2
1670
     * providers, this is typically due to one of first name, last name,
1671
     * and/or email address. Print out a special message for each OAuth2 IdP
1672
     * to let the user know how to fix the issue.
1673
     *
1674
     * @param string $idpname The name of the OAuth2 IdP.
1675
     * @param string $redirect The url for the <form> element
1676
     * @param string $redirectform Additional hidden input fields for the
1677
     *        <form>.
1678
     *
1679
     */
1680
    public static function printOAuth2AttributeReleaseErrorPage($idpname, $redirect, $redirectform)
1681
    {
1682
        Util::unsetAllUserSessionVars();
1683
        static::printHeader('Error Logging On');
1684
        static::printCollapseBegin(
1685
            'oauth2attrerror',
1686
            'Error Logging On',
1687
            false
1688
        );
1689
1690
        echo '
1691
            <div class="card-body px-5">';
1692
1693
        static::printErrorBox('There was a problem logging on.');
1694
1695
        if ($idpname == 'Google') {
1696
            echo '
1697
              <div class="card-text my-2">
1698
                There was a problem logging on. It appears that you have
1699
                attempted to use Google as your identity provider, but your
1700
                name or email address was missing. To rectify this problem,
1701
                go to the <a target="_blank"
1702
                href="https://myaccount.google.com/privacy#personalinfo">Google
1703
                Account Personal Information page</a>, and enter your first
1704
                name, last name, and email address. (All other Google
1705
                account information is not required by the CILogon Service.)
1706
              </div>
1707
              <div class="card-text my-2">
1708
                After you have updated your Google account profile, click
1709
                the "Proceed" button below and attempt to log on
1710
                with your Google account again. If you have any questions,
1711
                please contact us at the email address at the bottom of the
1712
                page.
1713
              </div>';
1714
        } elseif ($idpname == 'GitHub') {
1715
            echo '
1716
              <div class="card-text my-2">
1717
                There was a problem logging on. It appears that you have
1718
                attempted to use GitHub as your identity provider, but your
1719
                name or email address was missing. To rectify this problem,
1720
                go to the <a target="_blank"
1721
                href="https://github.com/settings/profile">GitHub
1722
                Public Profile page</a>, and enter your name and email
1723
                address. (All other GitHub account information is not
1724
                required by the CILogon Service.)
1725
              </div>
1726
              <div class="card-text my-2">
1727
                After you have updated your GitHub account profile, click
1728
                the "Proceed" button below and attempt to log on
1729
                with your GitHub account again. If you have any questions,
1730
                please contact us at the email address at the bottom of the
1731
                page.
1732
              </div>';
1733
        } elseif ($idpname == 'ORCID') {
1734
            echo '
1735
              <div class="card-text my-2">
1736
                There was a problem logging on. It appears that you have
1737
                attempted to use ORCID as your identity provider, but your
1738
                name or email address was missing. To rectify this problem,
1739
                go to your <a target="_blank"
1740
                href="https://orcid.org/my-orcid">ORCID
1741
                Profile page</a>, enter your name and email address, and
1742
                make sure they can be viewed by Everyone.
1743
                (All other ORCID account information is not required by
1744
                the CILogon Service.)
1745
              </div>
1746
              <div class="card-text my-2">
1747
                After you have updated your ORCID account profile, click
1748
                the "Proceed" button below and attempt to log on
1749
                with your ORCID account again. If you have any questions,
1750
                please contact us at the email address at the bottom of the
1751
                page.
1752
              </div>';
1753
        }
1754
1755
        static::printFormHead($redirect, 'get');
1756
1757
        echo '
1758
              <div class="card-text my-2">
1759
                <div class="form-group">
1760
                  <div class="form-row align-items-center
1761
                  justify-content-center">
1762
                    <div class="col-auto">
1763
                      <input type="hidden" name="providerId"
1764
                      value="' ,
1765
                      Util::getAuthzUrl($idpname) , '" />
1766
                      ', $redirectform, '
1767
                      <input type="submit" name="submit"
1768
                      class="btn btn-primary submit form-control"
1769
                      value="Proceed" />
1770
                    </div>
1771
                  </div> <!-- end form-row align-items-center -->
1772
                </div> <!-- end form-group -->
1773
              </div> <!-- end card-text -->
1774
            </form>
1775
            </div> <!-- end card-body -->';
1776
1777
        Content::printCollapseEnd();
1778
        Content::printFooter();
1779
    }
1780
1781
    /**
1782
     * handleLogOnButtonClicked
1783
     *
1784
     * This function is called when the user clicks the 'Log On' button
1785
     * on the IdP selection page. It checks to see if the 'Remember this
1786
     * selection' checkbox was checked and sets a cookie appropriately. It
1787
     * also sets a cookie 'providerId' so the last chosen IdP will be
1788
     * selected the next time the user visits the site. The function then
1789
     * calls the appropriate 'redirectTo...' function to send the user
1790
     * to the chosen IdP.
1791
     */
1792
    public static function handleLogOnButtonClicked()
1793
    {
1794
        // Get the list of currently available IdPs
1795
        $idps = static::getCompositeIdPList();
1796
1797
        // Set the cookie for keepidp if the checkbox was checked
1798
        $pc = new PortalCookie();
1799
        Util::setPortalOrCookie(
1800
            $pc,
1801
            'keepidp',
1802
            ((strlen(Util::getPostVar('keepidp')) > 0) ? 'checked' : '')
1803
        );
1804
1805
        // Get the user-chosen IdP from the posted form
1806
        $providerId = Util::getPostVar('providerId');
1807
        $providerIdValid = ((strlen($providerId) > 0) &&
1808
                            (isset($idps[$providerId])));
1809
1810
        // Set the cookie for the last chosen IdP and redirect to it if in list
1811
        Util::setPortalOrCookie(
1812
            $pc,
1813
            'providerId',
1814
            ($providerIdValid ? $providerId : ''),
1815
            true
1816
        );
1817
        if ($providerIdValid) {
1818
            $providerName = Util::getAuthzIdP($providerId);
1819
            if (in_array($providerName, Util::$oauth2idps)) {
1820
                // Log in with an OAuth2 IdP
1821
                static::redirectToGetOAuth2User($providerId);
1822
            } else { // Use InCommon authn
1823
                static::redirectToGetShibUser($providerId);
1824
            }
1825
        } else { // IdP not in list, or no IdP selected
1826
            Util::setSessionVar('logonerror', 'Please select a valid IdP.');
1827
            printLogonPage();
0 ignored issues
show
Bug introduced by
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

1827
            /** @scrutinizer ignore-call */ 
1828
            printLogonPage();
Loading history...
1828
        }
1829
    }
1830
1831
    /**
1832
     * handleNoSubmitButtonClicked
1833
     *
1834
     * This function is the 'default' case when no 'submit' button has been
1835
     * clicked, or if the submit session variable is not set. It checks
1836
     * to see if either the <forceinitialidp> option is set, or if the
1837
     * 'Remember this selection' checkbox was previously checked. If so,
1838
     * then rediret to the appropriate IdP. Otherwise, print the main
1839
     * Log On page.
1840
     */
1841
    public static function handleNoSubmitButtonClicked()
1842
    {
1843
        $providerId = '';
1844
        $keepidp = '';
1845
        $selected_idp = '';
1846
        $redirect_uri = '';
1847
        $client_id = '';
1848
        $callbackuri = Util::getSessionVar('callbackuri');
1849
        $readidpcookies = true;  // Assume config options are not set
1850
        $skin = Util::getSkin();
1851
        $forceinitialidp = (int)$skin->getConfigOption('forceinitialidp');
1852
        $initialidp = (string)$skin->getConfigOption('initialidp');
1853
1854
        // If this is a OIDC transaction, get the redirect_uri and
1855
        // client_id parameters from the session var clientparams.
1856
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
1857
        if (isset($clientparams['redirect_uri'])) {
1858
            $redirect_uri = $clientparams['redirect_uri'];
1859
        }
1860
        if (isset($clientparams['client_id'])) {
1861
            $client_id = $clientparams['client_id'];
1862
        }
1863
1864
        // Use the first element of the idphint list as the selected_idp.
1865
        $idphintlist = static::getIdphintList();
1866
        if (!empty($idphintlist)) {
1867
            $selected_idp = $idphintlist[0];
1868
        }
1869
1870
        if ((strlen($redirect_uri) > 0) || (strlen($client_id) > 0)) {
1871
            // CIL-431 - If the OAuth2/OIDC $redirect_uri or $client_id is set,
1872
            // then check for a match in the BYPASS_IDP_ARRAY to see if we
1873
            // should automatically redirect to a specific IdP. Used mainly
1874
            // by campus gateways.
1875
            $bypassidp = '';
1876
            foreach (BYPASS_IDP_ARRAY as $key => $value) {
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\BYPASS_IDP_ARRAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1877
                if (
1878
                    (preg_match($key, $redirect_uri)) ||
1879
                    (preg_match($key, $client_id))
1880
                ) {
1881
                    $bypassidp = $value;
1882
                    break;
1883
                }
1884
            }
1885
1886
            // CIL-613 - Next, check for a match in the ALLOW_BYPASS_ARRAY.
1887
            // If found, then allow the idphint/selected_idp to be used as the
1888
            // IdP to redirect to.
1889
            if (empty($bypassidp) && (!empty($selected_idp))) {
1890
                foreach (ALLOW_BYPASS_ARRAY as $value) {
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\ALLOW_BYPASS_ARRAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1891
                    if (
1892
                        (preg_match($value, $redirect_uri)) ||
1893
                        (preg_match($value, $client_id))
1894
                    ) {
1895
                        $bypassidp = $selected_idp;
1896
                        break;
1897
                    }
1898
                }
1899
            }
1900
1901
            if (!empty($bypassidp)) { // Match found!
1902
                $providerId = $bypassidp;
1903
                $keepidp = 'checked';
1904
                // To skip the next code blocks, unset a few variables.
1905
                $forceinitialidp = 0;     // Skip checking this option
1906
                $selected_idp = '';       // Skip any passed-in option
1907
                $readidpcookies = false;  // Don't read in the IdP cookies
1908
            }
1909
        }
1910
1911
        // If the <forceinitialidp> option is set, use either the
1912
        // <initialidp> or the selected_idp as the providerId, and use
1913
        // <forceinitialidp> as keepIdp. Otherwise, read the cookies
1914
        // 'providerId' and 'keepidp'.
1915
        if (
1916
            ($forceinitialidp == 1) &&
1917
            ((strlen($initialidp) > 0) || (strlen($selected_idp) > 0))
1918
        ) {
1919
            // If the <allowforceinitialidp> option is set, then make sure
1920
            // the callback / redirect uri is in the portal list.
1921
            $afii = $skin->getConfigOption('portallistaction', 'allowforceinitialidp');
1922
            if (
1923
                (is_null($afii)) || // Option not set, no need to check portal list
1924
                (((int)$afii == 1) &&
1925
                  (($skin->inPortalList($redirect_uri)) ||
1926
                   ($skin->inPortalList($client_id)) ||
1927
                   ($skin->inPortalList($callbackuri))))
1928
            ) {
1929
                // 'selected_idp' takes precedence over <initialidp>
1930
                if (strlen($selected_idp) > 0) {
1931
                    $providerId = $selected_idp;
1932
                } else {
1933
                    $providerId = $initialidp;
1934
                }
1935
                $keepidp = $forceinitialidp;
1936
                $readidpcookies = false; // Don't read in the IdP cookies
1937
            }
1938
        }
1939
1940
        // <initialidp> options not set, or portal not in portal list?
1941
        // Get idp and 'Remember this selection' from cookies instead.
1942
        $pc = new PortalCookie();
1943
        $pn = $pc->getPortalName();
1944
        if ($readidpcookies) {
1945
            // Check the portalcookie first, then the 'normal' cookies
1946
            if (strlen($pn) > 0) {
1947
                $keepidp    = $pc->get('keepidp');
1948
                $providerId = $pc->get('providerId');
1949
            } else {
1950
                $keepidp    = Util::getCookieVar('keepidp');
1951
                $providerId = Util::getCookieVar('providerId');
1952
            }
1953
        }
1954
1955
        // If both 'keepidp' and 'providerId' were set (and the
1956
        // providerId is a whitelisted IdP or valid OpenID provider),
1957
        // then skip the Logon page and proceed to the appropriate
1958
        // getuser script.
1959
        if ((strlen($providerId) > 0) && (strlen($keepidp) > 0)) {
1960
            // If selected_idp was specified at the OIDC authorize endpoint,
1961
            // make sure that it matches the saved providerId. If not,
1962
            // then show the Logon page and uncheck the keepidp checkbox.
1963
            if ((strlen($selected_idp) == 0) || ($selected_idp == $providerId)) {
1964
                Util::setPortalOrCookie($pc, 'providerId', $providerId, true);
1965
                $providerName = Util::getAuthzIdP($providerId);
1966
                if (in_array($providerName, Util::$oauth2idps)) {
1967
                    // Log in with an OAuth2 IdP
1968
                    static::redirectToGetOAuth2User($providerId);
1969
                } elseif (Util::getIdpList()->exists($providerId)) {
1970
                    // Log in with InCommon
1971
                    static::redirectToGetShibUser($providerId);
1972
                } else { // $providerId not in whitelist
1973
                    Util::setPortalOrCookie($pc, 'providerId', '', true);
1974
                    printLogonPage();
0 ignored issues
show
Bug introduced by
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

1974
                    /** @scrutinizer ignore-call */ 
1975
                    printLogonPage();
Loading history...
1975
                }
1976
            } else { // selected_idp does not match saved providerId
1977
                Util::setPortalOrCookie($pc, 'keepidp', '', true);
1978
                printLogonPage();
1979
            }
1980
        } else { // One of providerId or keepidp was not set
1981
            printLogonPage();
1982
        }
1983
    }
1984
1985
    /**
1986
     * verifyCurrentUserSession
1987
     *
1988
     * This function verifies the contents of the PHP session.  It checks
1989
     * the following:
1990
     * (1) The persistent store 'uid', the Identity Provider 'idp', the
1991
     *     IdP Display Name 'idpname', and the 'status' (of getUser()) are
1992
     *     all non-empty strings.
1993
     * (2) The 'status' (of getUser()) is even (i.e. STATUS_OK).
1994
     * (3) If $providerId is passed-in, it must match 'idp'.
1995
     * If all checks are good, then this function returns true.
1996
     *
1997
     * @param string $providerId (Optional) The user-selected Identity
1998
     *        Provider. If set, make sure $providerId matches the PHP
1999
     *        session variable 'idp'.
2000
     * @return bool True if the contents of the PHP session ar valid.
2001
     *              False otherwise.
2002
     */
2003
    public static function verifyCurrentUserSession($providerId = '')
2004
    {
2005
        $retval = false;
2006
2007
        $idp       = Util::getSessionVar('idp');
2008
        $idpname   = Util::getSessionVar('idpname');
2009
        $uid       = Util::getSessionVar('uid');
2010
        $status    = Util::getSessionVar('status');
2011
        $dn        = Util::getSessionVar('dn');
2012
        $authntime = Util::getSessionVar('authntime');
2013
2014
        // CIL-410 When using the /testidp/ flow, the 'storeattributes'
2015
        // session var is set. In this case, the only attribute that
2016
        // is needed is 'idp' (entityID).
2017
        if (Util::getSessionVar('storeattributes') == '1') {
2018
            if (strlen($idp) > 0) {
2019
                $retval = true;
2020
            }
2021
        } elseif (
2022
            (strlen($uid) > 0) && (strlen($idp) > 0) &&
2023
            (strlen($idpname) > 0) && (strlen($status) > 0) &&
2024
            (strlen($dn) > 0) && (strlen($authntime) > 0) &&
2025
            (!($status & 1)) // All STATUS_OK codes are even
2026
        ) {
2027
            // Check for eduGAIN IdP and possible get cert context
2028
            if (Util::isEduGAINAndGetCert()) {
2029
                Util::unsetUserSessionVars();
2030
            } elseif ((strlen($providerId) == 0) || ($providerId == $idp)) {
2031
                // If $providerId passed in, make sure it matches the $idp
2032
                $retval = true;
2033
                Util::getSkin()->init(); // Does the IdP need a forced skin?
2034
            }
2035
        }
2036
2037
        return $retval;
2038
    }
2039
2040
    /**
2041
     * redirectToGetShibUser
2042
     *
2043
     * This method redirects control flow to the getuser script for
2044
     * If the first parameter (a whitelisted entityId) is not specified,
2045
     * we check to see if either the providerId PHP session variable or the
2046
     * providerId cookie is set (in that order) and use one if available.
2047
     * The function then checks to see if there is a valid PHP session
2048
     * and if the providerId matches the 'idp' in the session.  If so, then
2049
     * we don't need to redirect to '/secure/getuser/' and instead we
2050
     * we display the main page.  However, if the PHP session is not valid,
2051
     * then this function redirects to the '/secure/getuser/' script so as
2052
     * to do a Shibboleth authentication via mod_shib. When the providerId
2053
     * is non-empty, the SessionInitiator will automatically go to that IdP
2054
     * (i.e. without stopping at a WAYF).  This function also sets
2055
     * several PHP session variables that are needed by the getuser script,
2056
     * including the 'responsesubmit' variable which is set as the return
2057
     * 'submit' variable in the 'getuser' script.
2058
     *
2059
     * @param string $providerId (Optional) An entityId of the
2060
     *        authenticating IdP. If not specified (or set to the empty
2061
     *        string), we check providerId PHP session variable and
2062
     *        providerId cookie (in that order) for non-empty values.
2063
     * @param string $responsesubmit (Optional) The value of the PHP session
2064
     *       'submit' variable to be set upon return from the 'getuser'
2065
     *        script.  This is utilized to control the flow of this script
2066
     *        after 'getuser'. Defaults to 'gotuser'.
2067
     * @param string $responseurl (Optional) A response url for redirection
2068
     *        after successful processing at /secure/getuser/. Defaults to
2069
     *        the current script directory.
2070
     */
2071
    public static function redirectToGetShibUser(
2072
        $providerId = '',
2073
        $responsesubmit = 'gotuser',
2074
        $responseurl = null
2075
    ) {
2076
        // If providerId not set, try the cookie value
2077
        if (strlen($providerId) == 0) {
2078
            $providerId = Util::getPortalOrNormalCookieVar('providerId');
2079
        }
2080
2081
        // If the user has a valid 'uid' in the PHP session, and the
2082
        // providerId matches the 'idp' in the PHP session, then
2083
        // simply go to the main page.
2084
        if (static::verifyCurrentUserSession($providerId)) {
2085
            printMainPage();
0 ignored issues
show
Bug introduced by
The function printMainPage 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

2085
            /** @scrutinizer ignore-call */ 
2086
            printMainPage();
Loading history...
2086
        } else { // Otherwise, redirect to the getuser script
2087
            // Set PHP session varilables needed by the getuser script
2088
            Util::setSessionVar(
2089
                'responseurl',
2090
                (is_null($responseurl) ?
2091
                    Util::getScriptDir(true) : $responseurl)
2092
            );
2093
            Util::setSessionVar('submit', 'getuser');
2094
            Util::setSessionVar('responsesubmit', $responsesubmit);
2095
            Util::getCsrf()->setCookieAndSession();
2096
2097
            // Set up the 'header' string for redirection thru mod_shib
2098
            $mhn = static::getMachineHostname($providerId);
2099
            $redirect = "Location: https://$mhn/Shibboleth.sso/Login?target=" .
2100
                urlencode("https://$mhn/secure/getuser/");
2101
2102
            if (strlen($providerId) > 0) {
2103
                // Use special NIHLogin Shibboleth SessionInitiator for acsByIndex
2104
                if ($providerId == 'urn:mace:incommon:nih.gov') {
2105
                    $redirect = preg_replace(
2106
                        '%/Shibboleth.sso/Login%',
2107
                        '/Shibboleth.sso/NIHLogin',
2108
                        $redirect
2109
                    );
2110
                }
2111
2112
                $redirect .= '&providerId=' . urlencode($providerId);
2113
2114
                // To bypass SSO at IdP, check for session var 'forceauthn' == 1
2115
                $forceauthn = Util::getSessionVar('forceauthn');
2116
                Util::unsetSessionVar('forceauthn');
2117
                if ($forceauthn) {
2118
                    $redirect .= '&forceAuthn=true';
2119
                } elseif (strlen($forceauthn) == 0) {
2120
                    // 'forceauth' was not set to '0' in the session, so
2121
                    // check the skin's option instead.
2122
                    $forceauthn = Util::getSkin()->getConfigOption('forceauthn');
2123
                    if ((!is_null($forceauthn)) && ((int)$forceauthn == 1)) {
2124
                        $redirect .= '&forceAuthn=true';
2125
                    }
2126
                }
2127
            }
2128
2129
            $log = new Loggit();
2130
            $log->info('Shibboleth Login="' . $redirect . '"');
2131
            header($redirect);
2132
            exit; // No further processing necessary
2133
        }
2134
    }
2135
2136
    /**
2137
     * redirectToGetOAuth2User
2138
     *
2139
     * This method redirects control flow to the getuser script for
2140
     * when the user logs in via OAuth 2.0. It first checks to see
2141
     * if we have a valid session. If so, we don't need to redirect and
2142
     * instead simply show the Get Certificate page. Otherwise, we start
2143
     * an OAuth 2.0 logon by composing a parameterized GET URL using
2144
     * the OAuth 2.0 endpoint.
2145
     *
2146
     * @param string $providerId (Optional) An entityId of the
2147
     *        authenticating IdP. If not specified (or set to the empty
2148
     *        string), we check providerId PHP session variable and
2149
     *        providerId cookie (in that order) for non-empty values.
2150
     * @param string $responsesubmit (Optional) The value of the PHP session
2151
     *        'submit' variable to be set upon return from the 'getuser'
2152
     *         script.  This is utilized to control the flow of this script
2153
     *         after 'getuser'. Defaults to 'gotuser'.
2154
     */
2155
    public static function redirectToGetOAuth2User(
2156
        $providerId = '',
2157
        $responsesubmit = 'gotuser'
2158
    ) {
2159
        // If providerId not set, try the cookie value
2160
        if (strlen($providerId) == 0) {
2161
            $providerId = Util::getPortalOrNormalCookieVar('providerId');
2162
        }
2163
2164
        // If the user has a valid 'uid' in the PHP session, and the
2165
        // providerId matches the 'idp' in the PHP session, then
2166
        // simply go to the 'Download Certificate' button page.
2167
        if (static::verifyCurrentUserSession($providerId)) {
2168
            printMainPage();
0 ignored issues
show
Bug introduced by
The function printMainPage 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

2168
            /** @scrutinizer ignore-call */ 
2169
            printMainPage();
Loading history...
2169
        } else { // Otherwise, redirect to the OAuth 2.0 endpoint
2170
            // Set PHP session varilables needed by the getuser script
2171
            Util::unsetSessionVar('logonerror');
2172
            Util::setSessionVar('responseurl', Util::getScriptDir(true));
2173
            Util::setSessionVar('submit', 'getuser');
2174
            Util::setSessionVar('responsesubmit', $responsesubmit);
2175
            $csrf = Util::getCsrf();
2176
            $csrf->setCookieAndSession();
2177
            $extraparams = array();
2178
            $extraparams['state'] = $csrf->getTokenValue();
2179
2180
            // To bypass SSO at IdP, check for session var 'forceauthn' == 1
2181
            $forceauthn = Util::getSessionVar('forceauthn');
2182
            Util::unsetSessionVar('forceauthn');
2183
            if ($forceauthn) {
2184
                $extraparams['approval_prompt'] = 'force';
2185
            } elseif (strlen($forceauthn) == 0) {
2186
                // 'forceauth' was not set to '0' in the session, so
2187
                // check the skin's option instead.
2188
                $forceauthn = Util::getSkin()->getConfigOption('forceauthn');
2189
                if ((!is_null($forceauthn)) && ((int)$forceauthn == 1)) {
2190
                    $extraparams['approval_prompt'] = 'force';
2191
                }
2192
            }
2193
2194
            // Get the provider name based on the provider authz URL
2195
            $providerName = Util::getAuthzIdP($providerId);
2196
2197
            // Get the authz URL and redirect
2198
            $oauth2 = new OAuth2Provider($providerName);
2199
            if (is_null($oauth2->provider)) {
2200
                Util::setSessionVar('logonerror', 'Invalid Identity Provider.');
2201
                printLogonPage();
0 ignored issues
show
Bug introduced by
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

2201
                /** @scrutinizer ignore-call */ 
2202
                printLogonPage();
Loading history...
2202
            } else {
2203
                $authUrl = $oauth2->provider->getAuthorizationUrl(
2204
                    array_merge(
2205
                        $oauth2->authzUrlOpts,
2206
                        $extraparams
2207
                    )
2208
                );
2209
                header('Location: ' . $authUrl);
2210
                exit; // No further processing necessary
2211
            }
2212
        }
2213
    }
2214
2215
    /**
2216
     * handleGotUser
2217
     *
2218
     * This function is called upon return from one of the getuser scripts
2219
     * which should have set the 'uid' and 'status' PHP session variables.
2220
     * It verifies that the status return is one of STATUS_OK (even
2221
     * values).  If not, we print an error message to the user.
2222
     */
2223
    public static function handleGotUser()
2224
    {
2225
        $log = new Loggit();
2226
        $uid = Util::getSessionVar('uid');
2227
        $status = Util::getSessionVar('status');
2228
2229
        // We must get and unset session vars BEFORE any HTML output since
2230
        // a redirect may go to another site, meaning we need to update
2231
        // the session cookie before we leave the cilogon.org domain.
2232
        $ePPN         = Util::getSessionVar('ePPN');
2233
        $ePTID        = Util::getSessionVar('ePTID');
2234
        $firstname    = Util::getSessionVar('firstname');
2235
        $lastname     = Util::getSessionVar('lastname');
2236
        $displayname  = Util::getSessionVar('displayname');
2237
        $emailaddr    = Util::getSessionVar('emailaddr');
2238
        $idp          = Util::getSessionVar('idp');
2239
        $idpname      = Util::getSessionVar('idpname');
2240
        $affiliation  = Util::getSessionVar('affiliation');
2241
        $ou           = Util::getSessionVar('ou');
2242
        $memberof     = Util::getSessionVar('memberof');
2243
        $acr          = Util::getSessionVar('acr');
2244
        $entitlement  = Util::getSessionVar('entitlement');
2245
        $itrustuin    = Util::getSessionVar('itrustuin');
2246
        $subjectID    = Util::getSessionVar('subjectID');
2247
        $pairwiseID   = Util::getSessionVar('pairwiseID');
2248
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
2249
        $failureuri   = Util::getSessionVar('failureuri');
2250
2251
        // CIL-410 The /testidp/ flow is indicated by the presence of the
2252
        // 'storeattributes' PHP session var. In this case, simply show
2253
        // the main testidp page with user and IdP attributes.
2254
        if (!empty(Util::getSessionVar('storeattributes'))) {
2255
            printMainPage();
0 ignored issues
show
Bug introduced by
The function printMainPage 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

2255
            /** @scrutinizer ignore-call */ 
2256
            printMainPage();
Loading history...
2256
            return;
2257
        }
2258
2259
        // Check for OIDC redirect_uri or OAuth 1.0a failureuri.
2260
        // If found, set 'Proceed' button redirect appropriately.
2261
        $redirect = '';
2262
        $redirectform = '';
2263
        // First, check for OIDC redirect_uri, with parameters in <form>
2264
        if (isset($clientparams['redirect_uri'])) {
2265
            $redirect = $clientparams['redirect_uri'];
2266
            $redirectform = '<input type="hidden" name="error" value="access_denied" />' .
2267
                '<input type="hidden" name="error_description" value="Missing attributes" />';
2268
            if (isset($clientparams['state'])) {
2269
                $redirectform .= '<input type="hidden" name="state" value="' .
2270
                    $clientparams['state'] . '" />';
2271
            }
2272
        }
2273
2274
        // Next, check for OAuth 1.0a
2275
        if ((strlen($redirect) == 0) && (strlen($failureuri) > 0)) {
2276
            $redirect = $failureuri . "?reason=missing_attributes";
2277
        }
2278
2279
        $isEduGAINAndGetCert = Util::isEduGAINAndGetCert($idp, $idpname);
2280
2281
        // Check for various error conditions and print out appropriate page
2282
        if (
2283
            (strlen($uid) == 0) ||    // Empty uid
2284
            (strlen($status) == 0) || // Empty status
2285
            ($status & 1) ||          // Odd-numbered status = error
2286
            ($isEduGAINAndGetCert)    // Not allowed
2287
        ) {
2288
            $log->error(
2289
                'Failed to getuser' .
2290
                ($isEduGAINAndGetCert ? ' due to eduGAIN IdP restriction.' : '.')
2291
            );
2292
2293
            // Is this a SAML IdP?
2294
            $idplist = Util::getIdpList();
2295
            $samlidp = ((!empty($idp)) && (!$idplist->isOAuth2($idp)));
2296
2297
            // Was there a misssing parameter?
2298
            $missingparam = ($status ==
2299
                DBService::$STATUS['STATUS_MISSING_PARAMETER_ERROR']);
2300
2301
            if (($isEduGAINAndGetCert) || ($missingparam && $samlidp)) {
2302
                static::printSAMLAttributeReleaseErrorPage(
2303
                    $ePPN,
2304
                    $ePTID,
2305
                    $firstname,
2306
                    $lastname,
2307
                    $displayname,
2308
                    $emailaddr,
2309
                    $idp,
2310
                    $idpname,
2311
                    $affiliation,
2312
                    $ou,
2313
                    $memberof,
2314
                    $acr,
2315
                    $entitlement,
2316
                    $itrustuin,
2317
                    $subjectID,
2318
                    $pairwiseID,
2319
                    $clientparams,
2320
                    $redirect,
2321
                    $redirectform,
2322
                    $isEduGAINAndGetCert
2323
                );
2324
            } elseif ($missingparam && (!$samlidp)) { // OAuth2 IdP
2325
                static::printOAuth2AttributeReleaseErrorPage(
2326
                    $idpname,
2327
                    $redirect,
2328
                    $redirectform
2329
                );
2330
            } else { // General error
2331
                static::printGeneralErrorPage($redirect, $redirectform);
2332
            }
2333
        } else { // EVERYTHING IS OKAY SO FAR
2334
            // Extra security check: Once the user has successfully
2335
            // authenticated with an IdP, verify that the chosen IdP was
2336
            // actually whitelisted. If not, then set error message and show
2337
            // Select an Identity Provider page again.
2338
            Util::getSkin()->init();  // Check for forced skin
2339
            $idps = static::getCompositeIdPList();
2340
            $providerId = Util::getSessionVar('idp');
2341
            if ((strlen($providerId) > 0) && (!isset($idps[$providerId]))) {
2342
                Util::setSessionVar(
2343
                    'logonerror',
2344
                    'Invalid IdP selected. Please try again.'
2345
                );
2346
                Util::sendErrorAlert(
2347
                    'Authentication attempt using non-whitelisted IdP',
2348
                    'A user successfully authenticated with an IdP,
2349
                    however, the selected IdP was not in the list of
2350
                    whitelisted IdPs as determined by the current skin. This
2351
                    might indicate the user attempted to circumvent the
2352
                    security check in "handleGotUser()" for valid IdPs for
2353
                    the skin.'
2354
                );
2355
                Util::unsetCookieVar('providerId');
2356
                Util::unsetAllUserSessionVars();
2357
                printLogonPage();
0 ignored issues
show
Bug introduced by
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

2357
                /** @scrutinizer ignore-call */ 
2358
                printLogonPage();
Loading history...
2358
            } else { // Got user successfully
2359
                static::gotUserSuccess();
2360
            }
2361
        }
2362
    }
2363
2364
    /**
2365
     * gotUserSuccess
2366
     *
2367
     * This function is called after the user has been successfully
2368
     * authenticated. If the 'status' session variable is STATUS_OK
2369
     * then it checks if we have a new or changed user and logs
2370
     * that appropriately. It then continues to the MainPage.
2371
     */
2372
    public static function gotUserSuccess()
2373
    {
2374
        $log = new Loggit();
2375
        $status = Util::getSessionVar('status');
2376
2377
        // If this is the first time the user has used the CILogon Service,
2378
        // and the flow is OAuth-based, send an alert if the name contains
2379
        // any HTML entities.
2380
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
2381
        $callbackuri = Util::getSessionVar('callbackuri');
2382
2383
        if (
2384
            ($status == DBService::$STATUS['STATUS_NEW_USER']) &&
2385
            ((strlen($callbackuri) > 0) ||
2386
             (isset($clientparams['code'])))
2387
        ) {
2388
            // Extra check for new users: see if any HTML entities
2389
            // are in the user name. If so, send an email alert.
2390
            $dn = Util::getSessionVar('dn');
2391
            $dn = static::reformatDN(preg_replace('/\s+email=.+$/', '', $dn));
2392
            $htmldn = Util::htmlent($dn);
2393
            if (strcmp($dn, $htmldn) != 0) {
2394
                Util::sendErrorAlert(
2395
                    'New user DN contains HTML entities',
2396
                    "htmlentites(DN) = $htmldn\n"
2397
                );
2398
            }
2399
        }
2400
2401
        // For a new user, or if the user got new attributes, just log it.
2402
        // Then proceed to the Main Page.
2403
        if ($status == DBService::$STATUS['STATUS_NEW_USER']) {
2404
            $log->info('New User.');
2405
        } elseif ($status == DBService::$STATUS['STATUS_USER_UPDATED']) {
2406
            $log->info('User IdP attributes changed.');
2407
        }
2408
        printMainPage();
0 ignored issues
show
Bug introduced by
The function printMainPage 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

2408
        /** @scrutinizer ignore-call */ 
2409
        printMainPage();
Loading history...
2409
    }
2410
2411
    /**
2412
     * generateP12
2413
     *
2414
     * This function is called when the user clicks the 'Get New
2415
     * Certificate' button. It first reads in the password fields and
2416
     * verifies that they are valid (i.e. they are long enough and match).
2417
     * Then it gets a credential from the MyProxy server and converts that
2418
     * certificate into a PKCS12 which is written to disk.  If everything
2419
     * succeeds, the temporary pkcs12 directory and lifetime is saved to
2420
     * the 'p12' PHP session variable, which is read later when the Main
2421
     * Page HTML is shown.
2422
     */
2423
    public static function generateP12()
2424
    {
2425
        $log = new Loggit();
2426
2427
        // Get the entered p12lifetime and p12multiplier and set the cookies
2428
        list($minlifetime, $maxlifetime) =
2429
            Util::getMinMaxLifetimes('pkcs12', 9516);
2430
        $p12lifetime   = Util::getPostVar('p12lifetime');
2431
        $p12multiplier = Util::getPostVar('p12multiplier');
2432
        if (strlen($p12multiplier) == 0) {
2433
            $p12multiplier = 1;  // For ECP, p12lifetime is in hours
2434
        }
2435
        $lifetime = $p12lifetime * $p12multiplier;
2436
        if ($lifetime <= 0) { // In case user entered negative number
2437
            $lifetime = $maxlifetime;
2438
            $p12lifetime = $maxlifetime;
2439
            $p12multiplier = 1;  // maxlifetime is in hours
2440
        } elseif ($lifetime < $minlifetime) {
2441
            $lifetime = $minlifetime;
2442
            $p12lifetime = $minlifetime;
2443
            $p12multiplier = 1;  // minlifetime is in hours
2444
        } elseif ($lifetime > $maxlifetime) {
2445
            $lifetime = $maxlifetime;
2446
            $p12lifetime = $maxlifetime;
2447
            $p12multiplier = 1;  // maxlifetime is in hours
2448
        }
2449
        Util::setCookieVar('p12lifetime', $p12lifetime);
2450
        Util::setCookieVar('p12multiplier', $p12multiplier);
2451
        Util::setSessionVar('p12lifetime', $p12lifetime);
2452
        Util::setSessionVar('p12multiplier', $p12multiplier);
2453
2454
        // Verify that the password is at least 12 characters long
2455
        $password1 = Util::getPostVar('password1');
2456
        $password2 = Util::getPostVar('password2');
2457
        $p12password = Util::getPostVar('p12password');  // For ECP clients
2458
        if (strlen($p12password) > 0) {
2459
            $password1 = $p12password;
2460
            $password2 = $p12password;
2461
        }
2462
        if (strlen($password1) < 12) {
2463
            Util::setSessionVar(
2464
                'p12error',
2465
                'Password must have at least 12 characters.'
2466
            );
2467
            return; // SHORT PASSWORD - NO FURTHER PROCESSING NEEDED!
2468
        }
2469
2470
        // Verify that the two password entry fields matched
2471
        if ($password1 != $password2) {
2472
            Util::setSessionVar('p12error', 'Passwords did not match.');
2473
            return; // MISMATCHED PASSWORDS - NO FURTHER PROCESSING NEEDED!
2474
        }
2475
2476
        // Set the port based on the Level of Assurance
2477
        $port = 7512;
2478
        $loa = Util::getSessionVar('loa');
2479
        if ($loa == 'http://incommonfederation.org/assurance/silver') {
2480
            $port = 7514;
2481
        } elseif ($loa == 'openid') {
2482
            $port = 7516;
2483
        }
2484
2485
        $dn = Util::getSessionVar('dn');
2486
        if (strlen($dn) > 0) {
2487
            // Append extra info, such as 'skin', to be processed by MyProxy
2488
            $myproxyinfo = Util::getSessionVar('myproxyinfo');
2489
            if (strlen($myproxyinfo) > 0) {
2490
                $dn .= " $myproxyinfo";
2491
            }
2492
            // Attempt to fetch a credential from the MyProxy server
2493
            $cert = MyProxy::getMyProxyCredential(
2494
                $dn,
2495
                '',
2496
                MYPROXY_HOST,
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\MYPROXY_HOST was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2497
                $port,
2498
                $lifetime,
2499
                '/var/www/config/hostcred.pem',
2500
                ''
2501
            );
2502
2503
            // The 'openssl pkcs12' command is picky in that the private
2504
            // key must appear BEFORE the public certificate. But MyProxy
2505
            // returns the private key AFTER. So swap them around.
2506
            $cert2 = '';
2507
            if (
2508
                preg_match(
2509
                    '/-----BEGIN CERTIFICATE-----([^-]+)' .
2510
                    '-----END CERTIFICATE-----[^-]*' .
2511
                    '-----BEGIN RSA PRIVATE KEY-----([^-]+)' .
2512
                    '-----END RSA PRIVATE KEY-----/',
2513
                    $cert,
2514
                    $match
2515
                )
2516
            ) {
2517
                $cert2 = "-----BEGIN RSA PRIVATE KEY-----" .
2518
                         $match[2] . "-----END RSA PRIVATE KEY-----\n" .
2519
                         "-----BEGIN CERTIFICATE-----" .
2520
                         $match[1] . "-----END CERTIFICATE-----";
2521
            }
2522
2523
            if (strlen($cert2) > 0) { // Successfully got a certificate!
2524
                // Create a temporary directory in /var/www/html/pkcs12/
2525
                $tdirparent = '/var/www/html/pkcs12/';
2526
                $polonum = '3';   // Prepend the polo? number to directory
2527
                if (preg_match('/(\d+)\./', php_uname('n'), $polomatch)) {
2528
                    $polonum = $polomatch[1];
2529
                }
2530
                $tdir = Util::tempDir($tdirparent, $polonum, 0770);
2531
                $p12dir = str_replace($tdirparent, '', $tdir);
2532
                $p12file = $tdir . '/usercred.p12';
2533
2534
                // Call the openssl pkcs12 program to convert certificate
2535
                exec('/bin/env ' .
2536
                     'RANDFILE=/tmp/.rnd ' .
2537
                     'CILOGON_PKCS12_PW=' . escapeshellarg($password1) . ' ' .
2538
                     '/usr/bin/openssl pkcs12 -export ' .
2539
                     '-passout env:CILOGON_PKCS12_PW ' .
2540
                     "-out $p12file " .
2541
                     '<<< ' . escapeshellarg($cert2));
2542
2543
                // Verify the usercred.p12 file was actually created
2544
                $size = @filesize($p12file);
2545
                if (($size !== false) && ($size > 0)) {
2546
                    $p12link = 'https://' . static::getMachineHostname() .
2547
                               '/pkcs12/' . $p12dir . '/usercred.p12';
2548
                    $p12 = (time() + 300) . " " . $p12link;
2549
                    Util::setSessionVar('p12', $p12);
2550
                    $log->info('Generated New User Certificate="' . $p12link . '"');
2551
                    //CIL-507 Special Log Message For XSEDE
2552
                    $log->info('USAGE email="' .
2553
                        Util::getSessionVar('emailaddr') . '" client="PKCS12"');
2554
                } else { // Empty or missing usercred.p12 file - shouldn't happen!
2555
                    Util::setSessionVar(
2556
                        'p12error',
2557
                        'Error creating certificate. Please try again.'
2558
                    );
2559
                    Util::deleteDir($tdir); // Remove the temporary directory
2560
                    $log->info('Error creating certificate - missing usercred.p12');
2561
                }
2562
            } else { // The myproxy-logon command failed - shouldn't happen!
2563
                Util::setSessionVar(
2564
                    'p12error',
2565
                    'Error! MyProxy unable to create certificate.'
2566
                );
2567
                $log->info('Error creating certificate - myproxy-logon failed');
2568
            }
2569
        } else { // Couldn't find the 'dn' PHP session value - shouldn't happen!
2570
            Util::setSessionVar(
2571
                'p12error',
2572
                'Missing username. Please enable cookies.'
2573
            );
2574
            $log->info('Error creating certificate - missing dn session variable');
2575
        }
2576
    }
2577
2578
    /**
2579
     * getLogOnButtonText
2580
     *
2581
     * This function checks the current skin to see if <logonbuttontext>
2582
     * has been configured.  If so, it returns that value.  Otherwise,
2583
     * it returns 'Log On'.
2584
     *
2585
     * @return string The text of the 'Log On' button for the WAYF, as
2586
     *         configured for the skin.  Defaults to 'Log On'.
2587
     */
2588
    public static function getLogOnButtonText()
2589
    {
2590
        $retval = 'Log On';
2591
        $lobt = Util::getSkin()->getConfigOption('logonbuttontext');
2592
        if (!is_null($lobt)) {
2593
            $retval = (string)$lobt;
2594
        }
2595
        return $retval;
2596
    }
2597
2598
    /**
2599
     * getSerialStringFromDN
2600
     *
2601
     * This function takes in a CILogon subject DN and returns just the
2602
     * serial string part (e.g., A325). This function is needed since the
2603
     * serial_string is not stored in the PHP session as a separate
2604
     * variable since it is always available in the 'dn' session variable.
2605
     *
2606
     * @param string $dn The certificate subject DN (typically found in the
2607
     *        session 'dn' variable)
2608
     * @return string The serial string extracted from the subject DN, or
2609
     *         empty string if DN is empty or wrong format.
2610
     */
2611
    public static function getSerialStringFromDN($dn)
2612
    {
2613
        $serial = ''; // Return empty string upon error
2614
2615
        // Strip off the email address, if present
2616
        $dn = preg_replace('/\s+email=.+$/', '', $dn);
2617
        // Find the 'CN=' entry
2618
        if (preg_match('%/DC=org/DC=cilogon/C=US/O=.*/CN=(.*)%', $dn, $match)) {
2619
            $cn = $match[1];
2620
            if (preg_match('/\s+([^\s]+)$/', $cn, $match)) {
2621
                $serial = $match[1];
2622
            }
2623
        }
2624
        return $serial;
2625
    }
2626
2627
    /**
2628
     * getEmailFromDN
2629
     *
2630
     * This function takes in a CILogon subject DN and returns just the
2631
     * email address part. This function is needed since the email address
2632
     * is not stored in the PHP session as a separate variable since it is
2633
     * always available in the 'dn' session variable.
2634
     *
2635
     * @param string $dn The certificate subject DN (typically found in the
2636
     *        session 'dn' variable)
2637
     * @return string The email address extracted from the subject DN, or
2638
     *         empty string if DN is empty or wrong format.
2639
     */
2640
    public static function getEmailFromDN($dn)
2641
    {
2642
        $email = ''; // Return empty string upon error
2643
        if (preg_match('/\s+email=(.+)$/', $dn, $match)) {
2644
            $email = $match[1];
2645
        }
2646
        return $email;
2647
    }
2648
2649
    /**
2650
     * reformatDN
2651
     *
2652
     * This function takes in a certificate subject DN with the email=...
2653
     * part already removed. It checks the skin to see if <dnformat> has
2654
     * been set. If so, it reformats the DN appropriately.
2655
     *
2656
     * @param string $dn The certificate subject DN (without the email=... part)
2657
     * @return string The certificate subject DN transformed according to
2658
     *         the value of the <dnformat> skin config option.
2659
     */
2660
    public static function reformatDN($dn)
2661
    {
2662
        $newdn = $dn;
2663
        $dnformat = (string)Util::getSkin()->getConfigOption('dnformat');
2664
        if (strlen($dnformat) > 0) {
2665
            if (
2666
                ($dnformat == 'rfc2253') &&
2667
                (preg_match(
2668
                    '%/DC=(.*)/DC=(.*)/C=(.*)/O=(.*)/CN=(.*)%',
2669
                    $dn,
2670
                    $match
2671
                ))
2672
            ) {
2673
                array_shift($match);
2674
                $m = array_reverse(Net_LDAP2_Util::escape_dn_value($match));
2675
                $newdn = "CN=$m[0],O=$m[1],C=$m[2],DC=$m[3],DC=$m[4]";
2676
            }
2677
        }
2678
        return $newdn;
2679
    }
2680
2681
    /**
2682
     * getMachineHostname
2683
     *
2684
     * This function is utilized in the formation of the URL for the
2685
     * PKCS12 credential download link and for the Shibboleth Single Sign-on
2686
     * session initiator URL. It returns a host-specific URL
2687
     * hostname by mapping the local machine hostname (as returned
2688
     * by 'uname -n') to an InCommon metadata cilogon.org hostname
2689
     * (e.g., polo2.cilogon.org). This function uses the HOSTNAME_ARRAY
2690
     * where the keys are the local machine hostname and
2691
     * the values are the external facing *.cilogon.org hostname.
2692
     * In case the local machine hostname cannot be found in the
2693
     * HOSTNAME_ARRAY, DEFAULT_HOSTNAME is returned.
2694
     *
2695
     * @param string $idp The entityID of the IdP used for potential
2696
     *        special handling (e.g., for Syngenta).
2697
     * @return string The full cilogon-specific hostname of this host.
2698
     */
2699
    public static function getMachineHostname($idp = '')
2700
    {
2701
        $retval = DEFAULT_HOSTNAME;
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\DEFAULT_HOSTNAME was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2702
        // CIL-439 For Syngenta, use just a single 'hostname' value to
2703
        // match their Active Directory configuration for CILogon's
2704
        // assertionConsumerService URL. Otherwise, map the local
2705
        // hostname to a *.cilogon.org domain name.
2706
        if ($idp != 'https://sts.windows.net/06219a4a-a835-44d5-afaf-3926343bfb89/') {
2707
            $localhost = php_uname('n');
2708
            if (array_key_exists($localhost, HOSTNAME_ARRAY)) {
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\HOSTNAME_ARRAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
2709
                $retval = HOSTNAME_ARRAY[$localhost];
2710
            }
2711
        }
2712
        return $retval;
2713
    }
2714
2715
    /**
2716
     * getCompositeIdPList
2717
     *
2718
     * This function generates a list of IdPs to display in the 'Select
2719
     * An Identity Provider' box on the main CILogon page or on the
2720
     * TestIdP page. For the main CILogon page, this is a filtered list of
2721
     * IdPs based on the skin's whitelist/blacklist and the global
2722
     * blacklist file. For the TestIdP page, the list is all InCommon IdPs.
2723
     *
2724
     * @return array A two-dimensional array where the primary key is the
2725
     *         entityID and the secondary key is either 'Display_Name'
2726
     *         or 'Organization_Name'.
2727
     */
2728
    public static function getCompositeIdPList()
2729
    {
2730
        $retarray = array();
2731
2732
        $idplist = Util::getIdpList();
2733
        $skin = Util::getSkin();
2734
2735
        // Check if the skin's config.xml has set the
2736
        // 'registeredbyincommonidps' option, which restricts the SAML-
2737
        // based IdPs to those with the <Registered_By_InCommon> tag.
2738
        // Otherwise, just get the SAML-based IdPs that have the
2739
        // <Whitelisted> tag. Note that the skin's <idpwhitelist>
2740
        // is still consulted in either case (below).
2741
        $registeredbyincommonidps = $skin->getConfigOption('registeredbyincommonidps');
2742
        if (
2743
            (!is_null($registeredbyincommonidps)) &&
2744
            ((int)$registeredbyincommonidps == 1)
2745
        ) {
2746
            $retarray = $idplist->getRegisteredByInCommonIdPs();
2747
        } else {
2748
            $retarray = $idplist->getWhitelistedIdPs();
2749
        }
2750
2751
        // Add all OAuth2 IdPs to the list
2752
        foreach (Util::$oauth2idps as $value) {
2753
            // CIL-617 Show OAuth2 IdPs only if client_id is configured
2754
            $client_id = constant(strtoupper($value) . '_OAUTH2_CLIENT_ID');
2755
            if (!empty($client_id)) {
2756
                $retarray[Util::getAuthzUrl($value)]['Organization_Name'] =
2757
                    $value;
2758
                $retarray[Util::getAuthzUrl($value)]['Display_Name'] =
2759
                    $value;
2760
            }
2761
        }
2762
2763
        // Check to see if the skin's config.xml has a whitelist of IDPs.
2764
        // If so, go thru master IdP list and keep only those IdPs in the
2765
        // config.xml's whitelist.
2766
        if ($skin->hasIdpWhitelist()) {
2767
            foreach ($retarray as $entityId => $names) {
2768
                if (!$skin->idpWhitelisted($entityId)) {
2769
                    unset($retarray[$entityId]);
2770
                }
2771
            }
2772
        }
2773
        // Next, check to see if the skin's config.xml has a blacklist of
2774
        // IdPs. If so, cull down the master IdP list removing 'bad' IdPs.
2775
        if ($skin->hasIdpBlacklist()) {
2776
            $idpblacklist = $skin->getConfigOption('idpblacklist');
2777
            foreach ($idpblacklist->idp as $blackidp) {
2778
                unset($retarray[(string)$blackidp]);
2779
            }
2780
        }
2781
2782
        // Fix for CIL-174 - As suggested by Keith Hazelton, replace commas and
2783
        // hyphens with just commas.
2784
        $regex = '/(University of California)\s*[,-]\s*/';
2785
        foreach ($retarray as $entityId => $names) {
2786
            if (preg_match($regex, $names['Organization_Name'])) {
2787
                $retarray[$entityId]['Organization_Name'] =
2788
                    preg_replace($regex, '$1, ', $names['Organization_Name']);
2789
            }
2790
            if (preg_match($regex, $names['Display_Name'])) {
2791
                $retarray[$entityId]['Display_Name'] =
2792
                    preg_replace($regex, '$1, ', $names['Display_Name']);
2793
            }
2794
        }
2795
2796
        // Re-sort the retarray by Display_Name for correct alphabetization.
2797
        uasort($retarray, function ($a, $b) {
2798
            return strcasecmp(
2799
                $a['Display_Name'],
2800
                $b['Display_Name']
2801
            );
2802
        });
2803
2804
        return $retarray;
2805
    }
2806
2807
    /**
2808
     * getIdphintList
2809
     *
2810
     * This function adds support for AARC-G049 "IdP Hinting". It
2811
     * searches both the GET query parameters and the OIDC client
2812
     * parameters passed to the 'authorize' endpoint for a parameter
2813
     * named either 'selected_idp' or 'idphint'. This parameter can be
2814
     * a single entityId or a comma-separated list of entityIds.
2815
     * The entries in the list are processed to remove any 'chained'
2816
     * idphints and also to transform OIDC 'issuer' values into
2817
     * CILogon-specific 'entityIds' as used in the 'Select an IdP'
2818
     * list. Any idps which are not in the current skin's 'Select
2819
     * an IdP' list are removed. The resulting processed list of
2820
     * entityIds is returned, which may be an empty array.
2821
     *
2822
     * @param array $idps (Optional) A list of valid (i.e., whitelisted) IdPs.
2823
     *        If this list is empty, then use the current skin's IdP list.
2824
     * @return array A list of entityIds / OIDC provider URLs extracted from
2825
     *         a passed-in parameter 'selected_idp' or 'idphint'. This array
2826
     *         may be empty if no such parameter was found, or if the
2827
     *         entityIds in the list were not valid.
2828
     */
2829
    public static function getIdphintList($idps = [])
2830
    {
2831
        // Check for either 'selected_idp' or 'idphint' parameter that was
2832
        // passed in via a query parameter, either for an OAuth transaction
2833
        // or just 'normally'. Note that if both 'selected_idp' and
2834
        // 'idphint' were passed, 'idphint' takes priority.
2835
2836
        $hintarray = array();
2837
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
2838
2839
        $hintstr = '';
2840
        if (!empty(@$clientparams['idphint'])) {
2841
            $hintstr = $clientparams['idphint'];
2842
        } elseif (!empty(Util::getGetVar('idphint'))) {
2843
            $hintstr = Util::getGetVar('idphint');
2844
        } elseif (!empty(@$clientparams['selected_idp'])) {
2845
            $hintstr = $clientparams['selected_idp'];
2846
        } elseif (!empty(Util::getGetVar('selected_idp'))) {
2847
            $hintstr = Util::getGetVar('selected_idp');
2848
        }
2849
2850
        if (!empty($hintstr)) {
2851
            // Split on comma to account for multiple idps
2852
            $hintarray = explode(',', $hintstr);
2853
2854
            // Process the list of IdPs to transform them appropriately.
2855
            foreach ($hintarray as &$value) {
2856
                // Check for 'chained' idp hints, and remove the GET params.
2857
                if (preg_match('%([^\?]*)\?%', $value, $matches)) {
2858
                    $value = $matches[1];
2859
                }
2860
                // Also, check for OIDC issuers and transform them into
2861
                // CILogon-specific values used in the 'Select an IdP' list.
2862
                if (preg_match('%https://accounts.google.com%', $value)) {
2863
                    $value = 'https://accounts.google.com/o/oauth2/auth';
2864
                } elseif (preg_match('%https://github.com%', $value)) {
2865
                    $value = 'https://github.com/login/oauth/authorize';
2866
                } elseif (preg_match('%https://orcid.org%', $value)) {
2867
                    $value = 'https://orcid.org/oauth/authorize';
2868
                }
2869
            }
2870
            unset($value); // Break the reference with the last element.
2871
2872
            // Remove any non-whitelisted IdPs from the hintarray.
2873
            if (empty($idps)) {
2874
                $idps = static::getCompositeIdPList();
2875
            }
2876
            foreach ($hintarray as $value) {
2877
                if (!isset($idps[$value])) {
2878
                    if (($key = array_search($value, $hintarray)) !== false) {
2879
                        unset($hintarray[$key]);
2880
                    }
2881
                }
2882
            }
2883
        }
2884
        return $hintarray;
2885
    }
2886
}
2887