Completed
Push — master ( a1987d...ef66da )
by Terrence
21:32 queued 06:26
created

Content::redirectToGetShibUser()   C

Complexity

Conditions 13
Paths 52

Size

Total Lines 74
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
cc 13
eloc 40
nc 52
nop 4
dl 0
loc 74
ccs 0
cts 40
cp 0
crap 182
rs 6.6166
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

768
                /** @scrutinizer ignore-call */ 
769
                printLogonPage();
Loading history...
769
            }
770
        } else { // 'Disable' clicked
771
            // Check if the user clicked 'Disable Two-Factor' and send email
772
            if (Util::getPostVar('missingphone') == '1') {
773
                // Make sure two-factor was enabled
774
                $twofactorname = TwoFactor::getEnabledName();
775
                if ($twofactorname != 'Disabled') {
776
                    $email = static::getEmailFromDN(Util::getSessionVar('dn'));
777
                    if (strlen($email) > 0) { // Make sure email address exists
778
                        TwoFactor::sendPhoneAlert(
779
                            'Forgot Phone for Two-Factor Authentication',
780
                            'While using the CILogon Service, you (or someone using your account)
781
indicated that you forgot your phone by clicking the "Disable Two-Factor"
782
button. This disabled two-factor authentication by "' . $twofactorname . '"
783
using "' . Util::getSessionVar('idpname') . '" as your Identity Provider.
784
785
If you did not disable two-factor authentication, please send email to
786
"[email protected]" to report this incident.',
787
                            $email
788
                        );
789
                    } else { // No email address is bad - send error alert
790
                        Util::sendErrorAlert(
791
                            'Missing Email Address',
792
                            'When attempting to send an email notification to a user who clicked the
793
"Disable Two-Factor" button because of a forgotten phone, the CILogon
794
Service was unable to find an email address. This should never occur and
795
is probably due to a badly formatted "dn" string.'
796
                        );
797
                    }
798
                }
799
            }
800
801
            // Finally, disable two-factor authentication
802
            TwoFactor::setDisabled();
803
            TwoFactor::write();
804
            static::printTwoFactorPage();
805
        }
806
    }
807
808
    /**
809
     * handleILostMyPhone
810
     *
811
     * This function is called when the user clicks the 'I Lost My Phone'
812
     * button.  It sends email to the user AND to alerts because Duo
813
     * Security requires that a sysadmin unregister the phone for the user.
814
     * It then unsets the 'twofactor' session variable, and writes it to
815
     * the datastore, effectively wiping out all two-factor information for
816
     * the user.
817
     */
818
    public static function handleILostMyPhone()
819
    {
820
        // First, send email to user
821
        $email = static::getEmailFromDN(Util::getSessionVar('dn'));
822
        if (strlen($email) > 0) { // Make sure email address exists
823
            TwoFactor::sendPhoneAlert(
824
                'Lost Phone for Two-Factor Authentication',
825
                'While using the CILogon Service, you (or someone using your account)
826
indicated that you lost your phone by clicking the "I Lost My Phone"
827
button. This removed two-factor authentication for your account when
828
using "' . Util::getSessionVar('idpname') . '" as your Identity Provider.
829
830
System administrators have been notified of this incident. If you require
831
further assistance, please send email to "[email protected]".',
832
                $email
833
            );
834
        } else { // No email address is bad - send error alert
835
            Util::sendErrorAlert(
836
                'Missing Email Address',
837
                'When attempting to send an email notification to a user who clicked the
838
"I Lost My Phone" button, the CILogon Service was unable to find an
839
email address. This should never occur and is probably due to a badly
840
formatted "dn" string.'
841
            );
842
        }
843
844
        // Next, send email to sysadmin
845
        $errortext = 'A user clicked the "I Lost My Phone" button. ';
846
        if (TwoFactor::isRegistered('duo')) {
847
            $duoconfig = new DuoConfig();
848
            $errortext .= '
849
850
The user had registered "Duo Security" as one of the two-factor methods.
851
Since there is no way for the CILogon Service to UNregister this method
852
at the Duo Security servers, a system administrator will need to delete
853
this user\'s registration at https://' . $duoconfig->param['host'] . ' .';
854
        }
855
        Util::sendErrorAlert('Two-Factor Authentication Disabled', $errortext);
856
857
        // Finally, disable and unregister two-factor authentication
858
        Util::unsetSessionVar('twofactor');
859
        TwoFactor::write();
860
        static::printTwoFactorPage();
861
    }
862
863
    /**
864
     * handleGoogleAuthenticatorLogin
865
     *
866
     * This function is called when the user enters a one time password as
867
     * generated by the Google Authenticator app. This can occur (1) when
868
     * the user is first configuring GA two-factor and (2) when the user
869
     * logs in to the CILogon Service and GA is enabled. If the OTP is
870
     * correctly validated, the gotUserSuccess() function is called to
871
     * show output to the user.
872
     */
873
    public static function handleGoogleAuthenticatorLogin()
874
    {
875
        $gacode = Util::getPostVar('gacode');
876
        if ((strlen($gacode) > 0) && (TwoFactor::isGACodeValid($gacode))) {
877
            static::gotUserSuccess();
878
        } else {
879
            TwoFactor::printPage('ga');
880
        }
881
    }
882
883
    /**
884
     * handleDuoSecurityLogin
885
     *
886
     * This function is called when the user authenticates with Duo
887
     * Security. If the Duo authentication is valid, then the
888
     * gotUserSuccess() function is then called to show output to the user.
889
     */
890
    public static function handleDuoSecurityLogin()
891
    {
892
        $sig_response = Util::getPostVar('sig_response');
893
        if ((strlen($sig_response) > 0) &&
894
            (TwoFactor::isDuoCodeValid($sig_response))) {
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 selected_idp,
1018
        // redirect_uri, and client_id parameters from the session
1019
        // var clientparams.
1020
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
1021
        if (isset($clientparams['selected_idp'])) {
1022
            $selected_idp = $clientparams['selected_idp'];
1023
        }
1024
        if (isset($clientparams['redirect_uri'])) {
1025
            $redirect_uri = $clientparams['redirect_uri'];
1026
        }
1027
        if (isset($clientparams['client_id'])) {
1028
            $client_id = $clientparams['client_id'];
1029
        }
1030
1031
        // CIL-431 - If the OAuth2/OIDC $redirect_uri or $client_id is set,
1032
        // then check for a match in the 'bypass.txt' file to see if we
1033
        // should automatically redirect to a specific IdP. Used mainly
1034
        // by campus gateways.
1035
        if ((strlen($redirect_uri) > 0) || (strlen($client_id) > 0)) {
1036
            $bypassidp = '';
1037
            $bypassarray = Util::readArrayFromFile(
1038
                Util::getServerVar('DOCUMENT_ROOT') . '/include/bypass.txt'
1039
            );
1040
            foreach ($bypassarray as $key => $value) {
1041
                if ((preg_match($key, $redirect_uri)) ||
1042
                    (preg_match($key, $client_id))) {
1043
                    $bypassidp = $value;
1044
                    break;
1045
                }
1046
            }
1047
            if (strlen($bypassidp) > 0) { // Match found!
1048
                $providerId = $bypassidp;
1049
                $keepidp = 'checked';
1050
                // To skip the next code blocks, unset a few variables.
1051
                $forceinitialidp = 0;     // Skip checking this option
1052
                $selected_idp = '';       // Skip any passed-in option
1053
                $readidpcookies = false;  // Don't read in the IdP cookies
1054
            }
1055
        }
1056
1057
        // If the <forceinitialidp> option is set, use either the
1058
        // <initialidp> or the 'selected_idp' as the providerId, and
1059
        // use <forceinitialidp> as keepIdp. Otherwise, read the
1060
        // cookies 'providerId' and 'keepidp'.
1061
        if (($forceinitialidp == 1) &&
1062
            ((strlen($initialidp) > 0) || (strlen($selected_idp) > 0))) {
1063
            // If the <allowforceinitialidp> option is set, then make sure
1064
            // the callback / redirect uri is in the portal list.
1065
            $afii=$skin->getConfigOption('portallistaction', 'allowforceinitialidp');
1066
            if ((is_null($afii)) || // Option not set, no need to check portal list
1067
                (((int)$afii == 1) &&
1068
                  (($skin->inPortalList($redirect_uri)) ||
1069
                   ($skin->inPortalList($client_id)) ||
1070
                   ($skin->inPortalList($callbackuri))))) {
1071
                // 'selected_idp' takes precedence over <initialidp>
1072
                if (strlen($selected_idp) > 0) {
1073
                    $providerId = $selected_idp;
1074
                } else {
1075
                    $providerId = $initialidp;
1076
                }
1077
                $keepidp = $forceinitialidp;
1078
                $readidpcookies = false; // Don't read in the IdP cookies
1079
            }
1080
        }
1081
1082
        // <initialidp> options not set, or portal not in portal list?
1083
        // Get idp and 'Remember this selection' from cookies instead.
1084
        $pc = new PortalCookie();
1085
        $pn = $pc->getPortalName();
1086
        if ($readidpcookies) {
1087
            // Check the portalcookie first, then the 'normal' cookies
1088
            if (strlen($pn) > 0) {
1089
                $keepidp    = $pc->get('keepidp');
1090
                $providerId = $pc->get('providerId');
1091
            } else {
1092
                $keepidp    = Util::getCookieVar('keepidp');
1093
                $providerId = Util::getCookieVar('providerId');
1094
            }
1095
        }
1096
1097
        // If both 'keepidp' and 'providerId' were set (and the
1098
        // providerId is a whitelisted IdP or valid OpenID provider),
1099
        // then skip the Logon page and proceed to the appropriate
1100
        // getuser script.
1101
        if ((strlen($providerId) > 0) && (strlen($keepidp) > 0)) {
1102
            // If selected_idp was specified at the OIDC authorize endpoint,
1103
            // make sure that it matches the saved providerId. If not,
1104
            // then show the Logon page and uncheck the keepidp checkbox.
1105
            if ((strlen($selected_idp) == 0) || ($selected_idp == $providerId)) {
1106
                $providerName = Util::getAuthzIdP($providerId);
1107
                if (in_array($providerName, Util::$oauth2idps)) {
1108
                    // Log in with an OAuth2 IdP
1109
                    static::redirectToGetOAuth2User($providerId);
1110
                } elseif (Util::getIdpList()->exists($providerId)) {
1111
                    // Log in with InCommon
1112
                    static::redirectToGetShibUser($providerId);
1113
                } else { // $providerId not in whitelist
1114
                    if (strlen($pn) > 0) {
1115
                        $pc->set('providerId', '');
1116
                        $pc->write();
1117
                    } else {
1118
                        Util::unsetCookieVar('providerId');
1119
                    }
1120
                    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

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

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

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

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

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

1815
            /** @scrutinizer ignore-call */ 
1816
            printMainPage();
Loading history...
1816
        }
1817
    }
1818
1819
    /**
1820
     * printNewUserPage
1821
     *
1822
     * This function prints out a notification page to new users showing
1823
     * that this is the first time they have logged in with a particular
1824
     * identity provider.
1825
     */
1826
    public static function printNewUserPage()
1827
    {
1828
        $log = new Loggit();
1829
        $log->info('New User page.');
1830
1831
        $dn = Util::getSessionVar('dn');
1832
        $dn = static::reformatDN(preg_replace('/\s+email=.+$/', '', $dn));
1833
1834
        static::printHeader('New User');
1835
1836
        echo '
1837
        <div class="boxed">
1838
        <br class="clear"/>
1839
        <p>
1840
        Welcome! Your new certificate subject is as follows.
1841
        </p>
1842
        <p>
1843
        <blockquote><tt>' , Util::htmlent($dn) , '</tt></blockquote>
1844
        </p>
1845
        <p>
1846
        You may need to register this certificate subject with relying parties.
1847
        </p>
1848
        <p>
1849
        You will not see this page again unless the CILogon Service assigns you
1850
        a new certificate subject. This may occur in the following situations:
1851
        </p>
1852
        <ul>
1853
        <li>You log on to the CILogon Service using an identity provider other
1854
        than ' , Util::getSessionVar('idpname') , '.
1855
        </li>
1856
        <li>You log on using a different ' , Util::getSessionVar('idpname') , '
1857
        identity.
1858
        </li>
1859
        <li>The CILogon Service has experienced an internal error.
1860
        </li>
1861
        </ul>
1862
        <p>
1863
        Click the "Proceed" button to continue. If you have any questions,
1864
        please contact us at the email address at the bottom of the page.
1865
        </p>
1866
        <div>
1867
        ';
1868
        static::printFormHead();
1869
        echo '
1870
        <p class="centered">
1871
        <input type="submit" name="submit" class="submit" value="Proceed" />
1872
        </p>
1873
        </form>
1874
        </div>
1875
        </div>
1876
        ';
1877
        static::printFooter();
1878
    }
1879
1880
    /**
1881
     * printUserChangedPage
1882
     *
1883
     * This function prints out a notification page informing the user that
1884
     * some of their attributes have changed, which will affect the
1885
     * contents of future issued certificates.  This page shows which
1886
     * attributes are different (displaying both old and new values) and
1887
     * what portions of the certificate are affected.
1888
     */
1889
    public static function printUserChangedPage()
1890
    {
1891
        $errstr = '';
1892
1893
        $log = new Loggit();
1894
        $log->info('User IdP attributes changed.');
1895
1896
        $uid = Util::getSessionVar('uid');
1897
        $dbs = new DBService();
1898
        if (($dbs->getUser($uid)) &&
1899
            (!($dbs->status & 1))) {  // STATUS_OK codes are even
1900
            $idpname = $dbs->idp_display_name;
1901
            $first   = $dbs->first_name;
1902
            $last    = $dbs->last_name;
1903
            $email   = $dbs->email;
1904
            $dn      = $dbs->distinguished_name;
1905
            $dn      = static::reformatDN(preg_replace('/\s+email=.+$/', '', $dn));
1906
1907
            if (($dbs->getLastArchivedUser($uid)) &&
1908
                (!($dbs->status & 1))) {  // STATUS_OK codes are even
1909
                $previdpname = $dbs->idp_display_name;
1910
                $prevfirst   = $dbs->first_name;
1911
                $prevlast    = $dbs->last_name;
1912
                $prevemail   = $dbs->email;
1913
                $prevdn      = $dbs->distinguished_name;
1914
                $prevdn      = static::reformatDN(
1915
                    preg_replace(
1916
                        '/\s+email=.+$/',
1917
                        '',
1918
                        $prevdn
1919
                    )
1920
                );
1921
1922
                $tablerowodd = true;
1923
1924
                static::printHeader('Certificate Information Changed');
1925
1926
                echo '
1927
                <div class="boxed">
1928
                <br class="clear"/>
1929
                <p>
1930
                One or more of the attributes released by your organization has
1931
                changed since the last time you logged on to the CILogon
1932
                Service. This will affect your certificates as described below.
1933
                </p>
1934
1935
                <div class="userchanged">
1936
                <table cellpadding="5">
1937
                  <tr class="headings">
1938
                    <th>Attribute</th>
1939
                    <th>Previous Value</th>
1940
                    <th>Current Value</th>
1941
                  </tr>
1942
                ';
1943
1944
                if ($idpname != $previdpname) {
1945
                    echo '
1946
                    <tr' , ($tablerowodd ? ' class="odd"' : '') , '>
0 ignored issues
show
introduced by
The condition $tablerowodd is always true.
Loading history...
1947
                      <th>Organization Name:</th>
1948
                      <td>'.$previdpname.'</td>
1949
                      <td>'.$idpname.'</td>
1950
                    </tr>
1951
                    ';
1952
                    $tablerowodd = !$tablerowodd;
0 ignored issues
show
introduced by
The condition $tablerowodd is always true.
Loading history...
1953
                }
1954
1955
                if ($first != $prevfirst) {
1956
                    echo '
1957
                    <tr' , ($tablerowodd ? ' class="odd"' : '') , '>
1958
                      <th>First Name:</th>
1959
                      <td>'.Util::htmlent($prevfirst).'</td>
1960
                      <td>'.Util::htmlent($first).'</td>
1961
                    </tr>
1962
                    ';
1963
                    $tablerowodd = !$tablerowodd;
1964
                }
1965
1966
                if ($last != $prevlast) {
1967
                    echo '
1968
                    <tr' , ($tablerowodd ? ' class="odd"' : '') , '>
1969
                      <th>Last Name:</th>
1970
                      <td>'.Util::htmlent($prevlast).'</td>
1971
                      <td>'.Util::htmlent($last).'</td>
1972
                    </tr>
1973
                    ';
1974
                    $tablerowodd = !$tablerowodd;
1975
                }
1976
1977
                if ($email != $prevemail) {
1978
                    echo '
1979
                    <tr' , ($tablerowodd ? ' class="odd"' : '') , '>
1980
                      <th>Email Address:</th>
1981
                      <td>'.$prevemail.'</td>
1982
                      <td>'.$email.'</td>
1983
                    </tr>
1984
                    ';
1985
                    $tablerowodd = !$tablerowodd;
0 ignored issues
show
Unused Code introduced by
The assignment to $tablerowodd is dead and can be removed.
Loading history...
1986
                }
1987
1988
                echo '
1989
                </table>
1990
                </div>
1991
                ';
1992
1993
                if (($idpname != $previdpname) ||
1994
                    ($first != $prevfirst) ||
1995
                    ($last != $prevlast)) {
1996
                    echo '
1997
                    <p>
1998
                    The above changes to your attributes will cause your
1999
                    <strong>certificate subject</strong> to change. You may be
2000
                    required to re-register with relying parties using this new
2001
                    certificate subject.
2002
                    </p>
2003
                    <p>
2004
                    <blockquote>
2005
                    <table cellspacing="0">
2006
                      <tr>
2007
                        <td>Previous Subject DN:</td>
2008
                        <td>' , Util::htmlent($prevdn) , '</td>
2009
                      </tr>
2010
                      <tr>
2011
                        <td>Current Subject DN:</td>
2012
                        <td>' , Util::htmlent($dn) , '</td>
2013
                      </tr>
2014
                    </table>
2015
                    </blockquote>
2016
                    </p>
2017
                    ';
2018
2019
                    // Special log message for Subject DN change
2020
                    $log->info("##### DN CHANGE ##### prevdn='$prevdn' newdn='$dn'");
2021
                }
2022
2023
                if ($email != $prevemail) {
2024
                    echo '
2025
                    <p>
2026
                    Your new certificate will contain your <strong>updated email
2027
                    address</strong>.
2028
                    This may change how your certificate may be used in email
2029
                    clients. Possible problems which may occur include:
2030
                    </p>
2031
                    <ul>
2032
                    <li>If your "from" address does not match what is
2033
                        contained in the certificate, recipients may fail to
2034
                        verify your signed email messages.</li>
2035
                    <li>If the email address in the certificate does not
2036
                        match the destination address, senders may have
2037
                        difficulty encrypting email addressed to you.</li>
2038
                    </ul>
2039
                    ';
2040
                }
2041
2042
                echo '
2043
                <p>
2044
                If you have any questions, please contact us at the email
2045
                address at the bottom of the page.
2046
                </p>
2047
                <div>
2048
                ';
2049
                static::printFormHead();
2050
                echo '
2051
                <p class="centered">
2052
                <input type="submit" name="submit" class="submit"
2053
                value="Proceed" />
2054
                </p>
2055
                </form>
2056
                </div>
2057
                </div>
2058
                ';
2059
                static::printFooter();
2060
            } else {  // Database error, should never happen
2061
                if (!is_null($dbs->status)) {
2062
                    $errstr = array_search($dbs->status, DBService::$STATUS);
2063
                }
2064
                $log->error('Database error reading last archived ' .
2065
                                  'user attributes. ' . $errstr);
0 ignored issues
show
Bug introduced by
Are you sure $errstr of type false|integer|string can be used in concatenation? ( Ignorable by Annotation )

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

2065
                                  'user attributes. ' . /** @scrutinizer ignore-type */ $errstr);
Loading history...
2066
                Util::sendErrorAlert(
2067
                    'dbService Error',
2068
                    'Error calling dbservice action "getLastArchivedUser" in ' .
2069
                    'printUserChangedPaged() method. ' . $errstr
2070
                );
2071
                Util::unsetAllUserSessionVars();
2072
                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

2072
                /** @scrutinizer ignore-call */ 
2073
                printLogonPage();
Loading history...
2073
            }
2074
        } else {  // Database error, should never happen
2075
            if (!is_null($dbs->status)) {
2076
                $errstr = array_search($dbs->status, DBService::$STATUS);
2077
            }
2078
            $log->error('Database error reading current user attributes. ' .
2079
                              $errstr);
2080
            Util::sendErrorAlert(
2081
                'dbService Error',
2082
                'Error calling dbservice action "getUser" in ' .
2083
                'printUserChangedPaged() method. ' . $errstr
2084
            );
2085
            Util::unsetAllUserSessionVars();
2086
            printLogonPage();
2087
        }
2088
    }
2089
2090
    /**
2091
     * generateP12
2092
     *
2093
     * This function is called when the user clicks the 'Get New
2094
     * Certificate' button. It first reads in the password fields and
2095
     * verifies that they are valid (i.e. they are long enough and match).
2096
     * Then it gets a credential from the MyProxy server and converts that
2097
     * certificate into a PKCS12 which is written to disk.  If everything
2098
     * succeeds, the temporary pkcs12 directory and lifetime is saved to
2099
     * the 'p12' PHP session variable, which is read later when the Main
2100
     * Page HTML is shown.
2101
     */
2102
    public static function generateP12()
2103
    {
2104
        $log = new Loggit();
2105
2106
        // Get the entered p12lifetime and p12multiplier and set the cookies
2107
        list($minlifetime, $maxlifetime) =
2108
            static::getMinMaxLifetimes('pkcs12', 9516);
2109
        $p12lifetime   = Util::getPostVar('p12lifetime');
2110
        $p12multiplier = Util::getPostVar('p12multiplier');
2111
        if (strlen($p12multiplier) == 0) {
2112
            $p12multiplier = 1;  // For ECP, p12lifetime is in hours
2113
        }
2114
        $lifetime = $p12lifetime * $p12multiplier;
2115
        if ($lifetime <= 0) { // In case user entered negative number
2116
            $lifetime = $maxlifetime;
2117
            $p12lifetime = $maxlifetime;
2118
            $p12multiplier = 1;  // maxlifetime is in hours
2119
        } elseif ($lifetime < $minlifetime) {
2120
            $lifetime = $minlifetime;
2121
            $p12lifetime = $minlifetime;
2122
            $p12multiplier = 1;  // minlifetime is in hours
2123
        } elseif ($lifetime > $maxlifetime) {
2124
            $lifetime = $maxlifetime;
2125
            $p12lifetime = $maxlifetime;
2126
            $p12multiplier = 1;  // maxlifetime is in hours
2127
        }
2128
        Util::setCookieVar('p12lifetime', $p12lifetime);
2129
        Util::setCookieVar('p12multiplier', $p12multiplier);
2130
        Util::setSessionVar('p12lifetime', $p12lifetime);
2131
        Util::setSessionVar('p12multiplier', $p12multiplier);
2132
2133
        // Verify that the password is at least 12 characters long
2134
        $password1 = Util::getPostVar('password1');
2135
        $password2 = Util::getPostVar('password2');
2136
        $p12password = Util::getPostVar('p12password');  // For ECP clients
2137
        if (strlen($p12password) > 0) {
2138
            $password1 = $p12password;
2139
            $password2 = $p12password;
2140
        }
2141
        if (strlen($password1) < 12) {
2142
            Util::setSessionVar(
2143
                'p12error',
2144
                'Password must have at least 12 characters.'
2145
            );
2146
            return; // SHORT PASSWORD - NO FURTHER PROCESSING NEEDED!
2147
        }
2148
2149
        // Verify that the two password entry fields matched
2150
        if ($password1 != $password2) {
2151
            Util::setSessionVar('p12error', 'Passwords did not match.');
2152
            return; // MISMATCHED PASSWORDS - NO FURTHER PROCESSING NEEDED!
2153
        }
2154
2155
        // Set the port based on the Level of Assurance
2156
        $port = 7512;
2157
        $loa = Util::getSessionVar('loa');
2158
        if ($loa == 'http://incommonfederation.org/assurance/silver') {
2159
            $port = 7514;
2160
        } elseif ($loa == 'openid') {
2161
            $port = 7516;
2162
        }
2163
2164
        $dn = Util::getSessionVar('dn');
2165
        if (strlen($dn) > 0) {
2166
            // Append extra info, such as 'skin', to be processed by MyProxy
2167
            $myproxyinfo = Util::getSessionVar('myproxyinfo');
2168
            if (strlen($myproxyinfo) > 0) {
2169
                $dn .= " $myproxyinfo";
2170
            }
2171
            // Attempt to fetch a credential from the MyProxy server
2172
            $cert = MyProxy::getMyProxyCredential(
2173
                $dn,
2174
                '',
2175
                'myproxy.cilogon.org,myproxy2.cilogon.org',
2176
                $port,
2177
                $lifetime,
2178
                '/var/www/config/hostcred.pem',
2179
                ''
2180
            );
2181
2182
            // The 'openssl pkcs12' command is picky in that the private
2183
            // key must appear BEFORE the public certificate. But MyProxy
2184
            // returns the private key AFTER. So swap them around.
2185
            $cert2 = '';
2186
            if (preg_match(
2187
                '/-----BEGIN CERTIFICATE-----([^-]+)' .
2188
                '-----END CERTIFICATE-----[^-]*' .
2189
                '-----BEGIN RSA PRIVATE KEY-----([^-]+)' .
2190
                '-----END RSA PRIVATE KEY-----/',
2191
                $cert,
2192
                $match
2193
            )) {
2194
                $cert2 = "-----BEGIN RSA PRIVATE KEY-----" .
2195
                         $match[2] . "-----END RSA PRIVATE KEY-----\n".
2196
                         "-----BEGIN CERTIFICATE-----" .
2197
                         $match[1] . "-----END CERTIFICATE-----";
2198
            }
2199
2200
            if (strlen($cert2) > 0) { // Successfully got a certificate!
2201
                // Create a temporary directory in /var/www/html/pkcs12/
2202
                $tdirparent = '/var/www/html/pkcs12/';
2203
                $polonum = '3';   // Prepend the polo? number to directory
2204
                if (preg_match('/(\d+)\./', php_uname('n'), $polomatch)) {
2205
                    $polonum = $polomatch[1];
2206
                }
2207
                $tdir = Util::tempDir($tdirparent, $polonum, 0770);
2208
                $p12dir = str_replace($tdirparent, '', $tdir);
2209
                $p12file = $tdir . '/usercred.p12';
2210
2211
                // Call the openssl pkcs12 program to convert certificate
2212
                exec('/bin/env ' .
2213
                     'RANDFILE=/tmp/.rnd ' .
2214
                     'CILOGON_PKCS12_PW=' . escapeshellarg($password1) . ' ' .
2215
                     '/usr/bin/openssl pkcs12 -export ' .
2216
                     '-passout env:CILOGON_PKCS12_PW ' .
2217
                     "-out $p12file " .
2218
                     '<<< ' . escapeshellarg($cert2));
2219
2220
                // Verify the usercred.p12 file was actually created
2221
                $size = @filesize($p12file);
2222
                if (($size !== false) && ($size > 0)) {
2223
                    $p12link = 'https://' . static::getMachineHostname() .
2224
                               '/pkcs12/' . $p12dir . '/usercred.p12';
2225
                    $p12 = (time()+300) . " " . $p12link;
2226
                    Util::setSessionVar('p12', $p12);
2227
                    $log->info('Generated New User Certificate="'.$p12link.'"');
2228
                    //CIL-507 Special Log Message For XSEDE
2229
                    $log->info('USAGE email="' .
2230
                        Util::getSessionVar('emailaddr') . '" client="PKCS12"');
2231
                } else { // Empty or missing usercred.p12 file - shouldn't happen!
2232
                    Util::setSessionVar(
2233
                        'p12error',
2234
                        'Error creating certificate. Please try again.'
2235
                    );
2236
                    Util::deleteDir($tdir); // Remove the temporary directory
2237
                    $log->info('Error creating certificate - missing usercred.p12');
2238
                }
2239
            } else { // The myproxy-logon command failed - shouldn't happen!
2240
                Util::setSessionVar(
2241
                    'p12error',
2242
                    'Error! MyProxy unable to create certificate.'
2243
                );
2244
                $log->info('Error creating certificate - myproxy-logon failed');
2245
            }
2246
        } else { // Couldn't find the 'dn' PHP session value - shouldn't happen!
2247
            Util::setSessionVar(
2248
                'p12error',
2249
                'Missing username. Please enable cookies.'
2250
            );
2251
            $log->info('Error creating certificate - missing dn session variable');
2252
        }
2253
    }
2254
2255
    /**
2256
     * getLogOnButtonText
2257
     *
2258
     * This function checks the current skin to see if <logonbuttontext>
2259
     * has been configured.  If so, it returns that value.  Otherwise,
2260
     * it returns 'Log On'.
2261
     *
2262
     * @return string The text of the 'Log On' button for the WAYF, as
2263
     *         configured for the skin.  Defaults to 'Log On'.
2264
     */
2265
    public static function getLogOnButtonText()
2266
    {
2267
        $retval = 'Log On';
2268
        $lobt = Util::getSkin()->getConfigOption('logonbuttontext');
2269
        if (!is_null($lobt)) {
2270
            $retval = (string)$lobt;
2271
        }
2272
        return $retval;
2273
    }
2274
2275
    /**
2276
     * getSerialStringFromDN
2277
     *
2278
     * This function takes in a CILogon subject DN and returns just the
2279
     * serial string part (e.g., A325). This function is needed since the
2280
     * serial_string is not stored in the PHP session as a separate
2281
     * variable since it is always available in the 'dn' session variable.
2282
     *
2283
     * @param string $dn The certificate subject DN (typically found in the
2284
     *        session 'dn' variable)
2285
     * @return string The serial string extracted from the subject DN, or
2286
     *         empty string if DN is empty or wrong format.
2287
     */
2288
    public static function getSerialStringFromDN($dn)
2289
    {
2290
        $serial = ''; // Return empty string upon error
2291
2292
        // Strip off the email address, if present
2293
        $dn = preg_replace('/\s+email=.+$/', '', $dn);
2294
        // Find the 'CN=' entry
2295
        if (preg_match('%/DC=org/DC=cilogon/C=US/O=.*/CN=(.*)%', $dn, $match)) {
2296
            $cn = $match[1];
2297
            if (preg_match('/\s+([^\s]+)$/', $cn, $match)) {
2298
                $serial = $match[1];
2299
            }
2300
        }
2301
        return $serial;
2302
    }
2303
2304
    /**
2305
     * getEmailFromDN
2306
     *
2307
     * This function takes in a CILogon subject DN and returns just the
2308
     * email address part. This function is needed since the email address
2309
     * is not stored in the PHP session as a separate variable since it is
2310
     * always available in the 'dn' session variable.
2311
     *
2312
     * @param string $dn The certificate subject DN (typically found in the
2313
     *        session 'dn' variable)
2314
     * @return string The email address extracted from the subject DN, or
2315
     *         empty string if DN is empty or wrong format.
2316
     */
2317
    public static function getEmailFromDN($dn)
2318
    {
2319
        $email = ''; // Return empty string upon error
2320
        if (preg_match('/\s+email=(.+)$/', $dn, $match)) {
2321
            $email = $match[1];
2322
        }
2323
        return $email;
2324
    }
2325
2326
    /**
2327
     * reformatDN
2328
     *
2329
     * This function takes in a certificate subject DN with the email=...
2330
     * part already removed. It checks the skin to see if <dnformat> has
2331
     * been set. If so, it reformats the DN appropriately.
2332
     *
2333
     * @param string $dn The certificate subject DN (without the email=... part)
2334
     * @return string The certificate subject DN transformed according to
2335
     *         the value of the <dnformat> skin config option.
2336
     */
2337
    public static function reformatDN($dn)
2338
    {
2339
        $newdn = $dn;
2340
        $dnformat = (string)Util::getSkin()->getConfigOption('dnformat');
2341
        if (!is_null($dnformat)) {
0 ignored issues
show
introduced by
The condition is_null($dnformat) is always false.
Loading history...
2342
            if (($dnformat == 'rfc2253') &&
2343
                (preg_match(
2344
                    '%/DC=(.*)/DC=(.*)/C=(.*)/O=(.*)/CN=(.*)%',
2345
                    $dn,
2346
                    $match
2347
                ))) {
2348
                array_shift($match);
2349
                $m = array_reverse(Net_LDAP2_Util::escape_dn_value($match));
2350
                $newdn = "CN=$m[0],O=$m[1],C=$m[2],DC=$m[3],DC=$m[4]";
2351
            }
2352
        }
2353
        return $newdn;
2354
    }
2355
2356
    /**
2357
     * getMinMaxLifetimes
2358
     *
2359
     * This function checks the skin's configuration to see if either or
2360
     * both of minlifetime and maxlifetime in the specified config.xml
2361
     * block have been set. If not, default to minlifetime of 1 (hour) and
2362
     * the specified defaultmaxlifetime.
2363
     *
2364
     * @param string $section The XML section block from which to read the
2365
     *        minlifetime and maxlifetime values. Can be one of the
2366
     *        following: 'pkcs12', 'gsca', or 'delegate'.
2367
     * @param int $defaultmaxlifetime Default maxlifetime (in hours) for the
2368
     *        credential.
2369
     * @return array An array consisting of two entries: the minimum and
2370
     *         maximum lifetimes (in hours) for a credential.
2371
     */
2372
    public static function getMinMaxLifetimes($section, $defaultmaxlifetime)
2373
    {
2374
        $minlifetime = 1;    // Default minimum lifetime is 1 hour
2375
        $maxlifetime = $defaultmaxlifetime;
2376
        $skin = Util::getSkin();
2377
        $skinminlifetime = $skin->getConfigOption($section, 'minlifetime');
2378
        // Read the skin's minlifetime value from the specified section
2379
        if ((!is_null($skinminlifetime)) && ((int)$skinminlifetime > 0)) {
2380
            $minlifetime = max($minlifetime, (int)$skinminlifetime);
2381
            // Make sure $minlifetime is less than $maxlifetime;
2382
            $minlifetime = min($minlifetime, $maxlifetime);
2383
        }
2384
        // Read the skin's maxlifetime value from the specified section
2385
        $skinmaxlifetime = $skin->getConfigOption($section, 'maxlifetime');
2386
        if ((!is_null($skinmaxlifetime)) && ((int)$skinmaxlifetime) > 0) {
2387
            $maxlifetime = min($maxlifetime, (int)$skinmaxlifetime);
2388
            // Make sure $maxlifetime is greater than $minlifetime
2389
            $maxlifetime = max($minlifetime, $maxlifetime);
2390
        }
2391
2392
        return array($minlifetime, $maxlifetime);
2393
    }
2394
2395
    /**
2396
     * getMachineHostname
2397
     *
2398
     * This function is utilized in the formation of the URL for the
2399
     * PKCS12 credential download link.  It returns a host-specific
2400
     * URL hostname by mapping the local machine hostname (as returned
2401
     * by 'uname -n') to an InCommon metadata cilogon.org hostname
2402
     * (e.g., polo2.cilogon.org). This function contains an array
2403
     * '$hostnames' where the values are the local machine hostname and
2404
     * the keys are the *.cilogon.org hostname. Since this array is
2405
     * fairly static, I didn't see the need to read it in from a config
2406
     * file. In case the local machine hostname cannot be found in the
2407
     * $hostnames array, 'cilogon.org' is returned by default.
2408
     *
2409
     * @param string $idp The entityID of the IdP used for potential
2410
     *        special handling (e.g., for Syngenta).
2411
     * @return string The full cilogon-specific hostname of this host.
2412
     */
2413
    public static function getMachineHostname($idp = '')
2414
    {
2415
        $retval = 'cilogon.org';
2416
        // CIL-439 For Syngenta, use just a single 'hostname' value to
2417
        // match their Active Directory configuration for CILogon's
2418
        // assertionConsumerService URL.
2419
        if ($idp == 'https://sts.windows.net/06219a4a-a835-44d5-afaf-3926343bfb89/') {
2420
            $retval = 'cilogon.org'; // Set to cilogon.org for production
2421
        // Otherwise, map the local hostname to a *.cilogon.org domain name.
2422
        } else {
2423
            $hostnames = array(
2424
                "polo1.ncsa.illinois.edu"        => "polo1.cilogon.org" ,
2425
                "poloa.ncsa.illinois.edu"        => "polo1.cilogon.org" ,
2426
                "polo2.ncsa.illinois.edu"        => "polo2.cilogon.org" ,
2427
                "polob.ncsa.illinois.edu"        => "polo2.cilogon.org" ,
2428
                "fozzie.nics.utk.edu"            => "polo3.cilogon.org" ,
2429
                "poloc.ncsa.illinois.edu"        => "test.cilogon.org" ,
2430
                "polot.ncsa.illinois.edu"        => "test.cilogon.org" ,
2431
                "polo-staging.ncsa.illinois.edu" => "test.cilogon.org" ,
2432
                "polod.ncsa.illinois.edu"        => "dev.cilogon.org" ,
2433
            );
2434
            $localhost = php_uname('n');
2435
            if (array_key_exists($localhost, $hostnames)) {
2436
                $retval = $hostnames[$localhost];
2437
            }
2438
        }
2439
        return $retval;
2440
    }
2441
2442
    /**
2443
     * getCompositeIdPList
2444
     *
2445
     * This function generates a list of IdPs to display in the 'Select
2446
     * An Identity Provider' box on the main CILogon page or on the
2447
     * TestIdP page. For the main CILogon page, this is a filtered list of
2448
     * IdPs based on the skin's whitelist/blacklist and the global
2449
     * blacklist file. For the TestIdP page, the list is all InCommon IdPs.
2450
     *
2451
     * @param bool $incommonidps (Optional) Show all InCommon IdPs in
2452
     *        selection list? Defaults to false, which means show only
2453
     *        whitelisted IdPs.
2454
     * @return array A two-dimensional array where the primary key is the
2455
     *         entityID and the secondary key is either 'Display_Name'
2456
     *         or 'Organization_Name'.
2457
     */
2458
    public static function getCompositeIdPList($incommonidps = false)
2459
    {
2460
        $retarray = array();
2461
2462
        $idplist = Util::getIdpList();
2463
        if ($incommonidps) { // Get all InCommon IdPs only
2464
            $retarray = $idplist->getInCommonIdPs();
2465
        } else { // Get the whitelisted InCommon IdPs, plus maybe OAuth2 IdPs
2466
            $retarray = $idplist->getWhitelistedIdPs();
2467
2468
            // Add all OAuth2 IdPs to the list
2469
            foreach (Util::$oauth2idps as $key => $value) {
2470
                $retarray[Util::getAuthzUrl($value)]['Organization_Name'] = $value;
2471
                $retarray[Util::getAuthzUrl($value)]['Display_Name'] = $value;
2472
            }
2473
2474
            // Check to see if the skin's config.xml has a whitelist of IDPs.
2475
            // If so, go thru master IdP list and keep only those IdPs in the
2476
            // config.xml's whitelist.
2477
            $skin = Util::getSkin();
2478
            if ($skin->hasIdpWhitelist()) {
2479
                foreach ($retarray as $entityId => $names) {
2480
                    if (!$skin->idpWhitelisted($entityId)) {
2481
                        unset($retarray[$entityId]);
2482
                    }
2483
                }
2484
            }
2485
            // Next, check to see if the skin's config.xml has a blacklist of
2486
            // IdPs. If so, cull down the master IdP list removing 'bad' IdPs.
2487
            if ($skin->hasIdpBlacklist()) {
2488
                $idpblacklist = $skin->getConfigOption('idpblacklist');
2489
                foreach ($idpblacklist->idp as $blackidp) {
2490
                    unset($retarray[(string)$blackidp]);
2491
                }
2492
            }
2493
        }
2494
2495
        // Fix for CIL-174 - As suggested by Keith Hazelton, replace commas and
2496
        // hyphens with just commas.
2497
        $regex = '/(University of California)\s*[,-]\s*/';
2498
        foreach ($retarray as $entityId => $names) {
2499
            if (preg_match($regex, $names['Organization_Name'])) {
2500
                $retarray[$entityId]['Organization_Name'] =
2501
                    preg_replace($regex, '$1, ', $names['Organization_Name']);
2502
            }
2503
            if (preg_match($regex, $names['Display_Name'])) {
2504
                $retarray[$entityId]['Display_Name'] =
2505
                    preg_replace($regex, '$1, ', $names['Display_Name']);
2506
            }
2507
        }
2508
2509
        // Re-sort the retarray by Display_Name for correct alphabetization.
2510
        uasort($retarray, function ($a, $b) {
2511
            return strcasecmp(
2512
                $a['Display_Name'],
2513
                $b['Display_Name']
2514
            );
2515
        });
2516
2517
        return $retarray;
2518
    }
2519
2520
    /**
2521
     * printAttributeReleaseErrorMessage
2522
     *
2523
     * This is a convenience method called by handleGotUser to print out
2524
     * the attribute release error page to the user.
2525
     *
2526
     * @param string $ePPN
2527
     * @param string $ePTID
2528
     * @param string $firstname
2529
     * @param string $lastname
2530
     * @param string $displayname
2531
     * @param string $emailaddr
2532
     * @param string $idp
2533
     * @param string $idpname
2534
     * @param string $affiliation
2535
     * @param string $ou
2536
     * @param string $memberof
2537
     * @param string $acr
2538
     * @param string $entitlement
2539
     * @param string $clientparams
2540
     * @param string $redirect
2541
     * @param string $redirectform
2542
     * @param bool   $edugainandgetcert
2543
     */
2544
    public static function printAttributeReleaseErrorMessage(
2545
        $ePPN,
2546
        $ePTID,
2547
        $firstname,
2548
        $lastname,
2549
        $displayname,
2550
        $emailaddr,
2551
        $idp,
2552
        $idpname,
2553
        $affiliation,
2554
        $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

2554
        /** @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...
2555
        $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

2555
        /** @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...
2556
        $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

2556
        /** @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...
2557
        $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

2557
        /** @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...
2558
        $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

2558
        /** @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...
2559
        $redirect,
2560
        $redirectform,
2561
        $edugainandgetcert
2562
    ) {
2563
        $errorboxstr =
2564
        '<p>There was a problem logging on. Your identity
2565
        provider has not provided CILogon with required information.</p>
2566
        <blockquote><table cellpadding="5">';
2567
2568
        $missingattrs = '';
2569
        // Show user which attributes are missing
2570
        if ((strlen($ePPN) == 0) && (strlen($ePTID) == 0)) {
2571
            $errorboxstr .=
2572
            '<tr><th>ePTID:</th><td>MISSING</td></tr>
2573
            <tr><th>ePPN:</th><td>MISSING</td></tr>';
2574
            $missingattrs .= '%0D%0A    eduPersonPrincipalName'.
2575
                             '%0D%0A    eduPersonTargetedID ';
2576
        }
2577
        if ((strlen($firstname) == 0) && (strlen($displayname) == 0)) {
2578
            $errorboxstr .=
2579
            '<tr><th>First Name:</th><td>MISSING</td></tr>';
2580
            $missingattrs .= '%0D%0A    givenName (first name)';
2581
        }
2582
        if ((strlen($lastname) == 0) && (strlen($displayname) == 0)) {
2583
            $errorboxstr .=
2584
            '<tr><th>Last Name:</th><td>MISSING</td></tr>';
2585
            $missingattrs .= '%0D%0A    sn (last name)';
2586
        }
2587
        if ((strlen($displayname) == 0) &&
2588
            ((strlen($firstname) == 0) || (strlen($lastname) == 0))) {
2589
            $errorboxstr .=
2590
            '<tr><th>Display Name:</th><td>MISSING</td></tr>';
2591
            $missingattrs .= '%0D%0A    displayName';
2592
        }
2593
        $emailvalid = filter_var($emailaddr, FILTER_VALIDATE_EMAIL);
2594
        if ((strlen($emailaddr) == 0) || (!$emailvalid)) {
2595
            $errorboxstr .=
2596
            '<tr><th>Email Address:</th><td>' .
2597
            ((strlen($emailaddr) == 0) ? 'MISSING' : 'INVALID') .
2598
            '</td></tr>';
2599
            $missingattrs .= '%0D%0A    mail (email address)';
2600
        }
2601
        // CIL-326/CIL-539 - For eduGAIN IdPs attempting to get a cert,
2602
        // print out missing R&S and SIRTFI values
2603
        $idplist = Util::getIdpList();
2604
        if ($edugainandgetcert) {
2605
            if (!$idplist->isREFEDSRandS($idp)) {
2606
                $errorboxstr .=
2607
                '<tr><th><a target="_blank"
2608
                href="http://refeds.org/category/research-and-scholarship">Research
2609
                and Scholarship</a>:</th><td>MISSING</td></tr>';
2610
                $missingattrs .= '%0D%0A    http://refeds.org/category/research-and-scholarship';
2611
            }
2612
            if (!$idplist->isSIRTFI($idp)) {
2613
                $errorboxstr .=
2614
                '<tr><th><a target="_blank"
2615
                href="https://refeds.org/sirtfi">SIRTFI</a>:</th><td>MISSING</td></tr>';
2616
                $missingattrs .= '%0D%0A    http://refeds.org/sirtfi';
2617
            }
2618
        }
2619
        $student = false;
2620
        $errorboxstr .= '</table></blockquote>';
2621
        if ((strlen($emailaddr) == 0) &&
2622
            (preg_match('/student@/', $affiliation))) {
2623
            $student = true;
2624
            $errorboxstr .= '<p><b>If you are a student</b>, ' .
2625
            'you may need to ask your identity provider ' .
2626
            'to release your email address.</p>';
2627
        }
2628
2629
        // Get contacts from metadata for email addresses
2630
        $shibarray = $idplist->getShibInfo($idp);
2631
        $emailmsg = '?subject=Attribute Release Problem for CILogon' .
2632
        '&[email protected]' .
2633
        '&body=Hello, I am having trouble logging on to ' .
2634
        'https://cilogon.org/ using the ' . $idpname .
2635
        ' Identity Provider (IdP) ' .
2636
        'due to the following missing attributes:%0D%0A' .
2637
        $missingattrs;
2638
        if ($student) {
2639
            $emailmsg .= '%0D%0A%0D%0ANote that my account is ' .
2640
            'marked "student" and thus my email address may need ' .
2641
            'to be released.';
2642
        }
2643
        $emailmsg .= '%0D%0A%0D%0APlease see ' .
2644
            'http://www.cilogon.org/service/addidp for more ' .
2645
            'details. Thank you for any help you can provide.';
2646
        $errorboxstr .= '<p>Contact your identity provider to ' .
2647
        'let them know you are having having a problem logging on ' .
2648
        'to CILogon.</p><blockquote><ul>';
2649
2650
        $addrfound = false;
2651
        $name = @$shibarray['Support Name'];
2652
        $addr = @$shibarray['Support Address'];
2653
        $addr = preg_replace('/^mailto:/', '', $addr);
2654
2655
        if (strlen($addr) > 0) {
2656
            $addrfound = true;
2657
            if (strlen($name) == 0) { // Use address if no name given
2658
                $name = $addr;
2659
            }
2660
            $errorboxstr .= '<li> Support Contact: ' .
2661
                $name . ' &lt;<a href="mailto:' .
2662
                $addr . $emailmsg . '">' .
2663
                $addr . '</a>&gt;</li>';
2664
        }
2665
2666
        if (!$addrfound) {
2667
            $name = @$shibarray['Technical Name'];
2668
            $addr = @$shibarray['Technical Address'];
2669
            $addr = preg_replace('/^mailto:/', '', $addr);
2670
            if (strlen($addr) > 0) {
2671
                $addrfound = true;
2672
                if (strlen($name) == 0) { // Use address if no name given
2673
                    $name = $addr;
2674
                }
2675
                $errorboxstr .= '<li> Technical Contact: ' .
2676
                    $name . ' &lt;<a href="mailto:' .
2677
                    $addr . $emailmsg . '">' .
2678
                    $addr . '</a>&gt;</li>';
2679
            }
2680
        }
2681
2682
        if (!$addrfound) {
2683
            $name = @$shibarray['Administrative Name'];
2684
            $addr = @$shibarray['Administrative Address'];
2685
            $addr = preg_replace('/^mailto:/', '', $addr);
2686
            if (strlen($addr) > 0) {
2687
                if (strlen($name) == 0) { // Use address if no name given
2688
                    $name = $addr;
2689
                }
2690
                $errorboxstr .= '<li>Administrative Contact: ' .
2691
                    $name . ' &lt;<a href="mailto:' .
2692
                    $addr . $emailmsg.'">' .
2693
                    $addr . '</a>&gt</li>';
2694
            }
2695
        }
2696
2697
        $errorboxstr .= '</ul></blockquote>
2698
2699
        <p> Alternatively, you can contact us at the email address
2700
        at the bottom of the page.</p>
2701
        ';
2702
2703
        static::printErrorBox($errorboxstr);
2704
2705
        echo '
2706
        <div>
2707
        ';
2708
2709
        static::printFormHead($redirect, 'get');
2710
        echo $redirectform , '
2711
        <input type="submit" name="submit" class="submit"
2712
        value="Proceed" />
2713
        </form>
2714
        </div>
2715
        ';
2716
    }
2717
2718
    /**
2719
     * isEduGAINAndGetCert
2720
     *
2721
     * This function checks to see if the current session IdP is an
2722
     * eduGAIN IdP (i.e., not Registered By InCommon) and the IdP does not
2723
     * have both the REFEDS R&S and SIRTFI extensions in metadata. If so,
2724
     * check to see if the transaction could be used to fetch a
2725
     * certificate. (The only time the transaction is not used to fetch
2726
     * a cert is during OIDC without the 'getcert' scope.) If all that is
2727
     * true, then return true. Otherwise return false.
2728
     *
2729
     * @param string $idp (optional) The IdP entityID. If empty, read value
2730
     *        from PHP session.
2731
     * @param string $idpname (optional) The IdP display name. If empty,
2732
     *        read value from PHP session.
2733
     * @return bool True if the current IdP is an eduGAIN IdP without
2734
     *         both REFEDS R&S and SIRTFI, AND the session could be
2735
     *         used to get a certificate.
2736
     */
2737
    public static function isEduGAINAndGetCert($idp = '', $idpname = '')
2738
    {
2739
        $retval = false; // Assume not eduGAIN IdP and getcert
2740
2741
        // If $idp or $idpname not passed in, get from current session.
2742
        if (strlen($idp) == 0) {
2743
            $idp = Util::getSessionVar('idp');
2744
        }
2745
        if (strlen($idpname) == 0) {
2746
            $idpname = Util::getSessionVar('idpname');
2747
        }
2748
2749
        // Check if this was an OIDC transaction, and if the
2750
        // 'getcert' scope was requested.
2751
        $oidcscopegetcert = false;
2752
        $oidctrans = false;
2753
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
2754
        if (isset($clientparams['scope'])) {
2755
            $oidctrans = true;
2756
            if (preg_match(
2757
                '/edu\.uiuc\.ncsa\.myproxy\.getcert/',
2758
                $clientparams['scope']
2759
            )) {
2760
                $oidcscopegetcert = true;
2761
            }
2762
        }
2763
2764
        // First, make sure $idp was set and is not an OAuth2 IdP.
2765
        $idplist = Util::getIdpList();
2766
        if (((strlen($idp) > 0) &&
2767
            (strlen($idpname) > 0) &&
2768
            (!in_array($idpname, Util::$oauth2idps))) &&
2769
                (
2770
                // Next, check for eduGAIN without REFEDS R&S and SIRTFI
2771
                ((!$idplist->isRegisteredByInCommon($idp)) &&
2772
                       ((!$idplist->isREFEDSRandS($idp)) ||
2773
                        (!$idplist->isSIRTFI($idp))
2774
                       )
2775
                ) &&
2776
                // Next, check if user could get X509 cert,
2777
                // i.e., OIDC getcert scope, or a non-OIDC
2778
                // transaction such as PKCS12, JWS, or OAuth 1.0a
2779
                ($oidcscopegetcert || !$oidctrans)
2780
                )
2781
            ) {
2782
            $retval = true;
2783
        }
2784
2785
        return $retval;
2786
    }
2787
}
2788