Passed
Push — master ( 8355af...b6ea7c )
by Terrence
15:07
created

Content::isEduGAINAndGetCert()   C

Complexity

Conditions 13
Paths 24

Size

Total Lines 49
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 182

Importance

Changes 0
Metric Value
cc 13
eloc 25
nc 24
nop 2
dl 0
loc 49
ccs 0
cts 0
cp 0
crap 182
rs 6.6166
c 0
b 0
f 0

How to fix   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
        $idphintlist = static::getIdphintList($idps);
289
        if (!empty($idphintlist)) {
290
            $useselectedidp = true;
291
            $providerId = $idphintlist[0];
292
            // Update the IdP selection list to show just the idphintlist.
293
            foreach ($idphintlist as $value) {
294
                $newidps[$value] = $idps[$value];
295
            }
296
            $idps = $newidps;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $newidps seems to be defined by a foreach iteration on line 293. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
297
            // Re-sort the $idps by Display_Name for correct alphabetization.
298
            uasort($idps, function ($a, $b) {
299
                return strcasecmp(
300
                    $a['Display_Name'],
301
                    $b['Display_Name']
302
                );
303
            });
304
        }
305
306
        echo '
307
        <br />
308
        <div class="actionbox"';
309
310
        if (Util::getSessionVar('showhelp') == 'on') {
311
            echo ' style="width:92%;"';
312
        }
313
314
        echo '>
315
        <table class="helptable">
316
        <tr>
317
        <td class="actioncell">
318
319
          <form action="' , Util::getScriptDir() , '" method="post">
320
          <fieldset>
321
322
          <p>' , ($useselectedidp ? 'Selected' : 'Select An') ,
323
          ' Identity Provider:</p>
324
          ';
325
326
        // See if the skin has set a size for the IdP <select> list
327
        $selectsize = 4;
328
        $ils = $skin->getConfigOption('idplistsize');
329
        if ((!is_null($ils)) && ((int)$ils > 0)) {
330
            $selectsize = (int)$ils;
331
        }
332
333
        // When selected_idp is used, list size may be smaller
334
        if ($useselectedidp) {
335
            $selectsize = min($selectsize, count($idps));
336
        }
337
338
        echo '
339
          <p>
340
          <select name="providerId" id="providerId" size="' , $selectsize , '"
341
           onkeypress="enterKeySubmit(event)" ondblclick="doubleClickSubmit()"' ,
342
           // Hide the drop-down arrow in Firefox and Chrome
343
          ($useselectedidp ?
344
              'style="-moz-appearance:none;-webkit-appearance:none"' : '') ,
345
           '>
346
        ';
347
348
        foreach ($idps as $entityId => $names) {
349
            echo '    <option value="' , $entityId , '"';
350
            if ($entityId == $providerId) {
351
                echo ' selected="selected"';
352
            }
353
            echo '>' , Util::htmlent($names['Display_Name']) , '</option>' , "\n    ";
354
        }
355
356
        echo '  </select>
357
        </p>
358
359
        <p id="listsearch" class="zeroheight">
360
        <label for="searchlist" class="helpcursor" title="' ,
361
        $searchtext , '">Search:</label>
362
        <input type="text" name="searchlist" id="searchlist" value=""
363
        size="30" onkeyup="searchOptions(this.value)"
364
        title="' , $searchtext , '" />
365
    <!--[if IE]><input type="text" style="display:none;" disabled="disabled" size="1"/><![endif]-->
366
        </p>
367
        ';
368
369
        if ($showremember) {
370
            echo '
371
            <p>
372
            <label for="keepidp" title="' , $helptext ,
373
            '" class="helpcursor">Remember this selection:</label>
374
            <input type="checkbox" name="keepidp" id="keepidp" ' ,
375
            ((strlen($keepidp) > 0) ? 'checked="checked" ' : '') ,
376
            'title="' , $helptext , '" class="helpcursor" />
377
            </p>
378
            ';
379
        }
380
381
        echo '
382
        <p class="silvercheckbox">
383
        <label for="silveridp">Request Silver:</label>
384
        <input type="checkbox" name="silveridp" id="silveridp"/>
385
        </p>
386
387
        <p>
388
        ';
389
390
        echo Util::getCsrf()->hiddenFormElement();
391
392
        $lobtext = static::getLogOnButtonText();
393
394
        echo '
395
        <input type="submit" name="submit" class="submit helpcursor"
396
        title="Continue to the selected identity provider."
397
        value="' , $lobtext , '" id="wayflogonbutton" />
398
        <input type="hidden" name="previouspage" value="WAYF" />
399
        <input type="submit" name="submit" class="submit helpcursor"
400
        title="Cancel authentication and navigate away from this site."
401
        value="Cancel" id="wayfcancelbutton" />
402
        </p>
403
        ';
404
405
        $logonerror = Util::getSessionVar('logonerror');
406
        if (strlen($logonerror) > 0) {
407
            echo "<p class=\"logonerror\">$logonerror</p>";
408
            Util::unsetSessionVar('logonerror');
409
        }
410
411
        echo '
412
        <p class="privacypolicy">
413
        By selecting "' , $lobtext , '", you agree to <a target="_blank"
414
        href="http://ca.cilogon.org/policy/privacy">CILogon\'s privacy
415
        policy</a>.
416
        </p>
417
418
        </fieldset>
419
420
        </form>
421
      </td>
422
      ';
423
424
        if (Util::getSessionVar('showhelp') == 'on') {
425
            echo '
426
          <td class="helpcell">
427
          <div>
428
          ';
429
430
            if ($incommonidps) { // InCommon IdPs only means running from /testidp/
431
                echo '
432
                <p>
433
                CILogon facilitates secure access to CyberInfrastructure
434
                (<acronym title="CyberInfrastructure">CI</acronym>). In
435
                order to test your identity provider with the CILogon Service,
436
                you must first Log On. If your preferred identity provider is
437
                not listed, please contact <a
438
                href="mailto:[email protected]">[email protected]</a>, and
439
                we will try to add your identity provider in the future.
440
                </p>
441
                ';
442
            } else { // If not InCommon only, print help text for OpenID providers.
443
                echo '
444
                <p>
445
                CILogon facilitates secure access to CyberInfrastructure
446
                (<acronym title="CyberInfrastructure">CI</acronym>).
447
                In order to use the CILogon Service, you must first select
448
                an identity provider. An identity provider (IdP) is an
449
                organization where you have an account and can log on
450
                to gain access to online services.
451
                </p>
452
                <p>
453
                If you are a faculty, staff, or student member of a university
454
                or college, please select it for your identity provider.
455
                If your school is not listed, please contact <a
456
                href="mailto:[email protected]">[email protected]</a>, and we will
457
                try to add your school in the future.
458
                </p>
459
                ';
460
461
                $googleauthz = Util::getAuthzUrl('Google');
462
                if ((isset($idps[$googleauthz])) &&
463
                    ($skin->idpAvailable($googleauthz))) {
464
                    echo '
465
                  <p>
466
                  If you have a <a target="_blank"
467
                  href="https://myaccount.google.com">Google</a>
468
                  account, you can select it for
469
                  authenticating to the CILogon Service.
470
                  </p>
471
                  ';
472
                }
473
                $githubauthz = Util::getAuthzUrl('GitHub');
474
                if ((isset($idps[$githubauthz])) &&
475
                    ($skin->idpAvailable($githubauthz))) {
476
                    echo '
477
                  <p>
478
                  If you have a <a target="_blank"
479
                  href="https://github.com/settings/profile">GitHub</a>
480
                  account, you can select it for
481
                  authenticating to the CILogon Service.
482
                  </p>
483
                  ';
484
                }
485
                $orcidauthz = Util::getAuthzUrl('ORCID');
486
                if ((isset($idps[$orcidauthz])) &&
487
                    ($skin->idpAvailable($orcidauthz))) {
488
                    echo '
489
                  <p>
490
                  If you have a <a target="_blank"
491
                  href="https://orcid.org/my-orcid">ORCID</a>
492
                  account, you can select it for
493
                  authenticating to the CILogon Service.
494
                  </p>
495
                  ';
496
                }
497
            }
498
499
            echo '
500
          </div>
501
          </td>
502
          ';
503
        }
504
        echo '
505
      </tr>
506
      </table>
507
      </div>
508
      ';
509
    }
510
511
    /**
512
     * printTwoFactorBox
513
     *
514
     * This function prints the 'Manage Two-Factor' box on the main page.
515
     */
516
    public static function printTwoFactorBox()
517
    {
518
        $managetwofactortext = 'Enable or disable two-factor authentication for your account';
519
520
        echo '
521
        <div class="twofactoractionbox"';
522
523
        $style = ''; // Might add extra CSS to the twofactoractionbox
524
        if (Util::getSessionVar('showhelp') == 'on') {
525
            $style .= "width:92%;";
526
        }
527
        if (TwoFactor::getEnabled() != 'none') {
528
            $style .= "display:block;"; // Force display if two-factor enabled
529
        }
530
        if (strlen($style) > 0) {
531
            echo ' style="' , $style , '"';
532
        }
533
534
        echo '>
535
        <table class="helptable">
536
        <tr>
537
        <td class="actioncell">
538
        ';
539
540
        static::printFormHead();
541
542
        $twofactorname = TwoFactor::getEnabledName();
543
        if ($twofactorname == 'Disabled') {
544
            $twofactorname = 'Two-Factor Authentication Disabled';
545
        } else {
546
            $twofactorname .= ' Enabled';
547
        }
548
        echo '
549
          <p>' , $twofactorname , '</p>
550
551
          <p>
552
          <input type="submit" name="submit" class="submit helpcursor"
553
          title="' , $managetwofactortext , '" value="Manage Two-Factor" />
554
          </p>
555
          </form>
556
        </td>
557
        ';
558
559
        if (Util::getSessionVar('showhelp') == 'on') {
560
            echo '
561
            <td class="helpcell">
562
            <div>
563
            <p>
564
            Two-factor authentication provides extra security on your account by
565
            using a physical device (e.g., your mobile phone) to generate a one
566
            time password which you enter after you log in to your selected
567
            Identity Provider. Click the "Manage Two-Factor" button to enable or
568
            disable two-factor authentication.
569
            </p>
570
            </div>
571
            </td>
572
            ';
573
        }
574
575
        echo '
576
        </tr>
577
        </table>
578
        </div> <!-- twofactoractionbox -->
579
        ';
580
    }
581
582
    /**
583
     * printTwoFactorPage
584
     *
585
     * This function prints out the Manage Two-Factor Authentication page.
586
     * Display of which two-factor types are available to the user is
587
     * controlled by CSS. From this page, the user can Enable or Disable
588
     * various two-factor authentication methods.
589
     */
590
    public static function printTwoFactorPage()
591
    {
592
        Util::setSessionVar('stage', 'managetwofactor'); // For Show/Hide Help button
593
594
        static::printHeader('Manage Two-Factor Authentication');
595
596
        $twofactorname = TwoFactor::getEnabledName();
597
598
        echo '
599
        <div class="boxed">
600
        ';
601
        static::printHelpButton();
602
        echo'
603
        <h2>Two-Factor Authentication</h2>
604
        <div class="actionbox">
605
        <p><b>Two-Factor Authentication:</b></p>
606
        <p>' , $twofactorname , '</p>
607
        </div> <!-- actionbox -->
608
        ';
609
610
        if (Util::getSessionVar('showhelp') == 'on') {
611
            echo '
612
            <div>
613
            <p>
614
            Multi-factor authentication requires a user to present two or more
615
            distinct authentication factors from the following categories:
616
            </p>
617
            <ul>
618
            <li>Something you <b>know</b> (e.g., username and password)</li>
619
            <li>Something you <b>have</b> (e.g., mobile phone or hardware
620
                token)</li>
621
            <li>Something you <b>are</b> (e.g., fingerprint)</li>
622
            </ul>
623
            <p>
624
            Below you can configure a second factor using something you <b>have</b>,
625
            i.e., your mobile phone. You will first be prompted to register the
626
            second-factor device, typically by installing specific apps on your
627
            phone and completing a registration process. Then you will need to log
628
            in with the second factor to verify the registration process.
629
            </p>
630
            <p>
631
            Once you have successfully enabled two-factor authentication, you will
632
            be prompted on future CILogon Service logons for your second-factor
633
            authentication.  You can select the second-factor authentication to use
634
            (or not) by clicking the "Enable" (or "Disable") button.
635
            </p>
636
            </div>
637
            ';
638
        }
639
640
        echo '
641
        <table class="twofactortypes">
642
        <tr class="twofactorgooglerow"' ,
643
        (TwoFactor::isEnabled('ga') ? ' style="display:table-row;"' : '') ,
644
        '>
645
        <th>Google Authenticator</th>
646
        <td>
647
        ';
648
        static::printFormHead();
649
        echo '
650
        <input type="hidden" name="twofactortype" value="ga" />
651
        <input type="submit" name="submit" class="submit" value="' ,
652
        (TwoFactor::isEnabled('ga') ? 'Disable' : 'Enable') ,
653
        '" />
654
        </form>
655
        </td>
656
        </tr>
657
        ';
658
659
        if (Util::getSessionVar('showhelp') == 'on') {
660
            echo '
661
            <tr>
662
            <td colspan="4">
663
            Google Authenticator is an app available for Android OS, Apple
664
            iOS, and BlackBerry OS. The app generates one-time password tokens.
665
            After you have logged on to the CILogon Service with your chosen
666
            Identity Provider, you would be prompted to use the Google
667
            Authenticator app to generate a second passcode and enter it.
668
            </td>
669
            </tr>
670
            ';
671
        }
672
673
        echo '
674
        <tr class="twofactorduorow"' ,
675
        (TwoFactor::isEnabled('duo') ? '
676
            style="display:table-row;border-top-width:1px"' : '') ,
677
        '>
678
        <th>Duo Security</th>
679
        <td>
680
        ';
681
        static::printFormHead();
682
        echo '
683
        <input type="hidden" name="twofactortype" value="duo" />
684
        <input type="submit" name="submit" class="submit" value="' ,
685
        (TwoFactor::isEnabled('duo') ? 'Disable' : 'Enable') ,
686
        '" />
687
        </form>
688
        </td>
689
        </tr>
690
        ';
691
692
        if (Util::getSessionVar('showhelp') == 'on') {
693
            echo '
694
            <tr>
695
            <td colspan="4">
696
            Duo Security is an app available for most smartphones, including
697
            Android OS, Apple iOS, Blackberry OS, Palm, Symbian, and Windows
698
            Mobile. The app can respond to "push" notifications, and can also
699
            generate one-time password tokens. After you have logged on to the
700
            CILogon Service with your chosen Identity Provider, you would be
701
            prompted to select a Duo log in method and then authenticate with
702
            your mobile phone.
703
            </td>
704
            </tr>
705
            ';
706
        }
707
708
        echo '
709
        </table>
710
711
        <noscript>
712
        <div class="nojs smaller">
713
        Javascript is disabled. In order to activate the link
714
        below, please enable Javascript in your browser.
715
        </div>
716
        </noscript>
717
718
        <p>
719
        <a href="javascript:showHideDiv(\'lostdevice\',-1)">Lost your phone?</a>
720
        </p>
721
        <div id="lostdevice" style="display:none">
722
        <p>
723
        If you have lost your phone, you can click on the
724
        "I Lost My Phone" button below to remove all two-factor methods
725
        from your account.  This will send an email message to the system
726
        adminisrator and to the email address provided by your Identity
727
        Provider.  You can then use the CILogon Service without
728
        two-factor authentication enabled. You will need to re-register your
729
        device with you want to use it for two-factor authentication again.
730
        <br/>
731
        ';
732
        static::printFormHead();
733
        echo '
734
        <input type="submit" name="submit" class="submit"
735
        value="I Lost My Phone" />
736
        </p>
737
        </div>
738
739
        <p>
740
        <input type="submit" name="submit" class="submit"
741
        value="Done with Two-Factor" />
742
        </p>
743
        </form>
744
745
        </div> <!-- boxed -->
746
        ';
747
        static::printFooter();
748
    }
749
750
    /**
751
     * handleEnableDisableTwoFactor
752
     *
753
     * This function is called when the user clicks either an 'Enable' or
754
     * 'Disable' button from the Manage Two-Factor page, or when the user
755
     * clicks 'Verify' on the Google Authenticator Registration page.
756
     * The passed-in parameter tells which type of button was pressed.
757
     * If 'Disable', then simply set 'enabled=none' in the datastore and
758
     * display the Manage Two-Factor page again. If 'Enable' or 'Verify',
759
     * check the 'twofactortype' hidden form variable for which two-factor
760
     * authentication method is to be enabled. Then print out that
761
     * two-factor page. Note that TwoFactor::printPage() does the work of
762
     * figuring out if the user has registered the phone yet or not, and
763
     * displays the appropriate page.
764
     *
765
     * @param bool $enable (Optional) True for 'enable', false for 'disable'.
766
     *        Defaults to false (for 'disable').
767
     */
768
    public static function handleEnableDisableTwoFactor($enable = false)
769
    {
770
        if ($enable) {
771
            $twofactortype = Util::getPostVar('twofactortype');
772
            if (strlen($twofactortype) > 0) {
773
                TwoFactor::printPage($twofactortype);
774
            } else {
775
                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

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

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

990
                /** @scrutinizer ignore-call */ 
991
                printMainPage();
Loading history...
991
            } elseif ($stage == 'managetwofactor') {
992
                static::printTwoFactorPage();
993
            } else {
994
                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

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

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

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

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

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

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

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

2077
                                  'user attributes. ' . /** @scrutinizer ignore-type */ $errstr);
Loading history...
2078
                Util::sendErrorAlert(
2079
                    'dbService Error',
2080
                    'Error calling dbservice action "getLastArchivedUser" in ' .
2081
                    'printUserChangedPaged() method. ' . $errstr
2082
                );
2083
                Util::unsetAllUserSessionVars();
2084
                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

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

2567
        /** @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...
2568
        $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

2568
        /** @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...
2569
        $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

2569
        /** @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...
2570
        $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

2570
        /** @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...
2571
        $itrustuin,
0 ignored issues
show
Unused Code introduced by
The parameter $itrustuin is not used and could be removed. ( Ignorable by Annotation )

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

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

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

Loading history...
2572
        $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

2572
        /** @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...
2573
        $redirect,
2574
        $redirectform,
2575
        $edugainandgetcert
2576
    ) {
2577
        $errorboxstr =
2578
        '<p>There was a problem logging on. Your identity
2579
        provider has not provided CILogon with required information.</p>
2580
        <blockquote><table cellpadding="5">';
2581
2582
        $missingattrs = '';
2583
        // Show user which attributes are missing
2584
        if ((strlen($ePPN) == 0) && (strlen($ePTID) == 0)) {
2585
            $errorboxstr .=
2586
            '<tr><th>ePTID:</th><td>MISSING</td></tr>
2587
            <tr><th>ePPN:</th><td>MISSING</td></tr>';
2588
            $missingattrs .= '%0D%0A    eduPersonPrincipalName'.
2589
                             '%0D%0A    eduPersonTargetedID ';
2590
        }
2591
        if ((strlen($firstname) == 0) && (strlen($displayname) == 0)) {
2592
            $errorboxstr .=
2593
            '<tr><th>First Name:</th><td>MISSING</td></tr>';
2594
            $missingattrs .= '%0D%0A    givenName (first name)';
2595
        }
2596
        if ((strlen($lastname) == 0) && (strlen($displayname) == 0)) {
2597
            $errorboxstr .=
2598
            '<tr><th>Last Name:</th><td>MISSING</td></tr>';
2599
            $missingattrs .= '%0D%0A    sn (last name)';
2600
        }
2601
        if ((strlen($displayname) == 0) &&
2602
            ((strlen($firstname) == 0) || (strlen($lastname) == 0))) {
2603
            $errorboxstr .=
2604
            '<tr><th>Display Name:</th><td>MISSING</td></tr>';
2605
            $missingattrs .= '%0D%0A    displayName';
2606
        }
2607
        $emailvalid = filter_var($emailaddr, FILTER_VALIDATE_EMAIL);
2608
        if ((strlen($emailaddr) == 0) || (!$emailvalid)) {
2609
            $errorboxstr .=
2610
            '<tr><th>Email Address:</th><td>' .
2611
            ((strlen($emailaddr) == 0) ? 'MISSING' : 'INVALID') .
2612
            '</td></tr>';
2613
            $missingattrs .= '%0D%0A    mail (email address)';
2614
        }
2615
        // CIL-326/CIL-539 - For eduGAIN IdPs attempting to get a cert,
2616
        // print out missing R&S and SIRTFI values
2617
        $idplist = Util::getIdpList();
2618
        if ($edugainandgetcert) {
2619
            if (!$idplist->isREFEDSRandS($idp)) {
2620
                $errorboxstr .=
2621
                '<tr><th><a target="_blank"
2622
                href="http://refeds.org/category/research-and-scholarship">Research
2623
                and Scholarship</a>:</th><td>MISSING</td></tr>';
2624
                $missingattrs .= '%0D%0A    http://refeds.org/category/research-and-scholarship';
2625
            }
2626
            if (!$idplist->isSIRTFI($idp)) {
2627
                $errorboxstr .=
2628
                '<tr><th><a target="_blank"
2629
                href="https://refeds.org/sirtfi">SIRTFI</a>:</th><td>MISSING</td></tr>';
2630
                $missingattrs .= '%0D%0A    http://refeds.org/sirtfi';
2631
            }
2632
        }
2633
        $student = false;
2634
        $errorboxstr .= '</table></blockquote>';
2635
        if ((strlen($emailaddr) == 0) &&
2636
            (preg_match('/student@/', $affiliation))) {
2637
            $student = true;
2638
            $errorboxstr .= '<p><b>If you are a student</b>, ' .
2639
            'you may need to ask your identity provider ' .
2640
            'to release your email address.</p>';
2641
        }
2642
2643
        // Get contacts from metadata for email addresses
2644
        $shibarray = $idplist->getShibInfo($idp);
2645
        $emailmsg = '?subject=Attribute Release Problem for CILogon' .
2646
        '&[email protected]' .
2647
        '&body=Hello, I am having trouble logging on to ' .
2648
        'https://cilogon.org/ using the ' . $idpname .
2649
        ' Identity Provider (IdP) ' .
2650
        'due to the following missing attributes:%0D%0A' .
2651
        $missingattrs;
2652
        if ($student) {
2653
            $emailmsg .= '%0D%0A%0D%0ANote that my account is ' .
2654
            'marked "student" and thus my email address may need ' .
2655
            'to be released.';
2656
        }
2657
        $emailmsg .= '%0D%0A%0D%0APlease see ' .
2658
            'http://www.cilogon.org/service/addidp for more ' .
2659
            'details. Thank you for any help you can provide.';
2660
        $errorboxstr .= '<p>Contact your identity provider to ' .
2661
        'let them know you are having having a problem logging on ' .
2662
        'to CILogon.</p><blockquote><ul>';
2663
2664
        $addrfound = false;
2665
        $name = @$shibarray['Support Name'];
2666
        $addr = @$shibarray['Support Address'];
2667
        $addr = preg_replace('/^mailto:/', '', $addr);
2668
2669
        if (strlen($addr) > 0) {
2670
            $addrfound = true;
2671
            if (strlen($name) == 0) { // Use address if no name given
2672
                $name = $addr;
2673
            }
2674
            $errorboxstr .= '<li> Support Contact: ' .
2675
                $name . ' &lt;<a href="mailto:' .
2676
                $addr . $emailmsg . '">' .
2677
                $addr . '</a>&gt;</li>';
2678
        }
2679
2680
        if (!$addrfound) {
2681
            $name = @$shibarray['Technical Name'];
2682
            $addr = @$shibarray['Technical Address'];
2683
            $addr = preg_replace('/^mailto:/', '', $addr);
2684
            if (strlen($addr) > 0) {
2685
                $addrfound = true;
2686
                if (strlen($name) == 0) { // Use address if no name given
2687
                    $name = $addr;
2688
                }
2689
                $errorboxstr .= '<li> Technical Contact: ' .
2690
                    $name . ' &lt;<a href="mailto:' .
2691
                    $addr . $emailmsg . '">' .
2692
                    $addr . '</a>&gt;</li>';
2693
            }
2694
        }
2695
2696
        if (!$addrfound) {
2697
            $name = @$shibarray['Administrative Name'];
2698
            $addr = @$shibarray['Administrative Address'];
2699
            $addr = preg_replace('/^mailto:/', '', $addr);
2700
            if (strlen($addr) > 0) {
2701
                if (strlen($name) == 0) { // Use address if no name given
2702
                    $name = $addr;
2703
                }
2704
                $errorboxstr .= '<li>Administrative Contact: ' .
2705
                    $name . ' &lt;<a href="mailto:' .
2706
                    $addr . $emailmsg.'">' .
2707
                    $addr . '</a>&gt</li>';
2708
            }
2709
        }
2710
2711
        $errorboxstr .= '</ul></blockquote>
2712
2713
        <p> Alternatively, you can contact us at the email address
2714
        at the bottom of the page.</p>
2715
        ';
2716
2717
        static::printErrorBox($errorboxstr);
2718
2719
        echo '
2720
        <div>
2721
        ';
2722
2723
        static::printFormHead($redirect, 'get');
2724
        echo $redirectform , '
2725
        <input type="submit" name="submit" class="submit"
2726
        value="Proceed" />
2727
        </form>
2728
        </div>
2729
        ';
2730
    }
2731
2732
    /**
2733
     * getIdphintList
2734
     *
2735
     * This function adds support for AARC-G049 "IdP Hinting". It
2736
     * searches both the GET query parameters and the OIDC client
2737
     * parameters passed to the 'authorize' endpoint for a parameter
2738
     * named either 'selected_idp' or 'idphint'. This parameter can be
2739
     * a single entityId or a comma-separated list of entityIds.
2740
     * The entries in the list are processed to remove any 'chained'
2741
     * idphints and also to transform OIDC 'issuer' values into
2742
     * CILogon-specific 'entityIds' as used in the 'Select an IdP'
2743
     * list. Any idps which are not in the current skin's 'Select
2744
     * an IdP' list are removed. The resulting processed list of
2745
     * entityIds is returned, which may be an empty array.
2746
     *
2747
     * @param array $idps (Optional) A list of valid (i.e., whitelisted) IdPs.
2748
     *        If this list is empty, then use the current skin's IdP list.
2749
     * @return array A list of entityIds / OIDC provider URLs extracted from
2750
     *         a passed-in parameter 'selected_idp' or 'idphint'. This array
2751
     *         may be empty if no such parameter was found, or if the
2752
     *         entityIds in the list were not valid.
2753
     */
2754
    public static function getIdphintList($idps = [])
2755
    {
2756
        // Check for either 'selected_idp' or 'idphint' parameter that was
2757
        // passed in via a query parameter, either for an OAuth transaction
2758
        // or just 'normally'. Note that if both 'selected_idp' and
2759
        // 'idphint' were passed, 'idphint' takes priority.
2760
2761
        $hintarray = array();
2762
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
2763
2764
        $hintstr = '';
2765
        if (!empty(@$clientparams['idphint'])) {
2766
            $hintstr = $clientparams['idphint'];
2767
        } elseif (!empty(Util::getGetVar('idphint'))) {
2768
            $hintstr = Util::getGetVar('idphint');
2769
        } elseif (!empty(@$clientparams['selected_idp'])) {
2770
            $hintstr = $clientparams['selected_idp'];
2771
        } elseif (!empty(Util::getGetVar('selected_idp'))) {
2772
            $hintstr = Util::getGetVar('selected_idp');
2773
        }
2774
2775
        if (!empty($hintstr)) {
2776
            // Split on comma to account for multiple idps
2777
            $hintarray = explode(',', $hintstr);
2778
2779
            // Process the list of IdPs to transform them appropriately.
2780
            foreach ($hintarray as &$value) {
2781
                // Check for 'chained' idp hints, and remove the GET params.
2782
                if (preg_match('%([^\?]*)\?%', $value, $matches)) {
2783
                    $value = $matches[1];
2784
                }
2785
                // Also, check for OIDC issuers and transform them into
2786
                // CILogon-specific values used in the 'Select an IdP' list.
2787
                if (preg_match('%https://accounts.google.com%', $value)) {
2788
                    $value = 'https://accounts.google.com/o/oauth2/auth';
2789
                } elseif (preg_match('%https://github.com%', $value)) {
2790
                    $value = 'https://github.com/login/oauth/authorize';
2791
                } elseif (preg_match('%https://orcid.org%', $value)) {
2792
                    $value = 'https://orcid.org/oauth/authorize';
2793
                }
2794
            }
2795
            unset($value); // Break the reference with the last element.
2796
2797
            // Remove any non-whitelisted IdPs from the hintarray.
2798
            if (empty($idps)) {
2799
                $idps = static::getCompositeIdPList();
2800
            }
2801
            foreach ($hintarray as $value) {
2802
                if (!isset($idps[$value])) {
2803
                    if (($key = array_search($value, $hintarray)) !== false) {
2804
                        unset($hintarray[$key]);
2805
                    }
2806
                }
2807
            }
2808
        }
2809
        return $hintarray;
2810
    }
2811
}
2812