Passed
Push — master ( b0f9dd...91df4b )
by Terrence
18:03 queued 03:06
created

Content::gotUserSuccess()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 47
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 7
eloc 22
c 2
b 0
f 0
nc 9
nop 0
dl 0
loc 47
ccs 0
cts 34
cp 0
crap 56
rs 8.6346
1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\Util;
6
use CILogon\Service\TwoFactor;
7
use CILogon\Service\MyProxy;
8
use CILogon\Service\PortalCookie;
9
use CILogon\Service\DBService;
10
use CILogon\Service\OAuth2Provider;
11
use CILogon\Service\Loggit;
12
use Net_LDAP2_Util;
13
14
// If needed, set the 'Notification' banner text to a non-empty value
15
// and uncomment the 'define' statement in order to display a
16
// notification box at the top of each page.
17
/*
18
define('BANNER_TEXT',
19
       'We are currently experiencing problems issuing certificates. We are
20
       working on a solution. We apologize for the inconvenience.'
21
);
22
*/
23
24
/**
25
 * Content
26
 */
27
class Content
28
{
29
    /**
30
     * printHeader
31
     *
32
     * This function should be called to print out the main HTML header
33
     * block for each web page.  This gives a consistent look to the site.
34
     * Any style changes should go in the cilogon.css file.
35
     *
36
     * @param string $title The text in the window's titlebar
37
     * @param string $extra Optional extra text to go in the <head> block
38
     * @param bool $csrfcookie Set the CSRF cookie. Defaults to true.
39
     */
40
    public static function printHeader($title = '', $extra = '', $csrfcookie = true)
41
    {
42
        if ($csrfcookie) {
43
            $csrf = Util::getCsrf();
44
            $csrf->setTheCookie();
45
        }
46
47
        // Find the 'Powered By CILogon' image if specified by the skin
48
        $poweredbyimg = "/images/poweredbycilogon.png";
49
        $skin = Util::getSkin();
50
        $skinpoweredbyimg = (string)$skin->getConfigOption('poweredbyimg');
51
        if (
52
            (!is_null($skinpoweredbyimg)) &&
0 ignored issues
show
introduced by
The condition is_null($skinpoweredbyimg) is always false.
Loading history...
53
            (strlen($skinpoweredbyimg) > 0) &&
54
            (is_readable('/var/www/html' . $skinpoweredbyimg))
55
        ) {
56
            $poweredbyimg = $skinpoweredbyimg;
57
        }
58
59
        echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
60
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
61
        <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
62
        <head><title>' , $title , '</title>
63
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
64
        <meta name="viewport" content="initial-scale=0.6" />
65
        <link rel="stylesheet" type="text/css" href="/include/cilogon.css" />
66
        ';
67
68
        $skin->printSkinLink();
69
70
        $deployjava = $skin->getConfigOption('deployjava');
71
        if ((!is_null($deployjava)) && ((int)$deployjava == 1)) {
72
            echo '<script type="text/javascript" src="/include/deployJava.js"></script>';
73
        }
74
75
        echo '
76
        <script type="text/javascript" src="/include/cilogon.js"></script>
77
        ' ;
78
79
        echo '
80
    <!--[if IE]>
81
        <style type="text/css">
82
          body { behavior: url(/include/csshover3.htc); }
83
        </style>
84
    <![endif]-->
85
        ';
86
87
        if (strlen($extra) > 0) {
88
            echo $extra;
89
        }
90
91
        echo '
92
        </head>
93
94
        <body>
95
96
        <div class="skincilogonlogo">
97
        <a target="_blank" href="http://www.cilogon.org/faq/"><img
98
        src="' , $poweredbyimg , '" alt="CILogon"
99
        title="CILogon Service" /></a>
100
        </div>
101
102
        <div class="logoheader">
103
           <h1><span>[CILogon Service]</span></h1>
104
        </div>
105
        <div class="pagecontent">
106
         ';
107
108
        if ((defined('BANNER_TEXT')) && (strlen(BANNER_TEXT) > 0)) {
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...
109
            echo '
110
            <div class="noticebanner">' , BANNER_TEXT , '</div>
111
            ';
112
        }
113
114
        $providerId = Util::getSessionVar('idp');
115
        if ($providerId == "urn:mace:incommon:idp.protectnetwork.org") {
116
            echo '
117
            <div class="noticebanner">Availability of the ProtectNetwork
118
            Identity Provider (IdP) will end after December 2014. Please
119
            consider using another IdP.</div>
120
            ';
121
        }
122
    }
123
124
    /**
125
     * printFooter
126
     *
127
     * This function should be called to print out the closing HTML block
128
     * for each web page.
129
     *
130
     * @param string $footer Optional extra text to be output before the
131
     * closing footer div.
132
     */
133
    public static function printFooter($footer = '')
134
    {
135
        if (strlen($footer) > 0) {
136
            echo $footer;
137
        }
138
139
        echo '
140
        <br class="clear" />
141
        <div class="footer">
142
        <a target="_blank" href="http://www.cilogon.org/faq"><img
143
        src="/images/questionIcon.png" class="floatrightclear"
144
        width="40" height="40" alt="CILogon FAQ" title="CILogon FAQ" /></a>
145
        <p>For questions about this site, please see the <a target="_blank"
146
        href="http://www.cilogon.org/faq">FAQs</a> or send email to <a
147
        href="mailto:[email protected]">help&nbsp;@&nbsp;cilogon.org</a>.</p>
148
        <p>Know <a target="_blank"
149
        href="http://ca.cilogon.org/responsibilities">your responsibilities</a>
150
        for using the CILogon Service.</p>
151
        <p>See <a target="_blank"
152
        href="http://ca.cilogon.org/acknowledgements">acknowledgements</a> of
153
        support for this site.</p>
154
        </div> <!-- Close "footer" div -->
155
        </div> <!-- Close "pagecontent" div -->
156
        </body>
157
        </html>
158
        ';
159
160
        session_write_close();
161
    }
162
163
    /**
164
     * printPageHeader
165
     *
166
     * This function prints a fancy formatted box with a single line of
167
     * text, suitable for a titlebox on each web page (to appear just below
168
     * the page banner at the very top). It prints a gradent border around
169
     * the four edges of the box and then outlines the inner box.
170
     *
171
     * @param string $text The text string to appear in the titlebox.
172
     */
173
    public static function printPageHeader($text)
174
    {
175
        echo '
176
        <div class="titlebox">' , $text , '
177
        </div>
178
        ';
179
    }
180
181
    /**
182
     * printFormHead
183
     *
184
     * This function prints out the opening <form> tag for displaying
185
     * submit buttons.  The first parameter is used for the 'action' value
186
     * of the <form>.  If omitted, getScriptDir() is called to get the
187
     * location of the current script.  This function outputs a hidden csrf
188
     * field in the form block.
189
     *
190
     * @param string $action (Optional) The value of the form's 'action'
191
     *        parameter. Defaults to getScriptDir().
192
     * @param string $method (Optional) The <form> 'method', one of 'get' or
193
     *        'post'. Defaults to 'post'.
194
     */
195
    public static function printFormHead(
196
        $action = '',
197
        $method = 'post'
198
    ) {
199
        static $formnum = 0;
200
201
        if (strlen($action) == 0) {
202
            $action = Util::getScriptDir();
203
        }
204
205
        echo '
206
        <form action="' , $action , '" method="' , $method , '"
207
         autocomplete="off" id="form' , sprintf("%02d", ++$formnum) , '">
208
        ';
209
        $csrf = Util::getCsrf();
210
        echo $csrf->hiddenFormElement();
211
    }
212
213
    /**
214
     * printWAYF
215
     *
216
     * This function prints the list of IdPs in a <select> form element
217
     * which can be printed on the main login page to allow the user to
218
     * select 'Where Are You From?'.  This function checks to see if a
219
     * cookie for the 'providerId' had been set previously, so that the
220
     * last used IdP is selected in the list.
221
     *
222
     * @param bool $showremember (Optional) Show the 'Remember this
223
     *        selection' checkbox? Defaults to true.
224
     * @param bool $samlidps (Optional) Show all SAML-based IdPs in
225
     *        selection list? Defaults to false, which means show
226
     *        only whitelisted IdPs.
227
     */
228
    public static function printWAYF($showremember = true, $samlidps = false)
229
    {
230
        $helptext = 'Check this box to bypass the welcome page on ' .
231
            'subsequent visits and proceed directly to the selected ' .
232
            'identity provider. You will need to clear your browser\'s ' .
233
            'cookies to return here.';
234
        $searchtext = "Enter characters to search for in the list above.";
235
236
        // Get an array of IdPs
237
        $idps = static::getCompositeIdPList($samlidps);
238
239
        $skin = Util::getSkin();
240
241
        // Check if the user had previously selected an IdP from the list.
242
        // First, check the portalcookie, then the 'normal' cookie.
243
        $keepidp = '';
244
        $providerId = '';
245
        $pc = new PortalCookie();
246
        $pn = $pc->getPortalName();
247
        if (strlen($pn) > 0) {
248
            $keepidp    = $pc->get('keepidp');
249
            $providerId = $pc->get('providerId');
250
        } else {
251
            $keepidp    = Util::getCookieVar('keepidp');
252
            $providerId = Util::getCookieVar('providerId');
253
        }
254
255
        // Make sure previously selected IdP is in list of available IdPs.
256
        if ((strlen($providerId) > 0) && (!isset($idps[$providerId]))) {
257
            $providerId = '';
258
        }
259
260
        // If no previous providerId, get from skin, or default to Google.
261
        if (strlen($providerId) == 0) {
262
            $initialidp = (string)$skin->getConfigOption('initialidp');
263
            if ((!is_null($initialidp)) && (isset($idps[$initialidp]))) {
0 ignored issues
show
introduced by
The condition is_null($initialidp) is always false.
Loading history...
264
                $providerId = $initialidp;
265
            } else {
266
                $providerId = Util::getAuthzUrl('Google');
267
            }
268
        }
269
270
        // Check if an OIDC client selected an IdP for the transaction.
271
        // If so, verify that the IdP is in the list of available IdPs.
272
        $useselectedidp = false;
273
        $idphintlist = static::getIdphintList($idps);
274
        if (!empty($idphintlist)) {
275
            $useselectedidp = true;
276
            $providerId = $idphintlist[0];
277
            // Update the IdP selection list to show just the idphintlist.
278
            foreach ($idphintlist as $value) {
279
                $newidps[$value] = $idps[$value];
280
            }
281
            $idps = $newidps;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newidps seems to be defined by a foreach iteration on line 278. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
282
            // Re-sort the $idps by Display_Name for correct alphabetization.
283
            uasort($idps, function ($a, $b) {
284
                return strcasecmp(
285
                    $a['Display_Name'],
286
                    $b['Display_Name']
287
                );
288
            });
289
        }
290
291
        echo '
292
        <br />
293
        <div class="actionbox"';
294
295
        if (Util::getSessionVar('showhelp') == 'on') {
296
            echo ' style="width:92%;"';
297
        }
298
299
        echo '>
300
        <table class="helptable">
301
        <tr>
302
        <td class="actioncell">
303
304
          <form action="' , Util::getScriptDir() , '" method="post">
305
          <fieldset>
306
307
          <p>' , ($useselectedidp ? 'Selected' : 'Select An') ,
308
          ' Identity Provider:</p>
309
          ';
310
311
        // See if the skin has set a size for the IdP <select> list
312
        $selectsize = 4;
313
        $ils = $skin->getConfigOption('idplistsize');
314
        if ((!is_null($ils)) && ((int)$ils > 0)) {
315
            $selectsize = (int)$ils;
316
        }
317
318
        // When selected_idp is used, list size may be smaller
319
        if ($useselectedidp) {
320
            $selectsize = min($selectsize, count($idps));
321
        }
322
323
        echo '
324
          <p>
325
          <select name="providerId" id="providerId" size="' , $selectsize , '"
326
           onkeypress="enterKeySubmit(event)" ondblclick="doubleClickSubmit()"' ,
327
           // Hide the drop-down arrow in Firefox and Chrome
328
          ($useselectedidp ?
329
              'style="-moz-appearance:none;-webkit-appearance:none"' : '') ,
330
           '>
331
        ';
332
333
        foreach ($idps as $entityId => $names) {
334
            echo '    <option value="' , $entityId , '"';
335
            if ($entityId == $providerId) {
336
                echo ' selected="selected"';
337
            }
338
            echo '>' , Util::htmlent($names['Display_Name']) , '</option>' , "\n    ";
339
        }
340
341
        echo '  </select>
342
        </p>
343
344
        <p id="listsearch" class="zeroheight">
345
        <label for="searchlist" class="helpcursor" title="' ,
346
        $searchtext , '">Search:</label>
347
        <input type="text" name="searchlist" id="searchlist" value=""
348
        size="30" onkeyup="searchOptions(this.value)"
349
        title="' , $searchtext , '" />
350
    <!--[if IE]><input type="text" style="display:none;" disabled="disabled" size="1"/><![endif]-->
351
        </p>
352
        ';
353
354
        if ($showremember) {
355
            echo '
356
            <p>
357
            <label for="keepidp" title="' , $helptext ,
358
            '" class="helpcursor">Remember this selection:</label>
359
            <input type="checkbox" name="keepidp" id="keepidp" ' ,
360
            ((strlen($keepidp) > 0) ? 'checked="checked" ' : '') ,
361
            'title="' , $helptext , '" class="helpcursor" />
362
            </p>
363
            ';
364
        }
365
366
        echo '
367
        <p class="silvercheckbox">
368
        <label for="silveridp">Request Silver:</label>
369
        <input type="checkbox" name="silveridp" id="silveridp"/>
370
        </p>
371
372
        <p>
373
        ';
374
375
        echo Util::getCsrf()->hiddenFormElement();
376
377
        $lobtext = static::getLogOnButtonText();
378
379
        echo '
380
        <input type="submit" name="submit" class="submit helpcursor"
381
        title="Continue to the selected identity provider."
382
        value="' , $lobtext , '" id="wayflogonbutton" />
383
        <input type="hidden" name="previouspage" value="WAYF" />
384
        <input type="submit" name="submit" class="submit helpcursor"
385
        title="Cancel authentication and navigate away from this site."
386
        value="Cancel" id="wayfcancelbutton" />
387
        </p>
388
        ';
389
390
        $logonerror = Util::getSessionVar('logonerror');
391
        if (strlen($logonerror) > 0) {
392
            echo "<p class=\"logonerror\">$logonerror</p>";
393
            Util::unsetSessionVar('logonerror');
394
        }
395
396
        echo '
397
        <p class="privacypolicy">
398
        By selecting "' , $lobtext , '", you agree to <a target="_blank"
399
        href="http://ca.cilogon.org/policy/privacy">CILogon\'s privacy
400
        policy</a>.
401
        </p>
402
403
        </fieldset>
404
405
        </form>
406
      </td>
407
      ';
408
409
        if (Util::getSessionVar('showhelp') == 'on') {
410
            echo '
411
          <td class="helpcell">
412
          <div>
413
          ';
414
415
            if ($samlidps) { // SAML-based IdPs only means running from /testidp/
416
                echo '
417
                <p>
418
                CILogon facilitates secure access to CyberInfrastructure
419
                (<acronym title="CyberInfrastructure">CI</acronym>). In
420
                order to test your identity provider with the CILogon Service,
421
                you must first Log On. If your preferred identity provider is
422
                not listed, please contact <a
423
                href="mailto:[email protected]">[email protected]</a>, and
424
                we will try to add your identity provider in the future.
425
                </p>
426
                ';
427
            } else { // If not InCommon only, print help text for OpenID providers.
428
                echo '
429
                <p>
430
                CILogon facilitates secure access to CyberInfrastructure
431
                (<acronym title="CyberInfrastructure">CI</acronym>).
432
                In order to use the CILogon Service, you must first select
433
                an identity provider. An identity provider (IdP) is an
434
                organization where you have an account and can log on
435
                to gain access to online services.
436
                </p>
437
                <p>
438
                If you are a faculty, staff, or student member of a university
439
                or college, please select it for your identity provider.
440
                If your school is not listed, please contact <a
441
                href="mailto:[email protected]">[email protected]</a>, and we will
442
                try to add your school in the future.
443
                </p>
444
                ';
445
446
                $googleauthz = Util::getAuthzUrl('Google');
447
                if (
448
                    (isset($idps[$googleauthz])) &&
449
                    ($skin->idpAvailable($googleauthz))
450
                ) {
451
                    echo '
452
                  <p>
453
                  If you have a <a target="_blank"
454
                  href="https://myaccount.google.com">Google</a>
455
                  account, you can select it for
456
                  authenticating to the CILogon Service.
457
                  </p>
458
                  ';
459
                }
460
                $githubauthz = Util::getAuthzUrl('GitHub');
461
                if (
462
                    (isset($idps[$githubauthz])) &&
463
                    ($skin->idpAvailable($githubauthz))
464
                ) {
465
                    echo '
466
                  <p>
467
                  If you have a <a target="_blank"
468
                  href="https://github.com/settings/profile">GitHub</a>
469
                  account, you can select it for
470
                  authenticating to the CILogon Service.
471
                  </p>
472
                  ';
473
                }
474
                $orcidauthz = Util::getAuthzUrl('ORCID');
475
                if (
476
                    (isset($idps[$orcidauthz])) &&
477
                    ($skin->idpAvailable($orcidauthz))
478
                ) {
479
                    echo '
480
                  <p>
481
                  If you have a <a target="_blank"
482
                  href="https://orcid.org/my-orcid">ORCID</a>
483
                  account, you can select it for
484
                  authenticating to the CILogon Service.
485
                  </p>
486
                  ';
487
                }
488
            }
489
490
            echo '
491
          </div>
492
          </td>
493
          ';
494
        }
495
        echo '
496
      </tr>
497
      </table>
498
      </div>
499
      ';
500
    }
501
502
    /**
503
     * printTwoFactorBox
504
     *
505
     * This function prints the 'Manage Two-Factor' box on the main page.
506
     */
507
    public static function printTwoFactorBox()
508
    {
509
        $managetwofactortext = 'Enable or disable two-factor authentication for your account';
510
511
        echo '
512
        <div class="twofactoractionbox"';
513
514
        $style = ''; // Might add extra CSS to the twofactoractionbox
515
        if (Util::getSessionVar('showhelp') == 'on') {
516
            $style .= "width:92%;";
517
        }
518
        if (TwoFactor::getEnabled() != 'none') {
519
            $style .= "display:block;"; // Force display if two-factor enabled
520
        }
521
        if (strlen($style) > 0) {
522
            echo ' style="' , $style , '"';
523
        }
524
525
        echo '>
526
        <table class="helptable">
527
        <tr>
528
        <td class="actioncell">
529
        ';
530
531
        static::printFormHead();
532
533
        $twofactorname = TwoFactor::getEnabledName();
534
        if ($twofactorname == 'Disabled') {
535
            $twofactorname = 'Two-Factor Authentication Disabled';
536
        } else {
537
            $twofactorname .= ' Enabled';
538
        }
539
        echo '
540
          <p>' , $twofactorname , '</p>
541
542
          <p>
543
          <input type="submit" name="submit" class="submit helpcursor"
544
          title="' , $managetwofactortext , '" value="Manage Two-Factor" />
545
          </p>
546
          </form>
547
        </td>
548
        ';
549
550
        if (Util::getSessionVar('showhelp') == 'on') {
551
            echo '
552
            <td class="helpcell">
553
            <div>
554
            <p>
555
            Two-factor authentication provides extra security on your account by
556
            using a physical device (e.g., your mobile phone) to generate a one
557
            time password which you enter after you log in to your selected
558
            Identity Provider. Click the "Manage Two-Factor" button to enable or
559
            disable two-factor authentication.
560
            </p>
561
            </div>
562
            </td>
563
            ';
564
        }
565
566
        echo '
567
        </tr>
568
        </table>
569
        </div> <!-- twofactoractionbox -->
570
        ';
571
    }
572
573
    /**
574
     * printTwoFactorPage
575
     *
576
     * This function prints out the Manage Two-Factor Authentication page.
577
     * Display of which two-factor types are available to the user is
578
     * controlled by CSS. From this page, the user can Enable or Disable
579
     * various two-factor authentication methods.
580
     */
581
    public static function printTwoFactorPage()
582
    {
583
        Util::setSessionVar('stage', 'managetwofactor'); // For Show/Hide Help button
584
585
        static::printHeader('Manage Two-Factor Authentication');
586
587
        $twofactorname = TwoFactor::getEnabledName();
588
589
        echo '
590
        <div class="boxed">
591
        ';
592
        static::printHelpButton();
593
        echo'
594
        <h2>Two-Factor Authentication</h2>
595
        <div class="actionbox">
596
        <p><b>Two-Factor Authentication:</b></p>
597
        <p>' , $twofactorname , '</p>
598
        </div> <!-- actionbox -->
599
        ';
600
601
        if (Util::getSessionVar('showhelp') == 'on') {
602
            echo '
603
            <div>
604
            <p>
605
            Multi-factor authentication requires a user to present two or more
606
            distinct authentication factors from the following categories:
607
            </p>
608
            <ul>
609
            <li>Something you <b>know</b> (e.g., username and password)</li>
610
            <li>Something you <b>have</b> (e.g., mobile phone or hardware
611
                token)</li>
612
            <li>Something you <b>are</b> (e.g., fingerprint)</li>
613
            </ul>
614
            <p>
615
            Below you can configure a second factor using something you <b>have</b>,
616
            i.e., your mobile phone. You will first be prompted to register the
617
            second-factor device, typically by installing specific apps on your
618
            phone and completing a registration process. Then you will need to log
619
            in with the second factor to verify the registration process.
620
            </p>
621
            <p>
622
            Once you have successfully enabled two-factor authentication, you will
623
            be prompted on future CILogon Service logons for your second-factor
624
            authentication.  You can select the second-factor authentication to use
625
            (or not) by clicking the "Enable" (or "Disable") button.
626
            </p>
627
            </div>
628
            ';
629
        }
630
631
        echo '
632
        <table class="twofactortypes">
633
        <tr class="twofactorgooglerow"' ,
634
        (TwoFactor::isEnabled('ga') ? ' style="display:table-row;"' : '') ,
635
        '>
636
        <th>Google Authenticator</th>
637
        <td>
638
        ';
639
        static::printFormHead();
640
        echo '
641
        <input type="hidden" name="twofactortype" value="ga" />
642
        <input type="submit" name="submit" class="submit" value="' ,
643
        (TwoFactor::isEnabled('ga') ? 'Disable' : 'Enable') ,
644
        '" />
645
        </form>
646
        </td>
647
        </tr>
648
        ';
649
650
        if (Util::getSessionVar('showhelp') == 'on') {
651
            echo '
652
            <tr>
653
            <td colspan="4">
654
            Google Authenticator is an app available for Android OS, Apple
655
            iOS, and BlackBerry OS. The app generates one-time password tokens.
656
            After you have logged on to the CILogon Service with your chosen
657
            Identity Provider, you would be prompted to use the Google
658
            Authenticator app to generate a second passcode and enter it.
659
            </td>
660
            </tr>
661
            ';
662
        }
663
664
        echo '
665
        <tr class="twofactorduorow"' ,
666
        (TwoFactor::isEnabled('duo') ? '
667
            style="display:table-row;border-top-width:1px"' : '') ,
668
        '>
669
        <th>Duo Security</th>
670
        <td>
671
        ';
672
        static::printFormHead();
673
        echo '
674
        <input type="hidden" name="twofactortype" value="duo" />
675
        <input type="submit" name="submit" class="submit" value="' ,
676
        (TwoFactor::isEnabled('duo') ? 'Disable' : 'Enable') ,
677
        '" />
678
        </form>
679
        </td>
680
        </tr>
681
        ';
682
683
        if (Util::getSessionVar('showhelp') == 'on') {
684
            echo '
685
            <tr>
686
            <td colspan="4">
687
            Duo Security is an app available for most smartphones, including
688
            Android OS, Apple iOS, Blackberry OS, Palm, Symbian, and Windows
689
            Mobile. The app can respond to "push" notifications, and can also
690
            generate one-time password tokens. After you have logged on to the
691
            CILogon Service with your chosen Identity Provider, you would be
692
            prompted to select a Duo log in method and then authenticate with
693
            your mobile phone.
694
            </td>
695
            </tr>
696
            ';
697
        }
698
699
        echo '
700
        </table>
701
702
        <noscript>
703
        <div class="nojs smaller">
704
        Javascript is disabled. In order to activate the link
705
        below, please enable Javascript in your browser.
706
        </div>
707
        </noscript>
708
709
        <p>
710
        <a href="javascript:showHideDiv(\'lostdevice\',-1)">Lost your phone?</a>
711
        </p>
712
        <div id="lostdevice" style="display:none">
713
        <p>
714
        If you have lost your phone, you can click on the
715
        "I Lost My Phone" button below to remove all two-factor methods
716
        from your account.  This will send an email message to the system
717
        adminisrator and to the email address provided by your Identity
718
        Provider.  You can then use the CILogon Service without
719
        two-factor authentication enabled. You will need to re-register your
720
        device with you want to use it for two-factor authentication again.
721
        <br/>
722
        ';
723
        static::printFormHead();
724
        echo '
725
        <input type="submit" name="submit" class="submit"
726
        value="I Lost My Phone" />
727
        </p>
728
        </div>
729
730
        <p>
731
        <input type="submit" name="submit" class="submit"
732
        value="Done with Two-Factor" />
733
        </p>
734
        </form>
735
736
        </div> <!-- boxed -->
737
        ';
738
        static::printFooter();
739
    }
740
741
    /**
742
     * handleEnableDisableTwoFactor
743
     *
744
     * This function is called when the user clicks either an 'Enable' or
745
     * 'Disable' button from the Manage Two-Factor page, or when the user
746
     * clicks 'Verify' on the Google Authenticator Registration page.
747
     * The passed-in parameter tells which type of button was pressed.
748
     * If 'Disable', then simply set 'enabled=none' in the datastore and
749
     * display the Manage Two-Factor page again. If 'Enable' or 'Verify',
750
     * check the 'twofactortype' hidden form variable for which two-factor
751
     * authentication method is to be enabled. Then print out that
752
     * two-factor page. Note that TwoFactor::printPage() does the work of
753
     * figuring out if the user has registered the phone yet or not, and
754
     * displays the appropriate page.
755
     *
756
     * @param bool $enable (Optional) True for 'enable', false for 'disable'.
757
     *        Defaults to false (for 'disable').
758
     */
759
    public static function handleEnableDisableTwoFactor($enable = false)
760
    {
761
        if ($enable) {
762
            $twofactortype = Util::getPostVar('twofactortype');
763
            if (strlen($twofactortype) > 0) {
764
                TwoFactor::printPage($twofactortype);
765
            } else {
766
                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

766
                /** @scrutinizer ignore-call */ 
767
                printLogonPage();
Loading history...
767
            }
768
        } else { // 'Disable' clicked
769
            // Check if the user clicked 'Disable Two-Factor' and send email
770
            if (Util::getPostVar('missingphone') == '1') {
771
                // Make sure two-factor was enabled
772
                $twofactorname = TwoFactor::getEnabledName();
773
                if ($twofactorname != 'Disabled') {
774
                    $email = static::getEmailFromDN(Util::getSessionVar('dn'));
775
                    if (strlen($email) > 0) { // Make sure email address exists
776
                        TwoFactor::sendPhoneAlert(
777
                            'Forgot Phone for Two-Factor Authentication',
778
                            'While using the CILogon Service, you (or someone using your account)
779
indicated that you forgot your phone by clicking the "Disable Two-Factor"
780
button. This disabled two-factor authentication by "' . $twofactorname . '"
781
using "' . Util::getSessionVar('idpname') . '" as your Identity Provider.
782
783
If you did not disable two-factor authentication, please send email to
784
"[email protected]" to report this incident.',
785
                            $email
786
                        );
787
                    } else { // No email address is bad - send error alert
788
                        Util::sendErrorAlert(
789
                            'Missing Email Address',
790
                            'When attempting to send an email notification to a user who clicked the
791
"Disable Two-Factor" button because of a forgotten phone, the CILogon
792
Service was unable to find an email address. This should never occur and
793
is probably due to a badly formatted "dn" string.'
794
                        );
795
                    }
796
                }
797
            }
798
799
            // Finally, disable two-factor authentication
800
            TwoFactor::setDisabled();
801
            TwoFactor::write();
802
            static::printTwoFactorPage();
803
        }
804
    }
805
806
    /**
807
     * handleILostMyPhone
808
     *
809
     * This function is called when the user clicks the 'I Lost My Phone'
810
     * button.  It sends email to the user AND to alerts because Duo
811
     * Security requires that a sysadmin unregister the phone for the user.
812
     * It then unsets the 'twofactor' session variable, and writes it to
813
     * the datastore, effectively wiping out all two-factor information for
814
     * the user.
815
     */
816
    public static function handleILostMyPhone()
817
    {
818
        // First, send email to user
819
        $email = static::getEmailFromDN(Util::getSessionVar('dn'));
820
        if (strlen($email) > 0) { // Make sure email address exists
821
            TwoFactor::sendPhoneAlert(
822
                'Lost Phone for Two-Factor Authentication',
823
                'While using the CILogon Service, you (or someone using your account)
824
indicated that you lost your phone by clicking the "I Lost My Phone"
825
button. This removed two-factor authentication for your account when
826
using "' . Util::getSessionVar('idpname') . '" as your Identity Provider.
827
828
System administrators have been notified of this incident. If you require
829
further assistance, please send email to "[email protected]".',
830
                $email
831
            );
832
        } else { // No email address is bad - send error alert
833
            Util::sendErrorAlert(
834
                'Missing Email Address',
835
                'When attempting to send an email notification to a user who clicked the
836
"I Lost My Phone" button, the CILogon Service was unable to find an
837
email address. This should never occur and is probably due to a badly
838
formatted "dn" string.'
839
            );
840
        }
841
842
        // Next, send email to sysadmin
843
        $errortext = 'A user clicked the "I Lost My Phone" button. ';
844
        if (TwoFactor::isRegistered('duo')) {
845
            $duoconfig = new DuoConfig();
846
            $errortext .= '
847
848
The user had registered "Duo Security" as one of the two-factor methods.
849
Since there is no way for the CILogon Service to UNregister this method
850
at the Duo Security servers, a system administrator will need to delete
851
this user\'s registration at https://' . $duoconfig->param['host'] . ' .';
852
        }
853
        Util::sendErrorAlert('Two-Factor Authentication Disabled', $errortext);
854
855
        // Finally, disable and unregister two-factor authentication
856
        Util::unsetSessionVar('twofactor');
857
        TwoFactor::write();
858
        static::printTwoFactorPage();
859
    }
860
861
    /**
862
     * handleGoogleAuthenticatorLogin
863
     *
864
     * This function is called when the user enters a one time password as
865
     * generated by the Google Authenticator app. This can occur (1) when
866
     * the user is first configuring GA two-factor and (2) when the user
867
     * logs in to the CILogon Service and GA is enabled. If the OTP is
868
     * correctly validated, the gotUserSuccess() function is called to
869
     * show output to the user.
870
     */
871
    public static function handleGoogleAuthenticatorLogin()
872
    {
873
        $gacode = Util::getPostVar('gacode');
874
        if ((strlen($gacode) > 0) && (TwoFactor::isGACodeValid($gacode))) {
875
            static::gotUserSuccess();
876
        } else {
877
            TwoFactor::printPage('ga');
878
        }
879
    }
880
881
    /**
882
     * handleDuoSecurityLogin
883
     *
884
     * This function is called when the user authenticates with Duo
885
     * Security. If the Duo authentication is valid, then the
886
     * gotUserSuccess() function is then called to show output to the user.
887
     */
888
    public static function handleDuoSecurityLogin()
889
    {
890
        $sig_response = Util::getPostVar('sig_response');
891
        if (
892
            (strlen($sig_response) > 0) &&
893
            (TwoFactor::isDuoCodeValid($sig_response))
894
        ) {
895
            static::gotUserSuccess();
896
        } else {
897
            TwoFactor::printPage('duo');
898
        }
899
    }
900
901
    /**
902
     * handleLogOnButtonClicked
903
     *
904
     * This function is called when the user clicks the 'Log On' button
905
     * on the IdP selection page. It checks to see if the 'Remember this
906
     * selection' checkbox was checked and sets a cookie appropriately. It
907
     * also sets a cookie 'providerId' so the last chosen IdP will be
908
     * selected the next time the user visits the site. The function then
909
     * calls the appropriate 'redirectTo...' function to send the user
910
     * to the chosen IdP.
911
     */
912
    public static function handleLogOnButtonClicked()
913
    {
914
        // Get the list of currently available IdPs
915
        $idps = static::getCompositeIdPList();
916
917
        // Set the cookie for keepidp if the checkbox was checked
918
        $pc = new PortalCookie();
919
        $pn = $pc->getPortalName();
920
        if (strlen(Util::getPostVar('keepidp')) > 0) {
921
            if (strlen($pn) > 0) {
922
                $pc->set('keepidp', 'checked');
923
            } else {
924
                Util::setCookieVar('keepidp', 'checked');
925
            }
926
        } else {
927
            if (strlen($pn) > 0) {
928
                $pc->set('keepidp', '');
929
            } else {
930
                Util::unsetCookieVar('keepidp');
931
            }
932
        }
933
934
        // Get the user-chosen IdP from the posted form
935
        $providerId = Util::getPostVar('providerId');
936
937
        // Set the cookie for the last chosen IdP and redirect to it if in list
938
        if ((strlen($providerId) > 0) && (isset($idps[$providerId]))) {
939
            if (strlen($pn) > 0) {
940
                $pc->set('providerId', $providerId);
941
                $pc->write();
942
            } else {
943
                Util::setCookieVar('providerId', $providerId);
944
            }
945
            $providerName = Util::getAuthzIdP($providerId);
946
            if (in_array($providerName, Util::$oauth2idps)) {
947
                // Log in with an OAuth2 IdP
948
                static::redirectToGetOAuth2User($providerId);
949
            } else { // Use InCommon authn
950
                static::redirectToGetShibUser($providerId);
951
            }
952
        } else { // IdP not in list, or no IdP selected
953
            if (strlen($pn) > 0) {
954
                $pc->set('providerId', '');
955
                $pc->write();
956
            } else {
957
                Util::unsetCookieVar('providerId');
958
            }
959
            Util::setSessionVar('logonerror', 'Please select a valid IdP.');
960
            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

960
            /** @scrutinizer ignore-call */ 
961
            printLogonPage();
Loading history...
961
        }
962
    }
963
964
    /**
965
     * handleHelpButtonClicked
966
     *
967
     * This function is called when the user clicks on the 'Show Help' /
968
     * 'Hide Help' button in the upper right corner of the page. It toggles
969
     * the 'showhelp' session variable and redisplays the appropriate page
970
     * with help now shown or hidden.
971
     */
972
    public static function handleHelpButtonClicked()
973
    {
974
        if (Util::getSessionVar('showhelp') == 'on') {
975
            Util::unsetSessionVar('showhelp');
976
        } else {
977
            Util::setSessionVar('showhelp', 'on');
978
        }
979
980
        $stage = Util::getSessionVar('stage');
981
        if (static::verifyCurrentUserSession()) {
982
            if ($stage == 'main') {
983
                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

983
                /** @scrutinizer ignore-call */ 
984
                printMainPage();
Loading history...
984
            } elseif ($stage == 'managetwofactor') {
985
                static::printTwoFactorPage();
986
            } else {
987
                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

987
                /** @scrutinizer ignore-call */ 
988
                printLogonPage();
Loading history...
988
            }
989
        } else {
990
            printLogonPage();
991
        }
992
    }
993
994
    /**
995
     * handleNoSubmitButtonClicked
996
     *
997
     * This function is the 'default' case when no 'submit' button has been
998
     * clicked, or if the submit session variable is not set. It checks
999
     * to see if either the <forceinitialidp> option is set, or if the
1000
     * 'Remember this selection' checkbox was previously checked. If so,
1001
     * then rediret to the appropriate IdP. Otherwise, print the main
1002
     * Log On page.
1003
     */
1004
    public static function handleNoSubmitButtonClicked()
1005
    {
1006
        $providerId = '';
1007
        $keepidp = '';
1008
        $selected_idp = '';
1009
        $redirect_uri = '';
1010
        $client_id = '';
1011
        $callbackuri = Util::getSessionVar('callbackuri');
1012
        $readidpcookies = true;  // Assume config options are not set
1013
        $skin = Util::getSkin();
1014
        $forceinitialidp = (int)$skin->getConfigOption('forceinitialidp');
1015
        $initialidp = (string)$skin->getConfigOption('initialidp');
1016
1017
        // If this is a OIDC transaction, get the redirect_uri and
1018
        // client_id parameters from the session var clientparams.
1019
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
1020
        if (isset($clientparams['redirect_uri'])) {
1021
            $redirect_uri = $clientparams['redirect_uri'];
1022
        }
1023
        if (isset($clientparams['client_id'])) {
1024
            $client_id = $clientparams['client_id'];
1025
        }
1026
1027
        // Use the first element of the idphint list as the selected_idp.
1028
        $idphintlist = static::getIdphintList();
1029
        if (!empty($idphintlist)) {
1030
            $selected_idp = $idphintlist[0];
1031
        }
1032
1033
        // CIL-431 - If the OAuth2/OIDC $redirect_uri or $client_id is set,
1034
        // then check for a match in the 'bypass.txt' file to see if we
1035
        // should automatically redirect to a specific IdP. Used mainly
1036
        // by campus gateways.
1037
        if ((strlen($redirect_uri) > 0) || (strlen($client_id) > 0)) {
1038
            $bypassidp = '';
1039
            $bypassarray = Util::readArrayFromFile(
1040
                Util::getServerVar('DOCUMENT_ROOT') . '/include/bypass.txt'
1041
            );
1042
            foreach ($bypassarray as $key => $value) {
1043
                if (
1044
                    (preg_match($key, $redirect_uri)) ||
1045
                    (preg_match($key, $client_id))
1046
                ) {
1047
                    $bypassidp = $value;
1048
                    break;
1049
                }
1050
            }
1051
            if (strlen($bypassidp) > 0) { // Match found!
1052
                $providerId = $bypassidp;
1053
                $keepidp = 'checked';
1054
                // To skip the next code blocks, unset a few variables.
1055
                $forceinitialidp = 0;     // Skip checking this option
1056
                $selected_idp = '';       // Skip any passed-in option
1057
                $readidpcookies = false;  // Don't read in the IdP cookies
1058
            }
1059
        }
1060
1061
        // If the <forceinitialidp> option is set, use either the
1062
        // <initialidp> or the selected_idp as the providerId, and use
1063
        // <forceinitialidp> as keepIdp. Otherwise, read the cookies
1064
        // 'providerId' and 'keepidp'.
1065
        if (
1066
            ($forceinitialidp == 1) &&
1067
            ((strlen($initialidp) > 0) || (strlen($selected_idp) > 0))
1068
        ) {
1069
            // If the <allowforceinitialidp> option is set, then make sure
1070
            // the callback / redirect uri is in the portal list.
1071
            $afii = $skin->getConfigOption('portallistaction', 'allowforceinitialidp');
1072
            if (
1073
                (is_null($afii)) || // Option not set, no need to check portal list
1074
                (((int)$afii == 1) &&
1075
                  (($skin->inPortalList($redirect_uri)) ||
1076
                   ($skin->inPortalList($client_id)) ||
1077
                   ($skin->inPortalList($callbackuri))))
1078
            ) {
1079
                // 'selected_idp' takes precedence over <initialidp>
1080
                if (strlen($selected_idp) > 0) {
1081
                    $providerId = $selected_idp;
1082
                } else {
1083
                    $providerId = $initialidp;
1084
                }
1085
                $keepidp = $forceinitialidp;
1086
                $readidpcookies = false; // Don't read in the IdP cookies
1087
            }
1088
        }
1089
1090
        // <initialidp> options not set, or portal not in portal list?
1091
        // Get idp and 'Remember this selection' from cookies instead.
1092
        $pc = new PortalCookie();
1093
        $pn = $pc->getPortalName();
1094
        if ($readidpcookies) {
1095
            // Check the portalcookie first, then the 'normal' cookies
1096
            if (strlen($pn) > 0) {
1097
                $keepidp    = $pc->get('keepidp');
1098
                $providerId = $pc->get('providerId');
1099
            } else {
1100
                $keepidp    = Util::getCookieVar('keepidp');
1101
                $providerId = Util::getCookieVar('providerId');
1102
            }
1103
        }
1104
1105
        // If both 'keepidp' and 'providerId' were set (and the
1106
        // providerId is a whitelisted IdP or valid OpenID provider),
1107
        // then skip the Logon page and proceed to the appropriate
1108
        // getuser script.
1109
        if ((strlen($providerId) > 0) && (strlen($keepidp) > 0)) {
1110
            // If selected_idp was specified at the OIDC authorize endpoint,
1111
            // make sure that it matches the saved providerId. If not,
1112
            // then show the Logon page and uncheck the keepidp checkbox.
1113
            if ((strlen($selected_idp) == 0) || ($selected_idp == $providerId)) {
1114
                $providerName = Util::getAuthzIdP($providerId);
1115
                if (in_array($providerName, Util::$oauth2idps)) {
1116
                    // Log in with an OAuth2 IdP
1117
                    static::redirectToGetOAuth2User($providerId);
1118
                } elseif (Util::getIdpList()->exists($providerId)) {
1119
                    // Log in with InCommon
1120
                    static::redirectToGetShibUser($providerId);
1121
                } else { // $providerId not in whitelist
1122
                    if (strlen($pn) > 0) {
1123
                        $pc->set('providerId', '');
1124
                        $pc->write();
1125
                    } else {
1126
                        Util::unsetCookieVar('providerId');
1127
                    }
1128
                    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

1128
                    /** @scrutinizer ignore-call */ 
1129
                    printLogonPage();
Loading history...
1129
                }
1130
            } else { // selected_idp does not match saved providerId
1131
                if (strlen($pn) > 0) {
1132
                    $pc->set('keepidp', '');
1133
                    $pc->write();
1134
                } else {
1135
                    Util::unsetCookieVar('keepidp');
1136
                }
1137
                printLogonPage();
1138
            }
1139
        } else { // One of providerId or keepidp was not set
1140
            printLogonPage();
1141
        }
1142
    }
1143
1144
    /**
1145
     * printIcon
1146
     *
1147
     * This function prints out the HTML for the little icons which can
1148
     * appear inline with other information.  This is accomplished via the
1149
     * use of wrapping the image in a <span> tag.
1150
     *
1151
     * @param string $icon The prefix of the '...Icon.png' image to be
1152
     *        shown. E.g., to show 'errorIcon.png', pass in 'error'.
1153
     * @param string $popuptext (Optionals) The popup 'title' text to be
1154
     *        displayed when the  mouse cursor hovers over the icon.
1155
     *        Defaults to empty string.
1156
     * @param string $class (Optionals) A CSS class for the icon. Will be
1157
     *        appended after the 'helpcursor' class. Defaults to empty
1158
     *        string.
1159
     */
1160
    public static function printIcon($icon, $popuptext = '', $class = '')
1161
    {
1162
        echo '<span';
1163
        if (strlen($popuptext) > 0) {
1164
            echo ' class="helpcursor ' , $class , '" title="' , $popuptext , '"';
1165
        }
1166
        echo '>&nbsp;<img src="/images/' , $icon , 'Icon.png"
1167
              alt="&laquo; ' , ucfirst($icon) , '"
1168
              width="14" height="14" /></span>';
1169
    }
1170
1171
    /**
1172
     * printHelpButton
1173
     *
1174
     * This function prints the 'Show Help' / 'Hide Help' button in the
1175
     * upper-right corner of the main box area on the page.
1176
     */
1177
    public static function printHelpButton()
1178
    {
1179
        echo '
1180
        <div class="helpbutton">
1181
        ';
1182
1183
        static::printFormHead();
1184
1185
        echo '
1186
          <input type="submit" name="submit" class="helpbutton" value="' ,
1187
          (Util::getSessionVar('showhelp') == 'on' ? 'Hide' : 'Show') , '&#10; Help " />
1188
          </form>
1189
        </div>
1190
        ';
1191
    }
1192
1193
    /**
1194
     * verifyCurrentUserSession
1195
     *
1196
     * This function verifies the contents of the PHP session.  It checks
1197
     * the following:
1198
     * (1) The persistent store 'uid', the Identity Provider 'idp', the
1199
     *     IdP Display Name 'idpname', and the 'status' (of getUser()) are
1200
     *     all non-empty strings.
1201
     * (2) The 'status' (of getUser()) is even (i.e. STATUS_OK).
1202
     * (3) If $providerId is passed-in, it must match 'idp'.
1203
     * If all checks are good, then this function returns true.
1204
     *
1205
     * @param string $providerId (Optional) The user-selected Identity
1206
     *        Provider. If set, make sure $providerId matches the PHP
1207
     *        session variable 'idp'.
1208
     * @return bool True if the contents of the PHP session ar valid.
1209
     *              False otherwise.
1210
     */
1211
    public static function verifyCurrentUserSession($providerId = '')
1212
    {
1213
        $retval = false;
1214
1215
        // Check for eduGAIN IdP and possible get cert context
1216
        if (Util::isEduGAINAndGetCert()) {
1217
            Util::unsetUserSessionVars();
1218
        }
1219
1220
        $idp       = Util::getSessionVar('idp');
1221
        $idpname   = Util::getSessionVar('idpname');
1222
        $uid       = Util::getSessionVar('uid');
1223
        $status    = Util::getSessionVar('status');
1224
        $dn        = Util::getSessionVar('dn');
1225
        $authntime = Util::getSessionVar('authntime');
1226
1227
1228
        if (
1229
            (strlen($uid) > 0) && (strlen($idp) > 0) &&
1230
            (strlen($idpname) > 0) && (strlen($status) > 0) &&
1231
            (strlen($dn) > 0) && (strlen($authntime) > 0) &&
1232
            (!($status & 1))
1233
        ) {  // All STATUS_OK codes are even
1234
            if ((strlen($providerId) == 0) || ($providerId == $idp)) {
1235
                $retval = true;
1236
            }
1237
        }
1238
1239
        // As a final check, see if the IdP requires a forced skin
1240
        if ($retval) {
1241
            Util::getSkin()->init();
1242
        }
1243
1244
        return $retval;
1245
    }
1246
1247
    /**
1248
     * redirectToGetShibUser
1249
     *
1250
     * This method redirects control flow to the getuser script for
1251
     * If the first parameter (a whitelisted entityId) is not specified,
1252
     * we check to see if either the providerId PHP session variable or the
1253
     * providerId cookie is set (in that order) and use one if available.
1254
     * The function then checks to see if there is a valid PHP session
1255
     * and if the providerId matches the 'idp' in the session.  If so, then
1256
     * we don't need to redirect to '/secure/getuser/' and instead we
1257
     * we display the main page.  However, if the PHP session is not valid,
1258
     * then this function redirects to the '/secure/getuser/' script so as
1259
     * to do a Shibboleth authentication via mod_shib. When the providerId
1260
     * is non-empty, the SessionInitiator will automatically go to that IdP
1261
     * (i.e. without stopping at a WAYF).  This function also sets
1262
     * several PHP session variables that are needed by the getuser script,
1263
     * including the 'responsesubmit' variable which is set as the return
1264
     * 'submit' variable in the 'getuser' script.
1265
     *
1266
     * @param string $providerId (Optional) An entityId of the
1267
     *        authenticating IdP. If not specified (or set to the empty
1268
     *        string), we check providerId PHP session variable and
1269
     *        providerId cookie (in that order) for non-empty values.
1270
     * @param string $responsesubmit (Optional) The value of the PHP session
1271
     *       'submit' variable to be set upon return from the 'getuser'
1272
     *        script.  This is utilized to control the flow of this script
1273
     *        after 'getuser'. Defaults to 'gotuser'.
1274
     * @param string responseurl (Optional) A response url for redirection
0 ignored issues
show
Bug introduced by
The type CILogon\Service\responseurl was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
1275
     *        after successful processing at /secure/getuser/. Defaults to
1276
     *        the current script directory.
1277
     * @param bool $allowsilver Is it okay to request silver assurance in
1278
     *        the authnContextClassRef? If not, then ignore the 'Request
1279
     *        Silver' checkbox and silver certification in metadata.
1280
     *        Defaults to true.
1281
     */
1282
    public static function redirectToGetShibUser(
1283
        $providerId = '',
1284
        $responsesubmit = 'gotuser',
1285
        $responseurl = null,
1286
        $allowsilver = true
1287
    ) {
1288
1289
        // If providerId not set, try the cookie value
1290
        if (strlen($providerId) == 0) {
1291
            $providerId = Util::getPortalOrNormalCookieVar('providerId');
1292
        }
1293
1294
        // If the user has a valid 'uid' in the PHP session, and the
1295
        // providerId matches the 'idp' in the PHP session, then
1296
        // simply go to the main page.
1297
        if (static::verifyCurrentUserSession($providerId)) {
1298
            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

1298
            /** @scrutinizer ignore-call */ 
1299
            printMainPage();
Loading history...
1299
        } else { // Otherwise, redirect to the getuser script
1300
            // Set PHP session varilables needed by the getuser script
1301
            Util::setSessionVar(
1302
                'responseurl',
1303
                (is_null($responseurl) ?
1304
                    Util::getScriptDir(true) : $responseurl)
1305
            );
1306
            Util::setSessionVar('submit', 'getuser');
1307
            Util::setSessionVar('responsesubmit', $responsesubmit);
1308
            Util::getCsrf()->setCookieAndSession();
1309
1310
            // Set up the 'header' string for redirection thru mod_shib
1311
            $mhn = static::getMachineHostname($providerId);
1312
            $redirect = "Location: https://$mhn/Shibboleth.sso/Login?target=" .
1313
                urlencode("https://$mhn/secure/getuser/");
1314
1315
            if (strlen($providerId) > 0) {
1316
                // Use special NIHLogin Shibboleth SessionInitiator for acsByIndex
1317
                if ($providerId == 'urn:mace:incommon:nih.gov') {
1318
                    $redirect = preg_replace(
1319
                        '%/Shibboleth.sso/Login%',
1320
                        '/Shibboleth.sso/NIHLogin',
1321
                        $redirect
1322
                    );
1323
                }
1324
1325
                $redirect .= '&providerId=' . urlencode($providerId);
1326
1327
                // To bypass SSO at IdP, check for session var 'forceauthn' == 1
1328
                $forceauthn = Util::getSessionVar('forceauthn');
1329
                Util::unsetSessionVar('forceauthn');
1330
                if ($forceauthn) {
1331
                    $redirect .= '&forceAuthn=true';
1332
                } elseif (strlen($forceauthn) == 0) {
1333
                    // 'forceauth' was not set to '0' in the session, so
1334
                    // check the skin's option instead.
1335
                    $forceauthn = Util::getSkin()->getConfigOption('forceauthn');
1336
                    if ((!is_null($forceauthn)) && ((int)$forceauthn == 1)) {
1337
                        $redirect .= '&forceAuthn=true';
1338
                    }
1339
                }
1340
1341
                // If Silver IdP or 'Request Silver' checked, send extra parameter
1342
                if ($allowsilver) {
1343
                    if (
1344
                        (Util::getIdpList()->isSilver($providerId)) ||
1345
                        (strlen(Util::getPostVar('silveridp')) > 0)
1346
                    ) {
1347
                        Util::setSessionVar('requestsilver', '1');
1348
                        $redirect .= '&authnContextClassRef=' .
1349
                            urlencode('http://id.incommon.org/assurance/silver');
1350
                    }
1351
                }
1352
            }
1353
1354
            $log = new Loggit();
1355
            $log->info('Shibboleth Login="' . $redirect . '"');
1356
            header($redirect);
1357
            exit; // No further processing necessary
1358
        }
1359
    }
1360
1361
    /**
1362
     * redirectToGetOAuth2User
1363
     *
1364
     * This method redirects control flow to the getuser script for
1365
     * when the user logs in via OAuth 2.0. It first checks to see
1366
     * if we have a valid session. If so, we don't need to redirect and
1367
     * instead simply show the Get Certificate page. Otherwise, we start
1368
     * an OAuth 2.0 logon by composing a parameterized GET URL using
1369
     * the OAuth 2.0 endpoint.
1370
     *
1371
     * @param string $providerId (Optional) An entityId of the
1372
     *        authenticating IdP. If not specified (or set to the empty
1373
     *        string), we check providerId PHP session variable and
1374
     *        providerId cookie (in that order) for non-empty values.
1375
     * @param string $responsesubmit (Optional) The value of the PHP session
1376
     *        'submit' variable to be set upon return from the 'getuser'
1377
     *         script.  This is utilized to control the flow of this script
1378
     *         after 'getuser'. Defaults to 'gotuser'.
1379
     */
1380
    public static function redirectToGetOAuth2User(
1381
        $providerId = '',
1382
        $responsesubmit = 'gotuser'
1383
    ) {
1384
        // If providerId not set, try the cookie value
1385
        if (strlen($providerId) == 0) {
1386
            $providerId = Util::getPortalOrNormalCookieVar('providerId');
1387
        }
1388
1389
        // If the user has a valid 'uid' in the PHP session, and the
1390
        // providerId matches the 'idp' in the PHP session, then
1391
        // simply go to the 'Download Certificate' button page.
1392
        if (static::verifyCurrentUserSession($providerId)) {
1393
            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

1393
            /** @scrutinizer ignore-call */ 
1394
            printMainPage();
Loading history...
1394
        } else { // Otherwise, redirect to the OAuth 2.0 endpoint
1395
            // Set PHP session varilables needed by the getuser script
1396
            Util::unsetSessionVar('logonerror');
1397
            Util::setSessionVar('responseurl', Util::getScriptDir(true));
1398
            Util::setSessionVar('submit', 'getuser');
1399
            Util::setSessionVar('responsesubmit', $responsesubmit);
1400
            $csrf = Util::getCsrf();
1401
            $csrf->setCookieAndSession();
1402
            $extraparams = array();
1403
            $extraparams['state'] = $csrf->getTokenValue();
1404
1405
            // To bypass SSO at IdP, check for session var 'forceauthn' == 1
1406
            $forceauthn = Util::getSessionVar('forceauthn');
1407
            Util::unsetSessionVar('forceauthn');
1408
            if ($forceauthn) {
1409
                $extraparams['approval_prompt'] = 'force';
1410
            } elseif (strlen($forceauthn) == 0) {
1411
                // 'forceauth' was not set to '0' in the session, so
1412
                // check the skin's option instead.
1413
                $forceauthn = Util::getSkin()->getConfigOption('forceauthn');
1414
                if ((!is_null($forceauthn)) && ((int)$forceauthn == 1)) {
1415
                    $extraparams['approval_prompt'] = 'force';
1416
                }
1417
            }
1418
1419
            // Get the provider name based on the provider authz URL
1420
            $providerName = Util::getAuthzIdP($providerId);
1421
1422
            // Get the authz URL and redirect
1423
            $oauth2 = new OAuth2Provider($providerName);
1424
            if (is_null($oauth2->provider)) {
1425
                Util::setSessionVar('logonerror', 'Invalid Identity Provider.');
1426
                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

1426
                /** @scrutinizer ignore-call */ 
1427
                printLogonPage();
Loading history...
1427
            } else {
1428
                $authUrl = $oauth2->provider->getAuthorizationUrl(
1429
                    array_merge(
1430
                        $oauth2->authzUrlOpts,
1431
                        $extraparams
1432
                    )
1433
                );
1434
                header('Location: ' . $authUrl);
1435
                exit; // No further processing necessary
1436
            }
1437
        }
1438
    }
1439
1440
    /**
1441
     * printErrorBox
1442
     *
1443
     * This function prints out a bordered box with an error icon and any
1444
     * passed-in error HTML text.  The error icon and text are output to
1445
     * a <table> so as to keep the icon to the left of the error text.
1446
     *
1447
     * @param string $errortext HTML error text to be output
1448
     */
1449
    public static function printErrorBox($errortext)
1450
    {
1451
        echo '
1452
        <div class="errorbox">
1453
        <table cellpadding="5">
1454
        <tr>
1455
        <td valign="top">
1456
        ';
1457
        static::printIcon('error');
1458
        echo '&nbsp;
1459
        </td>
1460
        <td> ' , $errortext , '
1461
        </td>
1462
        </tr>
1463
        </table>
1464
        </div>
1465
        ';
1466
    }
1467
1468
    /**
1469
     * handleGotUser
1470
     *
1471
     * This function is called upon return from one of the getuser scripts
1472
     * which should have set the 'uid' and 'status' PHP session variables.
1473
     * It verifies that the status return is one of STATUS_OK (even
1474
     * values).  If not, we print an error message to the user.
1475
     */
1476
    public static function handleGotUser()
1477
    {
1478
        $log = new Loggit();
1479
        $uid = Util::getSessionVar('uid');
1480
        $status = Util::getSessionVar('status');
1481
1482
        // We must get and unset session vars BEFORE any HTML output since
1483
        // a redirect may go to another site, meaning we need to update
1484
        // the session cookie before we leave the cilogon.org domain.
1485
        $ePPN         = Util::getSessionVar('ePPN');
1486
        $ePTID        = Util::getSessionVar('ePTID');
1487
        $firstname    = Util::getSessionVar('firstname');
1488
        $lastname     = Util::getSessionVar('lastname');
1489
        $displayname  = Util::getSessionVar('displayname');
1490
        $emailaddr    = Util::getSessionVar('emailaddr');
1491
        $idp          = Util::getSessionVar('idp');
1492
        $idpname      = Util::getSessionVar('idpname');
1493
        $affiliation  = Util::getSessionVar('affiliation');
1494
        $ou           = Util::getSessionVar('ou');
1495
        $memberof     = Util::getSessionVar('memberof');
1496
        $acr          = Util::getSessionVar('acr');
1497
        $entitlement  = Util::getSessionVar('entitlement');
1498
        $itrustuin    = Util::getSessionVar('itrustuin');
1499
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
1500
        $failureuri   = Util::getSessionVar('failureuri');
1501
1502
        // Check for OIDC redirect_uri or OAuth 1.0a failureuri.
1503
        // If found, set 'Proceed' button redirect appropriately.
1504
        $redirect = '';
1505
        $redirectform = '';
1506
        // First, check for OIDC redirect_uri, with parameters in <form>
1507
        if (isset($clientparams['redirect_uri'])) {
1508
            $redirect = $clientparams['redirect_uri'];
1509
            $redirectform = '<input type="hidden" name="error" value="access_denied" />' .
1510
                '<input type="hidden" name="error_description" value="Missing attributes" />';
1511
            if (isset($clientparams['state'])) {
1512
                $redirectform .= '<input type="hidden" name="state" value="' .
1513
                    $clientparams['state'] . '" />';
1514
            }
1515
        }
1516
        // Next, check for OAuth 1.0a
1517
        if ((strlen($redirect) == 0) && (strlen($failureuri) > 0)) {
1518
            $redirect = $failureuri . "?reason=missing_attributes";
1519
        }
1520
1521
        // If empty 'uid' or 'status' or odd-numbered status code, error!
1522
        if ((strlen($uid) == 0) || (strlen($status) == 0) || ($status & 1)) {
1523
            // Got all session vars by now, so okay to unset.
1524
            Util::unsetAllUserSessionVars();
1525
1526
            $log->error('Failed to getuser.');
1527
1528
            static::printHeader('Error Logging On');
1529
1530
            echo '
1531
            <div class="boxed">
1532
            ';
1533
1534
            if ($status == DBService::$STATUS['STATUS_MISSING_PARAMETER_ERROR']) {
1535
                // Check if the problem IdP was an OAuth2 IdP;
1536
                // probably no first/last name
1537
                if ($idpname == 'Google') {
1538
                    static::printErrorBox('
1539
                    <p>
1540
                    There was a problem logging on. It appears that you have
1541
                    attempted to use Google as your identity provider, but your
1542
                    name or email address was missing. To rectify this problem,
1543
                    go to the <a target="_blank"
1544
                    href="https://myaccount.google.com/privacy#personalinfo">Google
1545
                    Account Personal Information page</a>, and enter your first
1546
                    name, last name, and email address. (All other Google
1547
                    account information is not required by the CILogon Service.)
1548
                    </p>
1549
                    <p>
1550
                    After you have updated your Google account profile, click
1551
                    the "Proceed" button below and attempt to log on
1552
                    with your Google account again. If you have any questions,
1553
                    please contact us at the email address at the bottom of the
1554
                    page.</p>
1555
                    ');
1556
1557
                    echo '
1558
                    <div>
1559
                    ';
1560
                    static::printFormHead($redirect, 'get');
1561
                    echo '
1562
                    <p class="centered">
1563
                    <input type="hidden" name="providerId" value="' ,
1564
                    Util::getAuthzUrl('Google') , '" /> ' , $redirectform , '
1565
                    <input type="submit" name="submit" class="submit"
1566
                    value="Proceed" />
1567
                    </p>
1568
                    </form>
1569
                    </div>
1570
                    ';
1571
                } elseif ($idpname == 'GitHub') {
1572
                    static::printErrorBox('
1573
                    <p>
1574
                    There was a problem logging on. It appears that you have
1575
                    attempted to use GitHub as your identity provider, but your
1576
                    name or email address was missing. To rectify this problem,
1577
                    go to the <a target="_blank"
1578
                    href="https://github.com/settings/profile">GitHub
1579
                    Public Profile page</a>, and enter your name and email address.
1580
                    (All other GitHub account information is not required by
1581
                    the CILogon Service.)
1582
                    </p>
1583
                    <p>
1584
                    After you have updated your GitHub account profile, click
1585
                    the "Proceed" button below and attempt to log on
1586
                    with your GitHub account again. If you have any questions,
1587
                    please contact us at the email address at the bottom of the
1588
                    page.</p>
1589
                    ');
1590
1591
                    echo '
1592
                    <div>
1593
                    ';
1594
                    static::printFormHead($redirect, 'get');
1595
                    echo '
1596
                    <p class="centered">
1597
                    <input type="hidden" name="providerId" value="' ,
1598
                    Util::getAuthzUrl('GitHub') , '" /> ' , $redirectform , '
1599
                    <input type="submit" name="submit" class="submit"
1600
                    value="Proceed" />
1601
                    </p>
1602
                    </form>
1603
                    </div>
1604
                    ';
1605
                } elseif ($idpname == 'ORCID') {
1606
                    static::printErrorBox('
1607
                    <p>
1608
                    There was a problem logging on. It appears that you have
1609
                    attempted to use ORCID as your identity provider, but your
1610
                    name or email address was missing. To rectify this problem,
1611
                    go to your <a target="_blank"
1612
                    href="https://orcid.org/my-orcid">ORCID
1613
                    Profile page</a>, enter your name and email address, and
1614
                    make sure they can be viewed by Everyone.
1615
                    (All other ORCID account information is not required by
1616
                    the CILogon Service.)
1617
                    </p>
1618
                    <p>
1619
                    After you have updated your ORCID account profile, click
1620
                    the "Proceed" button below and attempt to log on
1621
                    with your ORCID account again. If you have any questions,
1622
                    please contact us at the email address at the bottom of the
1623
                    page.</p>
1624
                    ');
1625
1626
                    echo '
1627
                    <div>
1628
                    ';
1629
                    static::printFormHead($redirect, 'get');
1630
                    echo '
1631
                    <p class="centered">
1632
                    <input type="hidden" name="providerId" value="' ,
1633
                    Util::getAuthzUrl('ORCID') , '" /> ' , $redirectform , '
1634
                    <input type="submit" name="submit" class="submit"
1635
                    value="Proceed" />
1636
                    </p>
1637
                    </form>
1638
                    </div>
1639
                    ';
1640
                } else { // Problem was missing SAML attribute from Shib IdP
1641
                    static::printAttributeReleaseErrorMessage(
1642
                        $ePPN,
1643
                        $ePTID,
1644
                        $firstname,
1645
                        $lastname,
1646
                        $displayname,
1647
                        $emailaddr,
1648
                        $idp,
1649
                        $idpname,
1650
                        $affiliation,
1651
                        $ou,
1652
                        $memberof,
1653
                        $acr,
1654
                        $entitlement,
1655
                        $itrustuin,
1656
                        $clientparams,
1657
                        $redirect,
1658
                        $redirectform,
1659
                        Util::isEduGAINAndGetCert($idp, $idpname)
1660
                    );
1661
                }
1662
            } else {
1663
                static::printErrorBox('An internal error has occurred. System
1664
                    administrators have been notified. This may be a temporary
1665
                    error. Please try again later, or contact us at the the email
1666
                    address at the bottom of the page.');
1667
1668
                echo '
1669
                <div>
1670
                ';
1671
                static::printFormHead($redirect, 'get');
1672
                echo $redirectform , '
1673
                <input type="submit" name="submit" class="submit" value="Proceed" />
1674
                </form>
1675
                </div>
1676
                ';
1677
            }
1678
1679
            echo '
1680
            </div>
1681
            ';
1682
            static::printFooter();
1683
        } elseif (Util::isEduGAINAndGetCert($idp, $idpname)) {
1684
            // If eduGAIN IdP and session can get a cert, then error!
1685
            // Got all session vars by now, so okay to unset.
1686
            Util::unsetAllUserSessionVars();
1687
1688
            $log->error('Failed to getuser due to eduGAIN IdP restriction.');
1689
1690
            static::printHeader('Error Logging On');
1691
1692
            echo '
1693
            <div class="boxed">
1694
            ';
1695
            static::printAttributeReleaseErrorMessage(
1696
                $ePPN,
1697
                $ePTID,
1698
                $firstname,
1699
                $lastname,
1700
                $displayname,
1701
                $emailaddr,
1702
                $idp,
1703
                $idpname,
1704
                $affiliation,
1705
                $ou,
1706
                $memberof,
1707
                $acr,
1708
                $entitlement,
1709
                $itrustuin,
1710
                $clientparams,
1711
                $redirect,
1712
                $redirectform,
1713
                true
1714
            );
1715
1716
            echo '
1717
            </div>
1718
            ';
1719
            static::printFooter();
1720
        } else { // Got one of the STATUS_OK status codes
1721
            // Extra security check: Once the user has successfully authenticated
1722
            // with an IdP, verify that the chosen IdP was actually whitelisted.
1723
            // If not, then set error message and show Select an Identity Provider
1724
            // page again.
1725
            Util::getSkin()->init();  // Check for forced skin
1726
            $idps = static::getCompositeIdPList();
1727
            $providerId = Util::getSessionVar('idp');
1728
            if ((strlen($providerId) > 0) && (!isset($idps[$providerId]))) {
1729
                Util::setSessionVar(
1730
                    'logonerror',
1731
                    'Invalid IdP selected. Please try again.'
1732
                );
1733
                Util::sendErrorAlert(
1734
                    'Authentication attempt using non-whitelisted IdP',
1735
                    'A user successfully authenticated with an IdP, however, the
1736
selected IdP was not in the list of whitelisted IdPs as determined
1737
by the current skin. This might indicate the user attempted to
1738
circumvent the security check in "handleGotUser()" for valid
1739
IdPs for the skin.'
1740
                );
1741
                Util::unsetCookieVar('providerId');
1742
                Util::unsetAllUserSessionVars();
1743
                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

1743
                /** @scrutinizer ignore-call */ 
1744
                printLogonPage();
Loading history...
1744
            } else { // Check if two-factor authn is enabled and proceed accordingly
1745
                if (TwoFactor::getEnabled() == 'none') {
1746
                    static::gotUserSuccess();
1747
                } else {
1748
                    TwoFactor::printPage();
1749
                }
1750
            }
1751
        }
1752
    }
1753
1754
    /**
1755
     * gotUserSuccess
1756
     *
1757
     * This function is called after the user has been successfully
1758
     * authenticated. In the case of two-factor authentication, the user
1759
     * is first authenticated by the IdP, and then by the configured
1760
     * two-factor authentication method. If the 'status' session variable is
1761
     * STATUS_OK then it checks if we have a new or changed user and prints
1762
     * that page as appropriate. Otherwise it continues to the MainPage.
1763
     */
1764
    public static function gotUserSuccess()
1765
    {
1766
        $log = new Loggit();
1767
        $status = Util::getSessionVar('status');
1768
1769
        // If this is the first time the user has used the CILogon Service, we
1770
        // skip the New User page under the following circumstances.
1771
        // (1) We are using the OIDC authorization endpoint code flow (check for
1772
        // 'clientparams' session variable);
1773
        // (2) We are using the 'delegate' code flow (check for 'callbackuri'
1774
        // session variable), and one of the following applies:
1775
        //    (a) Skin has 'forceremember' set or
1776
        //    (b) Skin has 'initialremember' set and there is no cookie for the
1777
        //        current portal
1778
        // In these cases, we skip the New User page and proceed directly to the
1779
        // main page. Note that we still want to show the User Changed page to
1780
        // inform the user about updated DN strings.
1781
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
1782
        $callbackuri = Util::getSessionVar('callbackuri');
1783
        $skin = Util::getSkin();
1784
        $forceremember = $skin->getConfigOption('delegate', 'forceremember');
0 ignored issues
show
Unused Code introduced by
The assignment to $forceremember is dead and can be removed.
Loading history...
1785
1786
        if (
1787
            ($status == DBService::$STATUS['STATUS_NEW_USER']) &&
1788
            ((strlen($callbackuri) > 0) ||
1789
             (isset($clientparams['code'])))
1790
        ) {
1791
            // Extra check for new users: see if any HTML entities
1792
            // are in the user name. If so, send an email alert.
1793
            $dn = Util::getSessionVar('dn');
1794
            $dn = static::reformatDN(preg_replace('/\s+email=.+$/', '', $dn));
1795
            $htmldn = Util::htmlent($dn);
1796
            if (strcmp($dn, $htmldn) != 0) {
1797
                Util::sendErrorAlert(
1798
                    'New user DN contains HTML entities',
1799
                    "htmlentites(DN) = $htmldn\n"
1800
                );
1801
            }
1802
        }
1803
1804
        // For a new user, or if the user got new attributes, just log it.
1805
        if ($status == DBService::$STATUS['STATUS_NEW_USER']) {
1806
            $log->info('New User.');
1807
        } elseif ($status == DBService::$STATUS['STATUS_USER_UPDATED']) {
1808
            $log->info('User IdP attributes changed.');
1809
        }
1810
        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

1810
        /** @scrutinizer ignore-call */ 
1811
        printMainPage();
Loading history...
1811
    }
1812
1813
    /**
1814
     * generateP12
1815
     *
1816
     * This function is called when the user clicks the 'Get New
1817
     * Certificate' button. It first reads in the password fields and
1818
     * verifies that they are valid (i.e. they are long enough and match).
1819
     * Then it gets a credential from the MyProxy server and converts that
1820
     * certificate into a PKCS12 which is written to disk.  If everything
1821
     * succeeds, the temporary pkcs12 directory and lifetime is saved to
1822
     * the 'p12' PHP session variable, which is read later when the Main
1823
     * Page HTML is shown.
1824
     */
1825
    public static function generateP12()
1826
    {
1827
        $log = new Loggit();
1828
1829
        // Get the entered p12lifetime and p12multiplier and set the cookies
1830
        list($minlifetime, $maxlifetime) =
1831
            static::getMinMaxLifetimes('pkcs12', 9516);
1832
        $p12lifetime   = Util::getPostVar('p12lifetime');
1833
        $p12multiplier = Util::getPostVar('p12multiplier');
1834
        if (strlen($p12multiplier) == 0) {
1835
            $p12multiplier = 1;  // For ECP, p12lifetime is in hours
1836
        }
1837
        $lifetime = $p12lifetime * $p12multiplier;
1838
        if ($lifetime <= 0) { // In case user entered negative number
1839
            $lifetime = $maxlifetime;
1840
            $p12lifetime = $maxlifetime;
1841
            $p12multiplier = 1;  // maxlifetime is in hours
1842
        } elseif ($lifetime < $minlifetime) {
1843
            $lifetime = $minlifetime;
1844
            $p12lifetime = $minlifetime;
1845
            $p12multiplier = 1;  // minlifetime is in hours
1846
        } elseif ($lifetime > $maxlifetime) {
1847
            $lifetime = $maxlifetime;
1848
            $p12lifetime = $maxlifetime;
1849
            $p12multiplier = 1;  // maxlifetime is in hours
1850
        }
1851
        Util::setCookieVar('p12lifetime', $p12lifetime);
1852
        Util::setCookieVar('p12multiplier', $p12multiplier);
1853
        Util::setSessionVar('p12lifetime', $p12lifetime);
1854
        Util::setSessionVar('p12multiplier', $p12multiplier);
1855
1856
        // Verify that the password is at least 12 characters long
1857
        $password1 = Util::getPostVar('password1');
1858
        $password2 = Util::getPostVar('password2');
1859
        $p12password = Util::getPostVar('p12password');  // For ECP clients
1860
        if (strlen($p12password) > 0) {
1861
            $password1 = $p12password;
1862
            $password2 = $p12password;
1863
        }
1864
        if (strlen($password1) < 12) {
1865
            Util::setSessionVar(
1866
                'p12error',
1867
                'Password must have at least 12 characters.'
1868
            );
1869
            return; // SHORT PASSWORD - NO FURTHER PROCESSING NEEDED!
1870
        }
1871
1872
        // Verify that the two password entry fields matched
1873
        if ($password1 != $password2) {
1874
            Util::setSessionVar('p12error', 'Passwords did not match.');
1875
            return; // MISMATCHED PASSWORDS - NO FURTHER PROCESSING NEEDED!
1876
        }
1877
1878
        // Set the port based on the Level of Assurance
1879
        $port = 7512;
1880
        $loa = Util::getSessionVar('loa');
1881
        if ($loa == 'http://incommonfederation.org/assurance/silver') {
1882
            $port = 7514;
1883
        } elseif ($loa == 'openid') {
1884
            $port = 7516;
1885
        }
1886
1887
        $dn = Util::getSessionVar('dn');
1888
        if (strlen($dn) > 0) {
1889
            // Append extra info, such as 'skin', to be processed by MyProxy
1890
            $myproxyinfo = Util::getSessionVar('myproxyinfo');
1891
            if (strlen($myproxyinfo) > 0) {
1892
                $dn .= " $myproxyinfo";
1893
            }
1894
            // Attempt to fetch a credential from the MyProxy server
1895
            $cert = MyProxy::getMyProxyCredential(
1896
                $dn,
1897
                '',
1898
                'myproxy.cilogon.org,myproxy2.cilogon.org',
1899
                $port,
1900
                $lifetime,
1901
                '/var/www/config/hostcred.pem',
1902
                ''
1903
            );
1904
1905
            // The 'openssl pkcs12' command is picky in that the private
1906
            // key must appear BEFORE the public certificate. But MyProxy
1907
            // returns the private key AFTER. So swap them around.
1908
            $cert2 = '';
1909
            if (
1910
                preg_match(
1911
                    '/-----BEGIN CERTIFICATE-----([^-]+)' .
1912
                    '-----END CERTIFICATE-----[^-]*' .
1913
                    '-----BEGIN RSA PRIVATE KEY-----([^-]+)' .
1914
                    '-----END RSA PRIVATE KEY-----/',
1915
                    $cert,
1916
                    $match
1917
                )
1918
            ) {
1919
                $cert2 = "-----BEGIN RSA PRIVATE KEY-----" .
1920
                         $match[2] . "-----END RSA PRIVATE KEY-----\n" .
1921
                         "-----BEGIN CERTIFICATE-----" .
1922
                         $match[1] . "-----END CERTIFICATE-----";
1923
            }
1924
1925
            if (strlen($cert2) > 0) { // Successfully got a certificate!
1926
                // Create a temporary directory in /var/www/html/pkcs12/
1927
                $tdirparent = '/var/www/html/pkcs12/';
1928
                $polonum = '3';   // Prepend the polo? number to directory
1929
                if (preg_match('/(\d+)\./', php_uname('n'), $polomatch)) {
1930
                    $polonum = $polomatch[1];
1931
                }
1932
                $tdir = Util::tempDir($tdirparent, $polonum, 0770);
1933
                $p12dir = str_replace($tdirparent, '', $tdir);
1934
                $p12file = $tdir . '/usercred.p12';
1935
1936
                // Call the openssl pkcs12 program to convert certificate
1937
                exec('/bin/env ' .
1938
                     'RANDFILE=/tmp/.rnd ' .
1939
                     'CILOGON_PKCS12_PW=' . escapeshellarg($password1) . ' ' .
1940
                     '/usr/bin/openssl pkcs12 -export ' .
1941
                     '-passout env:CILOGON_PKCS12_PW ' .
1942
                     "-out $p12file " .
1943
                     '<<< ' . escapeshellarg($cert2));
1944
1945
                // Verify the usercred.p12 file was actually created
1946
                $size = @filesize($p12file);
1947
                if (($size !== false) && ($size > 0)) {
1948
                    $p12link = 'https://' . static::getMachineHostname() .
1949
                               '/pkcs12/' . $p12dir . '/usercred.p12';
1950
                    $p12 = (time() + 300) . " " . $p12link;
1951
                    Util::setSessionVar('p12', $p12);
1952
                    $log->info('Generated New User Certificate="' . $p12link . '"');
1953
                    //CIL-507 Special Log Message For XSEDE
1954
                    $log->info('USAGE email="' .
1955
                        Util::getSessionVar('emailaddr') . '" client="PKCS12"');
1956
                } else { // Empty or missing usercred.p12 file - shouldn't happen!
1957
                    Util::setSessionVar(
1958
                        'p12error',
1959
                        'Error creating certificate. Please try again.'
1960
                    );
1961
                    Util::deleteDir($tdir); // Remove the temporary directory
1962
                    $log->info('Error creating certificate - missing usercred.p12');
1963
                }
1964
            } else { // The myproxy-logon command failed - shouldn't happen!
1965
                Util::setSessionVar(
1966
                    'p12error',
1967
                    'Error! MyProxy unable to create certificate.'
1968
                );
1969
                $log->info('Error creating certificate - myproxy-logon failed');
1970
            }
1971
        } else { // Couldn't find the 'dn' PHP session value - shouldn't happen!
1972
            Util::setSessionVar(
1973
                'p12error',
1974
                'Missing username. Please enable cookies.'
1975
            );
1976
            $log->info('Error creating certificate - missing dn session variable');
1977
        }
1978
    }
1979
1980
    /**
1981
     * getLogOnButtonText
1982
     *
1983
     * This function checks the current skin to see if <logonbuttontext>
1984
     * has been configured.  If so, it returns that value.  Otherwise,
1985
     * it returns 'Log On'.
1986
     *
1987
     * @return string The text of the 'Log On' button for the WAYF, as
1988
     *         configured for the skin.  Defaults to 'Log On'.
1989
     */
1990
    public static function getLogOnButtonText()
1991
    {
1992
        $retval = 'Log On';
1993
        $lobt = Util::getSkin()->getConfigOption('logonbuttontext');
1994
        if (!is_null($lobt)) {
1995
            $retval = (string)$lobt;
1996
        }
1997
        return $retval;
1998
    }
1999
2000
    /**
2001
     * getSerialStringFromDN
2002
     *
2003
     * This function takes in a CILogon subject DN and returns just the
2004
     * serial string part (e.g., A325). This function is needed since the
2005
     * serial_string is not stored in the PHP session as a separate
2006
     * variable since it is always available in the 'dn' session variable.
2007
     *
2008
     * @param string $dn The certificate subject DN (typically found in the
2009
     *        session 'dn' variable)
2010
     * @return string The serial string extracted from the subject DN, or
2011
     *         empty string if DN is empty or wrong format.
2012
     */
2013
    public static function getSerialStringFromDN($dn)
2014
    {
2015
        $serial = ''; // Return empty string upon error
2016
2017
        // Strip off the email address, if present
2018
        $dn = preg_replace('/\s+email=.+$/', '', $dn);
2019
        // Find the 'CN=' entry
2020
        if (preg_match('%/DC=org/DC=cilogon/C=US/O=.*/CN=(.*)%', $dn, $match)) {
2021
            $cn = $match[1];
2022
            if (preg_match('/\s+([^\s]+)$/', $cn, $match)) {
2023
                $serial = $match[1];
2024
            }
2025
        }
2026
        return $serial;
2027
    }
2028
2029
    /**
2030
     * getEmailFromDN
2031
     *
2032
     * This function takes in a CILogon subject DN and returns just the
2033
     * email address part. This function is needed since the email address
2034
     * is not stored in the PHP session as a separate variable since it is
2035
     * always available in the 'dn' session variable.
2036
     *
2037
     * @param string $dn The certificate subject DN (typically found in the
2038
     *        session 'dn' variable)
2039
     * @return string The email address extracted from the subject DN, or
2040
     *         empty string if DN is empty or wrong format.
2041
     */
2042
    public static function getEmailFromDN($dn)
2043
    {
2044
        $email = ''; // Return empty string upon error
2045
        if (preg_match('/\s+email=(.+)$/', $dn, $match)) {
2046
            $email = $match[1];
2047
        }
2048
        return $email;
2049
    }
2050
2051
    /**
2052
     * reformatDN
2053
     *
2054
     * This function takes in a certificate subject DN with the email=...
2055
     * part already removed. It checks the skin to see if <dnformat> has
2056
     * been set. If so, it reformats the DN appropriately.
2057
     *
2058
     * @param string $dn The certificate subject DN (without the email=... part)
2059
     * @return string The certificate subject DN transformed according to
2060
     *         the value of the <dnformat> skin config option.
2061
     */
2062
    public static function reformatDN($dn)
2063
    {
2064
        $newdn = $dn;
2065
        $dnformat = (string)Util::getSkin()->getConfigOption('dnformat');
2066
        if (!is_null($dnformat)) {
0 ignored issues
show
introduced by
The condition is_null($dnformat) is always false.
Loading history...
2067
            if (
2068
                ($dnformat == 'rfc2253') &&
2069
                (preg_match(
2070
                    '%/DC=(.*)/DC=(.*)/C=(.*)/O=(.*)/CN=(.*)%',
2071
                    $dn,
2072
                    $match
2073
                ))
2074
            ) {
2075
                array_shift($match);
2076
                $m = array_reverse(Net_LDAP2_Util::escape_dn_value($match));
2077
                $newdn = "CN=$m[0],O=$m[1],C=$m[2],DC=$m[3],DC=$m[4]";
2078
            }
2079
        }
2080
        return $newdn;
2081
    }
2082
2083
    /**
2084
     * getMinMaxLifetimes
2085
     *
2086
     * This function checks the skin's configuration to see if either or
2087
     * both of minlifetime and maxlifetime in the specified config.xml
2088
     * block have been set. If not, default to minlifetime of 1 (hour) and
2089
     * the specified defaultmaxlifetime.
2090
     *
2091
     * @param string $section The XML section block from which to read the
2092
     *        minlifetime and maxlifetime values. Can be one of the
2093
     *        following: 'pkcs12' or 'delegate'.
2094
     * @param int $defaultmaxlifetime Default maxlifetime (in hours) for the
2095
     *        credential.
2096
     * @return array An array consisting of two entries: the minimum and
2097
     *         maximum lifetimes (in hours) for a credential.
2098
     */
2099
    public static function getMinMaxLifetimes($section, $defaultmaxlifetime)
2100
    {
2101
        $minlifetime = 1;    // Default minimum lifetime is 1 hour
2102
        $maxlifetime = $defaultmaxlifetime;
2103
        $skin = Util::getSkin();
2104
        $skinminlifetime = $skin->getConfigOption($section, 'minlifetime');
2105
        // Read the skin's minlifetime value from the specified section
2106
        if ((!is_null($skinminlifetime)) && ((int)$skinminlifetime > 0)) {
2107
            $minlifetime = max($minlifetime, (int)$skinminlifetime);
2108
            // Make sure $minlifetime is less than $maxlifetime;
2109
            $minlifetime = min($minlifetime, $maxlifetime);
2110
        }
2111
        // Read the skin's maxlifetime value from the specified section
2112
        $skinmaxlifetime = $skin->getConfigOption($section, 'maxlifetime');
2113
        if ((!is_null($skinmaxlifetime)) && ((int)$skinmaxlifetime) > 0) {
2114
            $maxlifetime = min($maxlifetime, (int)$skinmaxlifetime);
2115
            // Make sure $maxlifetime is greater than $minlifetime
2116
            $maxlifetime = max($minlifetime, $maxlifetime);
2117
        }
2118
2119
        return array($minlifetime, $maxlifetime);
2120
    }
2121
2122
    /**
2123
     * getMachineHostname
2124
     *
2125
     * This function is utilized in the formation of the URL for the
2126
     * PKCS12 credential download link.  It returns a host-specific
2127
     * URL hostname by mapping the local machine hostname (as returned
2128
     * by 'uname -n') to an InCommon metadata cilogon.org hostname
2129
     * (e.g., polo2.cilogon.org). This function contains an array
2130
     * '$hostnames' where the values are the local machine hostname and
2131
     * the keys are the *.cilogon.org hostname. Since this array is
2132
     * fairly static, I didn't see the need to read it in from a config
2133
     * file. In case the local machine hostname cannot be found in the
2134
     * $hostnames array, 'cilogon.org' is returned by default.
2135
     *
2136
     * @param string $idp The entityID of the IdP used for potential
2137
     *        special handling (e.g., for Syngenta).
2138
     * @return string The full cilogon-specific hostname of this host.
2139
     */
2140
    public static function getMachineHostname($idp = '')
2141
    {
2142
        $retval = 'cilogon.org';
2143
        // CIL-439 For Syngenta, use just a single 'hostname' value to
2144
        // match their Active Directory configuration for CILogon's
2145
        // assertionConsumerService URL.
2146
        if ($idp == 'https://sts.windows.net/06219a4a-a835-44d5-afaf-3926343bfb89/') {
2147
            $retval = 'cilogon.org'; // Set to cilogon.org for production
2148
        // Otherwise, map the local hostname to a *.cilogon.org domain name.
2149
        } else {
2150
            $hostnames = array(
2151
                "polo1.ncsa.illinois.edu"        => "polo1.cilogon.org" ,
2152
                "poloa.ncsa.illinois.edu"        => "polo1.cilogon.org" ,
2153
                "polo2.ncsa.illinois.edu"        => "polo2.cilogon.org" ,
2154
                "polob.ncsa.illinois.edu"        => "polo2.cilogon.org" ,
2155
                "fozzie.nics.utk.edu"            => "polo3.cilogon.org" ,
2156
                "poloc.ncsa.illinois.edu"        => "test.cilogon.org" ,
2157
                "polot.ncsa.illinois.edu"        => "test.cilogon.org" ,
2158
                "polo-staging.ncsa.illinois.edu" => "test.cilogon.org" ,
2159
                "polod.ncsa.illinois.edu"        => "dev.cilogon.org" ,
2160
            );
2161
            $localhost = php_uname('n');
2162
            if (array_key_exists($localhost, $hostnames)) {
2163
                $retval = $hostnames[$localhost];
2164
            }
2165
        }
2166
        return $retval;
2167
    }
2168
2169
    /**
2170
     * getCompositeIdPList
2171
     *
2172
     * This function generates a list of IdPs to display in the 'Select
2173
     * An Identity Provider' box on the main CILogon page or on the
2174
     * TestIdP page. For the main CILogon page, this is a filtered list of
2175
     * IdPs based on the skin's whitelist/blacklist and the global
2176
     * blacklist file. For the TestIdP page, the list is all InCommon IdPs.
2177
     *
2178
     * @param bool $samlidps (Optional) Show all SAML-based IdPs in
2179
     *        selection list? Defaults to false, which means show only
2180
     *        whitelisted IdPs.
2181
     * @return array A two-dimensional array where the primary key is the
2182
     *         entityID and the secondary key is either 'Display_Name'
2183
     *         or 'Organization_Name'.
2184
     */
2185
    public static function getCompositeIdPList($samlidps = false)
2186
    {
2187
        $retarray = array();
2188
2189
        $idplist = Util::getIdpList();
2190
        if ($samlidps) { // Get all SAML-based IdPs only
2191
            $retarray = $idplist->getSAMLIdPs();
2192
        } else { // Get the selected InCommon IdPs, plus maybe OAuth2 IdPs
2193
            $skin = Util::getSkin();
2194
2195
            // Check if the skin's config.xml has set the
2196
            // 'registeredbyincommonidps' option, which restricts the SAML-
2197
            // based IdPs to those with the <Registered_By_InCommon> tag.
2198
            // Otherwise, just get the SAML-based IdPs that have the
2199
            // <Whitelisted> tag. Note that the skin's <idpwhitelist>
2200
            // is still consulted in either case (below).
2201
            $registeredbyincommonidps = $skin->getConfigOption('registeredbyincommonidps');
2202
            if (
2203
                (!is_null($registeredbyincommonidps)) &&
2204
                ((int)$registeredbyincommonidps == 1)
2205
            ) {
2206
                $retarray = $idplist->getRegisteredByInCommonIdPs();
2207
            } else {
2208
                $retarray = $idplist->getWhitelistedIdPs();
2209
            }
2210
2211
            // Add all OAuth2 IdPs to the list
2212
            foreach (Util::$oauth2idps as $key => $value) {
2213
                $retarray[Util::getAuthzUrl($value)]['Organization_Name'] = $value;
2214
                $retarray[Util::getAuthzUrl($value)]['Display_Name'] = $value;
2215
            }
2216
2217
            // Check to see if the skin's config.xml has a whitelist of IDPs.
2218
            // If so, go thru master IdP list and keep only those IdPs in the
2219
            // config.xml's whitelist.
2220
            if ($skin->hasIdpWhitelist()) {
2221
                foreach ($retarray as $entityId => $names) {
2222
                    if (!$skin->idpWhitelisted($entityId)) {
2223
                        unset($retarray[$entityId]);
2224
                    }
2225
                }
2226
            }
2227
            // Next, check to see if the skin's config.xml has a blacklist of
2228
            // IdPs. If so, cull down the master IdP list removing 'bad' IdPs.
2229
            if ($skin->hasIdpBlacklist()) {
2230
                $idpblacklist = $skin->getConfigOption('idpblacklist');
2231
                foreach ($idpblacklist->idp as $blackidp) {
2232
                    unset($retarray[(string)$blackidp]);
2233
                }
2234
            }
2235
        }
2236
2237
        // Fix for CIL-174 - As suggested by Keith Hazelton, replace commas and
2238
        // hyphens with just commas.
2239
        $regex = '/(University of California)\s*[,-]\s*/';
2240
        foreach ($retarray as $entityId => $names) {
2241
            if (preg_match($regex, $names['Organization_Name'])) {
2242
                $retarray[$entityId]['Organization_Name'] =
2243
                    preg_replace($regex, '$1, ', $names['Organization_Name']);
2244
            }
2245
            if (preg_match($regex, $names['Display_Name'])) {
2246
                $retarray[$entityId]['Display_Name'] =
2247
                    preg_replace($regex, '$1, ', $names['Display_Name']);
2248
            }
2249
        }
2250
2251
        // Re-sort the retarray by Display_Name for correct alphabetization.
2252
        uasort($retarray, function ($a, $b) {
2253
            return strcasecmp(
2254
                $a['Display_Name'],
2255
                $b['Display_Name']
2256
            );
2257
        });
2258
2259
        return $retarray;
2260
    }
2261
2262
    /**
2263
     * printAttributeReleaseErrorMessage
2264
     *
2265
     * This is a convenience method called by handleGotUser to print out
2266
     * the attribute release error page to the user.
2267
     *
2268
     * @param string $ePPN
2269
     * @param string $ePTID
2270
     * @param string $firstname
2271
     * @param string $lastname
2272
     * @param string $displayname
2273
     * @param string $emailaddr
2274
     * @param string $idp
2275
     * @param string $idpname
2276
     * @param string $affiliation
2277
     * @param string $ou
2278
     * @param string $memberof
2279
     * @param string $acr
2280
     * @param string $entitlement
2281
     * @param string $itrustuin
2282
     * @param string $clientparams
2283
     * @param string $redirect
2284
     * @param string $redirectform
2285
     * @param bool   $edugainandgetcert
2286
     */
2287
    public static function printAttributeReleaseErrorMessage(
2288
        $ePPN,
2289
        $ePTID,
2290
        $firstname,
2291
        $lastname,
2292
        $displayname,
2293
        $emailaddr,
2294
        $idp,
2295
        $idpname,
2296
        $affiliation,
2297
        $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

2297
        /** @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...
2298
        $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

2298
        /** @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...
2299
        $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

2299
        /** @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...
2300
        $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

2300
        /** @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...
2301
        $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

2301
        /** @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...
2302
        $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

2302
        /** @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...
2303
        $redirect,
2304
        $redirectform,
2305
        $edugainandgetcert
2306
    ) {
2307
        $errorboxstr =
2308
        '<p>There was a problem logging on. Your identity
2309
        provider has not provided CILogon with required information.</p>
2310
        <blockquote><table cellpadding="5">';
2311
2312
        $missingattrs = '';
2313
        // Show user which attributes are missing
2314
        if ((strlen($ePPN) == 0) && (strlen($ePTID) == 0)) {
2315
            $errorboxstr .=
2316
            '<tr><th>ePTID:</th><td>MISSING</td></tr>
2317
            <tr><th>ePPN:</th><td>MISSING</td></tr>';
2318
            $missingattrs .= '%0D%0A    eduPersonPrincipalName' .
2319
                             '%0D%0A    eduPersonTargetedID ';
2320
        }
2321
        if ((strlen($firstname) == 0) && (strlen($displayname) == 0)) {
2322
            $errorboxstr .=
2323
            '<tr><th>First Name:</th><td>MISSING</td></tr>';
2324
            $missingattrs .= '%0D%0A    givenName (first name)';
2325
        }
2326
        if ((strlen($lastname) == 0) && (strlen($displayname) == 0)) {
2327
            $errorboxstr .=
2328
            '<tr><th>Last Name:</th><td>MISSING</td></tr>';
2329
            $missingattrs .= '%0D%0A    sn (last name)';
2330
        }
2331
        if (
2332
            (strlen($displayname) == 0) &&
2333
            ((strlen($firstname) == 0) || (strlen($lastname) == 0))
2334
        ) {
2335
            $errorboxstr .=
2336
            '<tr><th>Display Name:</th><td>MISSING</td></tr>';
2337
            $missingattrs .= '%0D%0A    displayName';
2338
        }
2339
        $emailvalid = filter_var($emailaddr, FILTER_VALIDATE_EMAIL);
2340
        if ((strlen($emailaddr) == 0) || (!$emailvalid)) {
2341
            $errorboxstr .=
2342
            '<tr><th>Email Address:</th><td>' .
2343
            ((strlen($emailaddr) == 0) ? 'MISSING' : 'INVALID') .
2344
            '</td></tr>';
2345
            $missingattrs .= '%0D%0A    mail (email address)';
2346
        }
2347
        // CIL-326/CIL-539 - For eduGAIN IdPs attempting to get a cert,
2348
        // print out missing R&S and SIRTFI values
2349
        $idplist = Util::getIdpList();
2350
        if ($edugainandgetcert) {
2351
            if (!$idplist->isREFEDSRandS($idp)) {
2352
                $errorboxstr .=
2353
                '<tr><th><a target="_blank"
2354
                href="http://refeds.org/category/research-and-scholarship">Research
2355
                and Scholarship</a>:</th><td>MISSING</td></tr>';
2356
                $missingattrs .= '%0D%0A    http://refeds.org/category/research-and-scholarship';
2357
            }
2358
            if (!$idplist->isSIRTFI($idp)) {
2359
                $errorboxstr .=
2360
                '<tr><th><a target="_blank"
2361
                href="https://refeds.org/sirtfi">SIRTFI</a>:</th><td>MISSING</td></tr>';
2362
                $missingattrs .= '%0D%0A    http://refeds.org/sirtfi';
2363
            }
2364
        }
2365
        $student = false;
2366
        $errorboxstr .= '</table></blockquote>';
2367
        if (
2368
            (strlen($emailaddr) == 0) &&
2369
            (preg_match('/student@/', $affiliation))
2370
        ) {
2371
            $student = true;
2372
            $errorboxstr .= '<p><b>If you are a student</b>, ' .
2373
            'you may need to ask your identity provider ' .
2374
            'to release your email address.</p>';
2375
        }
2376
2377
        // Get contacts from metadata for email addresses
2378
        $shibarray = $idplist->getShibInfo($idp);
2379
        $emailmsg = '?subject=Attribute Release Problem for CILogon' .
2380
        '&[email protected]' .
2381
        '&body=Hello, I am having trouble logging on to ' .
2382
        'https://cilogon.org/ using the ' . $idpname .
2383
        ' Identity Provider (IdP) ' .
2384
        'due to the following missing attributes:%0D%0A' .
2385
        $missingattrs;
2386
        if ($student) {
2387
            $emailmsg .= '%0D%0A%0D%0ANote that my account is ' .
2388
            'marked "student" and thus my email address may need ' .
2389
            'to be released.';
2390
        }
2391
        $emailmsg .= '%0D%0A%0D%0APlease see ' .
2392
            'http://www.cilogon.org/service/addidp for more ' .
2393
            'details. Thank you for any help you can provide.';
2394
        $errorboxstr .= '<p>Contact your identity provider to ' .
2395
        'let them know you are having having a problem logging on ' .
2396
        'to CILogon.</p><blockquote><ul>';
2397
2398
        $addrfound = false;
2399
        $name = @$shibarray['Support Name'];
2400
        $addr = @$shibarray['Support Address'];
2401
        $addr = preg_replace('/^mailto:/', '', $addr);
2402
2403
        if (strlen($addr) > 0) {
2404
            $addrfound = true;
2405
            if (strlen($name) == 0) { // Use address if no name given
2406
                $name = $addr;
2407
            }
2408
            $errorboxstr .= '<li> Support Contact: ' .
2409
                $name . ' &lt;<a href="mailto:' .
2410
                $addr . $emailmsg . '">' .
2411
                $addr . '</a>&gt;</li>';
2412
        }
2413
2414
        if (!$addrfound) {
2415
            $name = @$shibarray['Technical Name'];
2416
            $addr = @$shibarray['Technical Address'];
2417
            $addr = preg_replace('/^mailto:/', '', $addr);
2418
            if (strlen($addr) > 0) {
2419
                $addrfound = true;
2420
                if (strlen($name) == 0) { // Use address if no name given
2421
                    $name = $addr;
2422
                }
2423
                $errorboxstr .= '<li> Technical Contact: ' .
2424
                    $name . ' &lt;<a href="mailto:' .
2425
                    $addr . $emailmsg . '">' .
2426
                    $addr . '</a>&gt;</li>';
2427
            }
2428
        }
2429
2430
        if (!$addrfound) {
2431
            $name = @$shibarray['Administrative Name'];
2432
            $addr = @$shibarray['Administrative Address'];
2433
            $addr = preg_replace('/^mailto:/', '', $addr);
2434
            if (strlen($addr) > 0) {
2435
                if (strlen($name) == 0) { // Use address if no name given
2436
                    $name = $addr;
2437
                }
2438
                $errorboxstr .= '<li>Administrative Contact: ' .
2439
                    $name . ' &lt;<a href="mailto:' .
2440
                    $addr . $emailmsg . '">' .
2441
                    $addr . '</a>&gt</li>';
2442
            }
2443
        }
2444
2445
        $errorboxstr .= '</ul></blockquote>
2446
2447
        <p> Alternatively, you can contact us at the email address
2448
        at the bottom of the page.</p>
2449
        ';
2450
2451
        static::printErrorBox($errorboxstr);
2452
2453
        echo '
2454
        <div>
2455
        ';
2456
2457
        static::printFormHead($redirect, 'get');
2458
        echo $redirectform , '
2459
        <input type="submit" name="submit" class="submit"
2460
        value="Proceed" />
2461
        </form>
2462
        </div>
2463
        ';
2464
    }
2465
2466
    /**
2467
     * getIdphintList
2468
     *
2469
     * This function adds support for AARC-G049 "IdP Hinting". It
2470
     * searches both the GET query parameters and the OIDC client
2471
     * parameters passed to the 'authorize' endpoint for a parameter
2472
     * named either 'selected_idp' or 'idphint'. This parameter can be
2473
     * a single entityId or a comma-separated list of entityIds.
2474
     * The entries in the list are processed to remove any 'chained'
2475
     * idphints and also to transform OIDC 'issuer' values into
2476
     * CILogon-specific 'entityIds' as used in the 'Select an IdP'
2477
     * list. Any idps which are not in the current skin's 'Select
2478
     * an IdP' list are removed. The resulting processed list of
2479
     * entityIds is returned, which may be an empty array.
2480
     *
2481
     * @param array $idps (Optional) A list of valid (i.e., whitelisted) IdPs.
2482
     *        If this list is empty, then use the current skin's IdP list.
2483
     * @return array A list of entityIds / OIDC provider URLs extracted from
2484
     *         a passed-in parameter 'selected_idp' or 'idphint'. This array
2485
     *         may be empty if no such parameter was found, or if the
2486
     *         entityIds in the list were not valid.
2487
     */
2488
    public static function getIdphintList($idps = [])
2489
    {
2490
        // Check for either 'selected_idp' or 'idphint' parameter that was
2491
        // passed in via a query parameter, either for an OAuth transaction
2492
        // or just 'normally'. Note that if both 'selected_idp' and
2493
        // 'idphint' were passed, 'idphint' takes priority.
2494
2495
        $hintarray = array();
2496
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
2497
2498
        $hintstr = '';
2499
        if (!empty(@$clientparams['idphint'])) {
2500
            $hintstr = $clientparams['idphint'];
2501
        } elseif (!empty(Util::getGetVar('idphint'))) {
2502
            $hintstr = Util::getGetVar('idphint');
2503
        } elseif (!empty(@$clientparams['selected_idp'])) {
2504
            $hintstr = $clientparams['selected_idp'];
2505
        } elseif (!empty(Util::getGetVar('selected_idp'))) {
2506
            $hintstr = Util::getGetVar('selected_idp');
2507
        }
2508
2509
        if (!empty($hintstr)) {
2510
            // Split on comma to account for multiple idps
2511
            $hintarray = explode(',', $hintstr);
2512
2513
            // Process the list of IdPs to transform them appropriately.
2514
            foreach ($hintarray as &$value) {
2515
                // Check for 'chained' idp hints, and remove the GET params.
2516
                if (preg_match('%([^\?]*)\?%', $value, $matches)) {
2517
                    $value = $matches[1];
2518
                }
2519
                // Also, check for OIDC issuers and transform them into
2520
                // CILogon-specific values used in the 'Select an IdP' list.
2521
                if (preg_match('%https://accounts.google.com%', $value)) {
2522
                    $value = 'https://accounts.google.com/o/oauth2/auth';
2523
                } elseif (preg_match('%https://github.com%', $value)) {
2524
                    $value = 'https://github.com/login/oauth/authorize';
2525
                } elseif (preg_match('%https://orcid.org%', $value)) {
2526
                    $value = 'https://orcid.org/oauth/authorize';
2527
                }
2528
            }
2529
            unset($value); // Break the reference with the last element.
2530
2531
            // Remove any non-whitelisted IdPs from the hintarray.
2532
            if (empty($idps)) {
2533
                $idps = static::getCompositeIdPList();
2534
            }
2535
            foreach ($hintarray as $value) {
2536
                if (!isset($idps[$value])) {
2537
                    if (($key = array_search($value, $hintarray)) !== false) {
2538
                        unset($hintarray[$key]);
2539
                    }
2540
                }
2541
            }
2542
        }
2543
        return $hintarray;
2544
    }
2545
}
2546