Completed
Push — master ( 88dd7e...5967cb )
by Terrence
11:15
created

index-functions.php ➔ getErrorStatusText()   D

Complexity

Conditions 18
Paths 40

Size

Total Lines 61

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 40
nop 2
dl 0
loc 61
rs 4.8666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains functions called by index-site.php. The index-site.php
5
 * file should include this file with the following statement at the top:
6
 *
7
 * require_once __DIR__ . '/index-functions.php';
8
 */
9
10
use CILogon\Service\Util;
11
use CILogon\Service\Content;
12
use CILogon\Service\DBService;
13
use CILogon\Service\Loggit;
14
15
/**
16
 * printLogonPage
17
 *
18
 * This function prints out the HTML for the main cilogon.org page.
19
 * Explanatory text is shown as well as a button to log in to an IdP
20
 * and get rerouted to the Shibboleth protected getuser script.
21
 */
22
function printLogonPage()
23
{
24
    $log = new Loggit();
25
    $log->info('Welcome page hit.');
26
27
    Util::setSessionVar('stage', 'logon'); // For Show/Hide Help button clicks
28
29
    Content::printHeader(
30
        'Welcome To The CILogon OpenID Connect Authorization Service'
31
    );
32
33
    $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
34
35
    // If the <hideportalinfo> option is set, do not show the portal info
36
    // if the OIDC redirect_uri or client_id is in the portal list.
37
    $showportalinfo = true;
38
    $skin = Util::getSkin();
39
    if (
40
        ((int)$skin->getConfigOption('portallistaction', 'hideportalinfo') == 1) &&
41
        (
42
            ($skin->inPortalList($clientparams['redirect_uri'])) ||
43
            ($skin->inPortalList($clientparams['client_id']))
44
        )
45
    ) {
46
        $showportalinfo = false;
47
    }
48
49
    if ($showportalinfo) {
50
        printOIDCConsent();
51
    }
52
    Content::printWAYF();
53
    Content::printFooter();
54
}
55
56
/**
57
 * printOIDCErrorPage
58
 *
59
 * This function prints out the HTML for the page when the the various
60
 * OIDC parameters sent by the client are missing or bad.
61
 */
62
function printOIDCErrorPage()
63
{
64
    $log = new Loggit();
65
    $log->warn('Missing or invalid OIDC parameters.');
66
67
    Content::printHeader('CILogon Authorization Endpoint');
68
    Content::printCollapseBegin('oidcdefault', 'CILogon OIDC Authorization Endpoint', false);
69
70
    echo '
71
        <div class="card-body px-5">
72
          <div class="card-text my-2">
73
            You have reached the CILogon OAuth2/OpenID Connect (OIDC)
74
            Authorization Endpoint. This service is for use by OAuth2/OIDC
75
            Relying Parties (RPs) to authorize users of the CILogon Service.
76
            End users should not normally see this page.
77
          </div> <!-- end row -->
78
    ';
79
80
    $client_error_msg = Util::getSessionVar('client_error_msg');
81
    Util::unsetSessionVar('client_error_msg');
82
    if (strlen($client_error_msg) > 0) {
83
        echo '<div class="alert alert-danger" role="alert">', $client_error_msg, '</div>';
84
    } else {
85
        echo '
86
          <div class="card-text my-2">
87
            Possible reasons for seeing this page include:
88
          </div> <!-- end row -->
89
          <div class="card-text my-2">
90
            <ul>
91
              <li>You navigated directly to this page.</li>
92
              <li>You clicked your browser\'s "Back" button.</li>
93
              <li>There was a problem with the OpenID Connect client.</li>
94
            </ul>
95
          </div> <!-- end row -->
96
        ';
97
    }
98
99
    echo '
100
          <div class="card-text my-2">
101
            For assistance, please contact us at the email address at the
102
            bottom of the page.
103
          </div>
104
          <div class="card-text my-2">
105
            <strong>Note:</strong> You must enable cookies in your web
106
            browser to use this site.
107
          </div>
108
        </div> <!-- end card-body -->
109
    ';
110
111
    Content::printCollapseEnd();
112
    Content::printFooter();
113
}
114
115
/**
116
 * printMainPage
117
 *
118
 * This function is poorly named for the OIDC case, but is called by
119
 * gotUserSuccess, so the name stays. This function is called once the
120
 * user has successfully logged on at the selected IdP. In the OIDC
121
 * case, the user's UID is then paired with the OIDC 'code' and
122
 * 'authntime' in the datastore so that it can be fetched later when
123
 * the OIDC client wants to get userinfo or a certificate. There
124
 * really isn't anything 'printed' to the user here. Control is
125
 * simply redirected to the OIDC client with appropriate success or
126
 * error response.
127
 */
128
function printMainPage()
129
{
130
    $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
131
    $redirect = 'Location: ' . $clientparams['redirect_url'];
132
133
    $log = new Loggit();
134
    $log->info('Calling setTransactionState dbService method...');
135
136
    $dbs = new DBService();
137
    if (
138
        ($dbs->setTransactionState(
139
            $clientparams['code'],
140
            Util::getSessionVar('user_uid'),
141
            Util::getSessionVar('authntime'),
142
            Util::getLOA(),
143
            Util::getSessionVar('myproxyinfo')
144
        )) && (!($dbs->status & 1))
145
    ) { // STATUS_OK codes are even
146
        // CIL-360 - Check for Response Mode
147
        // http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
148
        if (isset($clientparams['response_mode'])) {
149
            $responsemode = $clientparams['response_mode'];
150
            if ($responsemode == 'query') {
151
                // This is the default mode for 'code' response
152
            } elseif ($responsemode == 'fragment') {
153
                // Replace '?' with '#'
154
                $redirect = str_replace('?', '#', $redirect);
155
            } elseif ($responsemode == 'form_post') {
156
                // https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
157
                // At this point, $clientparams['redirect_url'] contains
158
                // both the callback uri and all query string parameters
159
                // that should be passed to the callback uri. We need
160
                // to separate the two so we can put the query parameters
161
                // into hidden <input> fields in the output form.
162
                $orig_redirect_uri = $clientparams['redirect_uri'];
163
                $full_redirect_url = $clientparams['redirect_url'];
164
                $queryparams = str_replace(
165
                    $orig_redirect_uri . '?',
166
                    '',
167
                    $full_redirect_url
168
                );
169
                Util::unsetClientSessionVars();
170
                // Util::unsetAllUserSessionVars();
171
                // Get the components of the response (split by '&')
172
                $comps = explode('&', $queryparams);
173
                $outform = '<html>
174
  <head><title>Submit This Form</title></head>
175
  <body onload="javascript:document.forms[0].submit()">
176
    <form method="post" action="' . $orig_redirect_uri . '">
177
    ';
178
                foreach ($comps as $value) {
179
                    $params = explode('=', $value);
180
                    $outform .= '<input type="hidden" name="' . $params[0] .
181
                         '" value="' . html_entity_decode($params[1]) . '"/>';
182
                }
183
                $outform .= '
184
    </form>
185
  </body>
186
</html>';
187
                $log->info(
188
                    'response_mode=form_post; outputting form' . "\n" .
189
                    $outform
190
                );
191
                echo $outform;
192
                exit; // No further processing necessary
193
            }
194
        }
195
        $log->info('setTransactionState succeeded, redirect to ' . $redirect);
196
        // CIL-507 Special log message for XSEDE
197
        $log->info('USAGE email="' . Util::getSessionVar('email') .
198
                   '" client="' . $clientparams['client_name'] . '"');
199
    } else { // dbservice error
200
        $errstr = '';
201
        if (!is_null($dbs->status)) {
202
            $errstr = array_search($dbs->status, DBService::$STATUS);
203
        }
204
        $redirect = 'Location: ' . $clientparams['redirect_uri'] .
205
            (preg_match('/\?/', $clientparams['redirect_uri']) ? '&' : '?') .
206
            'error=server_error&error_description=' .
207
            'Unable%20to%20associate%20user%20UID%20with%20OIDC%20code' .
208
            ((isset($clientparams['state'])) ?
209
                '&state=' . $clientparams['state'] : '');
210
        $log->info("setTransactionState failed $errstr, redirect to $redirect");
211
        Util::sendErrorAlert(
212
            'dbService Error',
213
            'Error calling dbservice action "setTransactionState" in ' .
214
            'OIDC authorization endpoint\'s printMainPage() method. ' .
215
            $errstr . ' Redirected to ' . $redirect
216
        );
217
        Util::unsetUserSessionVars();
218
    }
219
220
    Util::unsetClientSessionVars();
221
    // Util::unsetAllUserSessionVars();
222
    header($redirect);
223
    exit; // No further processing necessary
224
}
225
226
/**
227
 * printOIDCConsent
228
 *
229
 * This function prints out the block showing the scopes requested by the
230
 * OIDC client.
231
 */
232
function printOIDCConsent()
233
{
234
    // Look in the 'scope' OIDC parameter to see which attributes are
235
    // being requested. The values we care about are 'email', 'profile'
236
    // (for first/last name), and 'edu.uiuc.ncsa.myproxy.getcert'
237
    // (which gives a certificate containing first/last name AND email).
238
    // Anything else should just be output as-is.
239
    $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
240
    $scopes = preg_split("/[\s\+]+/", $clientparams['scope']);
241
    $scopes = array_unique($scopes); // Remove any duplicates
242
243
    // CIL-779 Show only those scopes which have been registered, i.e.,
244
    // compute the set intersection of requested and registered scopes.
245
    $client_scopes = json_decode($clientparams['client_scopes'], true);
246
    if (!is_null($client_scopes)) {
247
        $scopes = array_intersect($scopes, $client_scopes);
248
    }
249
250
    Content::printCollapseBegin('oidcconsent', 'Consent to Attribute Release', false);
251
252
    $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
253
    echo '
254
        <div class="card-body px-5">
255
          <div class="card-text my-2">
256
            <a target="_blank" href="' ,
257
            htmlspecialchars($clientparams['client_home_url']) , '">',
258
            htmlspecialchars($clientparams['client_name']) , '</a>' ,
259
            ' requests access to the following information.
260
            If you do not approve this request, do not proceed.
261
          </div> <!-- end row -->
262
          <ul>
263
    ';
264
265
    if (in_array('openid', $scopes)) {
266
        echo '<li>Your CILogon user identifier</li>';
267
        $scopes = array_diff($scopes, ['openid']);
268
    }
269
    if (
270
        (in_array('profile', $scopes)) ||
271
        (in_array('edu.uiuc.ncsa.myproxy.getcert', $scopes))
272
    ) {
273
        echo '<li>Your name</li>';
274
        $scopes = array_diff($scopes, ['profile']);
275
    }
276
    if (
277
        (in_array('email', $scopes)) ||
278
        (in_array('edu.uiuc.ncsa.myproxy.getcert', $scopes))
279
    ) {
280
        echo '<li>Your email address</li>';
281
        $scopes = array_diff($scopes, ['email']);
282
    }
283
    if (in_array('org.cilogon.userinfo', $scopes)) {
284
        echo '<li>Your username and affiliation from your identity provider</li>';
285
        $scopes = array_diff($scopes, ['org.cilogon.userinfo']);
286
    }
287
    if (in_array('edu.uiuc.ncsa.myproxy.getcert', $scopes)) {
288
        echo '<li>A certificate that allows "' ,
289
        htmlspecialchars($clientparams['client_name']) ,
290
        '" to act on your behalf</li>';
291
        $scopes = array_diff($scopes, ['edu.uiuc.ncsa.myproxy.getcert']);
292
    }
293
    // Output any remaining scopes as-is
294
    foreach ($scopes as $value) {
295
        echo '<li>', $value , '</li>';
296
    }
297
    echo '</ul>
298
        </div> <!-- end card-body -->
299
    ';
300
301
    Content::printCollapseEnd();
302
}
303
304
/**
305
 * verifyOIDCParams
306
 *
307
 * This function verifies that all of the various OIDC parameters are
308
 * set in the PHP session. First, the function checks if an OIDC
309
 * client has passed appropriate parameters to the authorization
310
 * endpoint. If so, we call the 'real' OA4MP OIDC authorization
311
 * endpoint and let it verify the client parameters. Upon successful
312
 * return, we read the database to get the OIDC client information
313
 * to display to the user. All client parameters (including the ones
314
 * passed in) are saved to the 'clientparams' PHP session variable,
315
 * which is encoded as a JSON token to preserve arrays. If there are
316
 * any errors, false is returned and an email is sent. In some cases
317
 * the session variable 'client_error_msg' is set so it can be
318
 * displayed by the printOIDCErrorPage() function.
319
 *
320
 * @return bool True if the various parameters related to the OIDC
321
 *         session are present. False otherwise.
322
 */
323
function verifyOIDCParams()
324
{
325
    $retval = false; // Assume OIDC session info is not valid
326
327
    // Combine the $_GET and $_POST arrays into a single array which can be
328
    // stored in the 'clientparams' session variable as a JSON object.
329
    $clientparams = array();
330
    foreach ($_GET as $key => $value) {
331
        $clientparams[$key] = $value;
332
    }
333
    foreach ($_POST as $key => $value) {
334
        $clientparams[$key] = $value;
335
    }
336
337
    // CIL-624 If X509 certs are disabled, check for 'getcert' scope.
338
    // If found, show an error message.
339
    $scope = Util::getGetVar('scope');
340
    if (
341
        (defined('DISABLE_X509')) &&
342
        (DISABLE_X509 === true) &&
343
        (preg_match('/edu.uiuc.ncsa.myproxy.getcert/', $scope))
344
    ) {
345
        Util::sendErrorAlert(
346
            'CILogon OIDC authz endpoint error',
347
            'The CILogon OIDC authorization endpoint received a request ' .
348
            'including the "edu.ncsa.uiuc.myproxy.getcert" scope, ' .
349
            'but the server is configured with DISABLE_X509 to prevent ' .
350
            'downloading certificates. ' .
351
            "\n\n" .
352
            'clientparams = ' . print_r($clientparams, true) .
353
            "\n"
354
        );
355
        Util::setSessionVar(
356
            'client_error_msg',
357
            'The CILogon Service is currently configured to prevent ' .
358
            'downloading X.509 certificates, but the incoming request ' .
359
            'included the "edu.ncsa.uiuc.myproxy.getcert" scope. ' .
360
            'CILogon system administrators have been notified.'
361
        );
362
        $clientparams = array();
363
364
    // If the 'redirect_uri' parameter was passed in then let the 'real'
365
    // OA4MP OIDC authz endpoint handle parse the request since it might be
366
    // possible to return an error code to the client.
367
    } elseif (isset($clientparams['redirect_uri'])) {
368
        $ch = curl_init();
369
        if ($ch !== false) {
370
            $url = OAUTH2_CREATE_TRANSACTION_URL;
371
            if (count($_GET) > 0) {
372
                // CIL-658 Look for double-encoded spaces in 'scope'
373
                if (strlen($scope) > 0) {
374
                    $_GET['scope'] = preg_replace('/(\+|%2B)/', ' ', $scope);
375
                }
376
                $url .= (preg_match('/\?/', $url) ? '&' : '?') .
377
                    http_build_query($_GET);
378
            }
379
            if (count($_POST) > 0) {
380
                curl_setopt($ch, CURLOPT_POST, true);
381
                curl_setopt($ch, CUROPT_POSTFIELDS, http_build_query($_POST));
382
            }
383
            curl_setopt($ch, CURLOPT_URL, $url);
384
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
385
            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
386
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); // Catch redirects
387
            $output = curl_exec($ch);
388
            if (curl_errno($ch)) { // Send alert on curl errors
389
                Util::sendErrorAlert(
390
                    'cUrl Error',
391
                    'cUrl Error    = ' . curl_error($ch) . "\n" .
392
                    "URL Accessed  = $url" .
393
                    "\n\n" .
394
                    'clientparams = ' . print_r($clientparams, true)
395
                );
396
                $clientparams = array();
397
            } else {
398
                $info = curl_getinfo($ch);
399
                if ($info !== false) {
400
                    if (
401
                        (isset($info['http_code'])) &&
402
                        ($info['http_code'] == 200)
403
                    ) {
404
                        // The OA4MP OIDC authz endpoint responded with 200
405
                        // (success). The body of the message should be a
406
                        // JSON token containing the appropriate parameters
407
                        // such as the 'code'.
408
                        $json = json_decode($output, true);
409
                        if (isset($json['code'])) {
410
                            // Got 'code' - save to session and read OIDC
411
                            // client info from the database to display
412
                            // to the user
413
                            $clientparams['redirect_url'] =
414
                                $clientparams['redirect_uri'] .
415
                                (preg_match('/\?/', $clientparams['redirect_uri']) ? '&' : '?') .
416
                                http_build_query($json);
417
                            $clientparams['code'] = $json['code'];
418
                            // CIL-618 Read OIDC client info from database
419
                            if (!Util::getOIDCClientParams($clientparams)) {
420
                                Util::sendErrorAlert(
421
                                    'getOIDCClientParams Error',
422
                                    'Error getting OIDC client parameters ' .
423
                                    'in verifyOIDCParams() function for ' .
424
                                    'client_id="' .
425
                                    $clientparams['client_id'] . '".'
426
                                );
427
                                $clientparams = array();
428
                            }
429
                        } else {
430
                            // Either the output returned was not a valid
431
                            // JSON token, or there was no 'code' found in
432
                            // the returned JSON token.
433
                            $errortxt = getErrorStatusText($output, $clientparams);
434
435
                            Util::sendErrorAlert(
436
                                'OA4MP OIDC authz endpoint error',
437
                                (!empty($errortxt) ? $errortxt :
438
                                'The OA4MP OIDC authorization endpoint ' .
439
                                'returned an HTTP response 200, but either ' .
440
                                'the output was not a valid JSON token, or ' .
441
                                'there was no "code" in the JSON token. ' .
442
                                ((strlen($output) > 0) ?
443
                                    "\n\nReturned output =\n$output" : '')) .
444
                                "\n\n" .
445
                                'curl_getinfo = ' . print_r($info, true) . "\n\n" .
446
                                'clientparams = ' . print_r($clientparams, true) .
447
                                "\n"
448
                            );
449
                            Util::setSessionVar(
450
                                'client_error_msg',
451
                                'There was an unrecoverable error during the transaction. ' .
452
                                'CILogon system administrators have been notified. ' .
453
                                (!empty($errortxt) ? "<p><b>Error message: $errortxt</b><p>" : '')
454
                            );
455
                            $clientparams = array();
456
                        }
457
                    } elseif (
458
                        (isset($info['http_code'])) &&
459
                        ($info['http_code'] == 302)
460
                    ) {
461
                        // The OA4MP OIDC authz endpoint responded with 302
462
                        // (redirect) which indicates an OIDC error was
463
                        // detected. We need to check the response for an
464
                        // 'error' and simply redirect error to OIDC client.
465
                        $redirect_url = '';
466
                        if (isset($info['redirect_url'])) {
467
                            $redirect_url = $info['redirect_url'];
468
                            $clientparams['redirect_url'] = $redirect_url;
469
                            // CIL-407 - In case of two question marks '?'
470
                            // in redirect_url (caused by OIDC authz endpoint
471
                            // blindly appending "?error=..."), change all
472
                            // but the first '?' to '&'.
473
                            // https://stackoverflow.com/a/37150213
474
                            if (substr_count($redirect_url, '?') > 1) {
475
                                $arr = explode('?', $redirect_url, 2);
476
                                $arr[1] = str_replace('?', '&', $arr[1]);
477
                                $redirect_url = implode('?', $arr);
478
                            }
479
                        }
480
                        // Get components of redirect_url - need 'query'
481
                        $comps = parse_url($redirect_url);
482
                        if ($comps !== false) {
483
                            // Look for 'error' in query
484
                            $query = '';
485
                            if (isset($comps['query'])) {
486
                                $query = $comps['query'];
487
                                $query = html_entity_decode($query);
488
                            }
489
                            $queries = explode('&', $query);
490
                            $params = array();
491
                            foreach ($queries as $value) {
492
                                $x = explode('=', $value);
493
                                $params[$x[0]] = $x[1];
494
                            }
495
                            if (isset($params['error'])) {
496
                                // Got 'error' - simply return to OIDC client
497
                                Util::unsetAllUserSessionVars();
498
                                header("Location: $redirect_url");
499
                                exit; // No further processing necessary
500
                            } else { // Weird params - Should never get here!
501
                                Util::sendErrorAlert(
502
                                    'OA4MP OIDC 302 Error',
503
                                    'The OA4MP OIDC authz endpoint ' .
504
                                    'returned a 302 redirect (error) ' .
505
                                    'response, but there was no "error" ' .
506
                                    "query parameter.\n\n" .
507
                                    "redirect_url = $redirect_url\n\n" .
508
                                    'clientparams = ' .
509
                                    print_r($clientparams, true) .
510
                                    "\n"
511
                                );
512
                                $clientparams = array();
513
                            }
514
                        } else { // parse_url($redirect_url) gave error
515
                            Util::sendErrorAlert(
516
                                'parse_url(redirect_url) error',
517
                                'There was an error when attempting to ' .
518
                                'parse the redirect_url. This should never ' .
519
                                "happen.\n\n" .
520
                                "redirect_url = $redirect_url\n\n" .
521
                                'clientparams = ' . print_r($clientparams, true) .
522
                                "\n"
523
                            );
524
                            $clientparams = array();
525
                        }
526
                    } else {
527
                        // An HTTP return code other than 200 (success) or
528
                        // 302 (redirect) means that the OA4MP OIDC authz
529
                        // endpoint tried to handle an unrecoverable error,
530
                        // possibly by outputting HTML. If so, then we
531
                        // ignore it and output our own error message to the
532
                        // user.
533
                        Util::sendErrorAlert(
534
                            'OA4MP OIDC authz endpoint error',
535
                            'The OA4MP OIDC authorization endpoint returned ' .
536
                            'an HTTP response other than 200 or 302. ' .
537
                            ((strlen($output) > 0) ?
538
                                "\n\nReturned output =\n$output" : '') .
539
                            "\n\n" .
540
                            'curl_getinfo = ' . print_r($info, true) . "\n\n" .
541
                            'clientparams = ' . print_r($clientparams, true) .
542
                            "\n"
543
                        );
544
                        // CIL-423 Better end-user error output for errors.
545
                        // Scan output for ServletException message.
546
                        $errstr = '';
547
                        if (
548
                            preg_match(
549
                                '/javax.servlet.ServletException:\s?(.*)/',
550
                                $output,
551
                                $matches
552
                            )
553
                        ) {
554
                            $output = '';
555
                            $errstr = '
556
                            <div>
557
                            <p>Error Message: <b>' .
558
                            $matches[1] . '</b>.</p>
559
                            <ul>
560
                            <li>Did you <b>register</b> your OAuth2/OIDC client? If not, go
561
                            <b><a target="_blank" href="https://' .
562
                            Util::getHN()
563
                            . '/oauth2/register">here</a></b> to do so.</li>
564
                            <li>Did you receive confirmation that your OAuth2/OIDC client
565
                            was <b>approved</b>? If not, please wait up to 48 hours for an
566
                            approval email from CILogon administrators.</li>
567
                            <li>Did you configure your OAuth2/OIDC client with the
568
                            registered <b>client ID and secret</b>?</li>
569
                            </ul>
570
                            </div>';
571
                        }
572
                        Util::setSessionVar(
573
                            'client_error_msg',
574
                            'There was an unrecoverable error during the transaction. ' .
575
                            'CILogon system administrators have been notified.' .
576
                            ((strlen($errstr) > 0) ? $errstr : '') .
577
                            ((strlen($output) > 0) ?
578
                            '<br/><pre>' .
579
                            preg_replace('/\+/', ' ', $output) .
580
                            '</pre>' : '')
581
                        );
582
                        $clientparams = array();
583
                    }
584
                } else { // curl_getinfo() returned false - should not happen
585
                    Util::sendErrorAlert(
586
                        'curl_getinfo error',
587
                        'When attempting to talk to the OA4MP OIDC ' .
588
                        'authorization endpoint, curl_getinfo() returned ' .
589
                        "false. This should never happen.\n\n" .
590
                        'clientparams = ' . print_r($clientparams, true) . "\n"
591
                    );
592
                    $clientparams = array();
593
                }
594
            }
595
            curl_close($ch);
596
        } else { // curl_init() returned false - should not happen
597
            Util::sendErrorAlert(
598
                'curl_init error',
599
                'When attempting to talk to the OA4MP OIDC authorization ' .
600
                'endpoint, curl_init() returned false. This should never ' .
601
                "happen.\n\n" .
602
                'clientparams = ' . print_r($clientparams, true) . "\n"
603
            );
604
            $clientparams = array();
605
        }
606
607
    // If redirect_uri was not passed in, but one of the other required OIDC
608
    // parameters WAS passed in, then assume that this was an attempt by an
609
    // OIDC client to use the authz endpoint, and display an error message
610
    // that at least one parameter (redirect_uri) was missing from the
611
    // request. Note that since we don't have a redirect_uri, we cannot
612
    // return code flow back to the OIDC client.
613
    } elseif (
614
        (isset($clientparams['client_id'])) ||
615
        (isset($clientparams['scope'])) ||
616
        (isset($clientparams['response_type']))
617
    ) {
618
        $missing = 'redirect_uri' .
619
            ((isset($clientparams['client_id'])) ? '' : ', client_id') .
620
            ((isset($clientparams['scope'])) ? '' : ', scope') .
621
            ((isset($clientparams['response_type'])) ? '' : ', response_type');
622
        Util::sendErrorAlert(
623
            'CILogon OIDC authz endpoint error',
624
            'The CILogon OIDC authorization endpoint received a request ' .
625
            'from an OIDC client, but at least one of the required ' .
626
            'parameters (' . $missing . ') was missing. ' .
627
            "\n\n" .
628
            'clientparams = ' . print_r($clientparams, true) .
629
            "\n"
630
        );
631
        Util::setSessionVar(
632
            'client_error_msg',
633
            'It appears that an OpenID Connect client attempted to ' .
634
            'initiate a session with the CILogon Service, but at least ' .
635
            'one of the requried parameters (' . $missing . ') ' .
636
            'was missing. CILogon system administrators have been notified.'
637
        );
638
        $clientparams = array();
639
640
    // If none of the required OIDC authz endpoint parameters were passed
641
    // in, then this might be a later step in the authz process. So check
642
    // the session variable array 'clientparams' for the required
643
    // information.
644
    } else {
645
        $clientparams = json_decode(Util::getSessionVar('clientparams'), true);
646
    }
647
648
    // Now check to verify all variables have data
649
    if (
650
        (isset($clientparams['redirect_uri'])) &&
651
        (isset($clientparams['scope'])) &&
652
        (isset($clientparams['response_type'])) &&
653
        (isset($clientparams['client_id'])) &&
654
        (isset($clientparams['code'])) &&
655
        (isset($clientparams['client_name'])) &&
656
        (isset($clientparams['client_home_url'])) &&
657
        (isset($clientparams['client_callback_uri'])) &&
658
        (isset($clientparams['client_scopes'])) &&
659
        (isset($clientparams['redirect_url'])) &&
660
        (isset($clientparams['clientstatus'])) &&
661
        (!($clientparams['clientstatus'] & 1))
662
    ) { // STATUS_OK* are even
663
        $retval = true;
664
        Util::setSessionVar('clientparams', json_encode($clientparams));
665
    }
666
667
    return $retval;
668
}
669
670
/**
671
 * getErrorStatusText
672
 *
673
 * This function is called when the OA4MP OIDC authz endpoint responds with
674
 * a 200 (success), but the returned output was not a valid JSON token, or
675
 * there was no 'code' found in the returned JSON token. So attempt to scan
676
 * the returned $output for error messages that can be returned to the end
677
 * user and added to the alert email sent to admins.
678
 *
679
 * @param string $output The returned text from the OA4MP authz endpoint.
680
 * @param array $clientparams An array of the incoming OIDC client parameters.
681
 * @return string Error text to be displayed to the end user and added to
682
 *         the alert email sent to admins.
683
 */
684
function getErrorStatusText($output, $clientparams)
685
{
686
    $errtxt = '';
687
688
    // CIL-575 Check the $output for a "status=..." line and convert
689
    // the error number to an error message defined in CILogon\Service\Util.
690
    if (preg_match('/status=(\d+)/', $output, $matches)) {
691
        $errnum = $matches[1];
692
        $errstr = array_search($errnum, DBService::$STATUS);
693
        $errtxt = @DBService::$STATUS_TEXT[$errstr];
694
    }
695
696
    // CIL-831 The OA4MP code returns a STATUS_INTERNAL_ERROR when there is
697
    // weirdness in the incoming client parameters. Look for some special
698
    // error conditions and set the error text appropriately.
699
    if ($errstr == 'STATUS_INTERNAL_ERROR') {
0 ignored issues
show
Bug introduced by
The variable $errstr does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
700
        $params = [
701
            'redirect_uri',
702
            'scope',
703
            'response_type',
704
            'client_id',
705
            'prompt',
706
            'response_mode',
707
        ];
708
        foreach ($params as $value) {
709
            $$value = @$clientparams[$value];
710
        }
711
712
        if (empty($scope)) {
0 ignored issues
show
Bug introduced by
The variable $scope seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
713
            $errtxt = "Missing or empty 'scope' parameter.";
714
        } elseif (empty($client_id)) {
0 ignored issues
show
Bug introduced by
The variable $client_id seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
715
            $errtxt = "Missing or empty 'client_id' parameter.";
716
        } elseif (empty($response_type)) {
0 ignored issues
show
Bug introduced by
The variable $response_type seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
717
            $errtxt = "Missing or empty 'response_type' parameter.";
718
        } elseif (preg_match('/[\+%"\']/', $scope)) {
719
            $errtxt = "Invalid characters found in 'scope' parameter, may be URL encoded twice.";
720
        } elseif (preg_match('/[A-Z]/', $scope)) {
721
            $errtxt = "Upper case characters found in 'scope' parameter.";
722
        } elseif ($response_type != 'code') {
723
            $errtxt = "Unsupported 'response_type' parameter. Only 'code' is supported.";
724
        } elseif ((!empty($prompt)) && ($prompt != 'login') && ($prompt != 'select_account')) {
0 ignored issues
show
Bug introduced by
The variable $prompt seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
725
            $errtxt = "Unsupported 'prompt' parameter. Only 'login' and 'select_account' are supported.";
726
        } elseif (
727
            (!empty($response_mode)) &&
0 ignored issues
show
Bug introduced by
The variable $response_mode seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
728
            ($response_mode != 'query') &&
729
            ($response_mode != 'fragment') &&
730
            ($response_mode != 'form_post')
731
        ) {
732
            $errtxt = "Unsupported 'response_mode' parameter.";
733
        }
734
    }
735
736
    // CIL-697 The OA4MP code should eventually return an
737
    // "error_description=..." field that can give detailed error text to
738
    // replace the default text associated with STATUS_INTERNAL_ERROR.
739
    if (preg_match('/error_description=([^\r\n]+)/', $output, $matches)) {
740
        $errtxt = $matches[1];
741
    }
742
743
    return $errtxt;
744
}
745