1 | <?php |
||
2 | |||
3 | namespace CILogon\Service; |
||
4 | |||
5 | use CILogon\Service\Util; |
||
6 | use CILogon\Service\Content; |
||
7 | use CILogon\Service\Loggit; |
||
8 | |||
9 | /** |
||
10 | * ShibError |
||
11 | * This class handles errors passed by the Shibboleth SP |
||
12 | * software when /etc/shibboleth/shibboleth2.xml is |
||
13 | * configured as follows: |
||
14 | * <Errors redirectErrors="https://cilogon.org"/> |
||
15 | * Note that with v.2.4.x of the Shibboleth SP software, |
||
16 | * the 'redirectErrors' attribute must be an absolute URL, |
||
17 | * meaning that the shibboleth2.xml file will be different |
||
18 | * for https://test.cilogon.org/. V.2.5 of the SP |
||
19 | * software should allow relative URLS. |
||
20 | * |
||
21 | * To handle errors from the Shibboleth SP software, |
||
22 | * create a new ShibError instance at the root index.php |
||
23 | * file. The constructor looks for various 'GET' |
||
24 | * parameters that get passed by the SP software. If there |
||
25 | * is an error, show an error page with the info provided |
||
26 | * by the SP software. |
||
27 | * |
||
28 | * Example usage: |
||
29 | * require_once 'ShibError.php'; |
||
30 | * // The class constructor does ALL of the work |
||
31 | * $shiberror = new ShibError(); |
||
32 | */ |
||
33 | class ShibError |
||
34 | { |
||
35 | |||
36 | /** |
||
37 | * @var array $errorarray Holds the values of the various shibboleth |
||
38 | * error parameters |
||
39 | */ |
||
40 | private $errorarray; |
||
41 | |||
42 | /** |
||
43 | * @var array $errorparams Shibboleth error parameters passed to the |
||
44 | * redirectErrors handler: |
||
45 | * https://wiki.shibboleth.net/confluence/display/SHIB2/NativeSPErrors |
||
46 | */ |
||
47 | private static $errorparams = array( |
||
48 | 'requestURL', // The URL associated with the request |
||
49 | 'errorType', // The general type of error |
||
50 | 'errorText', // The actual error message |
||
51 | 'entityID', // Name of identity provider, if known |
||
52 | 'now', // Current date and time |
||
53 | 'statusCode', // SAML status code causing error, sent by IdP |
||
54 | 'statusCode2', // SAML sub-status code causing error, sent by IdP |
||
55 | 'statusMessage', // SAML status message, sent by IdP |
||
56 | 'contactName', // A support contact name for the IdP |
||
57 | // provided by that site's metadata. |
||
58 | 'contactEmail', // A contact email address for the IdP contact |
||
59 | // provided by that site's metadata. |
||
60 | 'errorURL', // The URL of an error handling page for the IdP |
||
61 | // provided by that site's metadata. |
||
62 | ); |
||
63 | |||
64 | /** |
||
65 | * __construct |
||
66 | * |
||
67 | * Default constructor. This method attempts to read in the |
||
68 | * various Shibboleth SP error parameters that would have been |
||
69 | * passed as parameters to the 'redirectErrors' handler URL, i.e. |
||
70 | * in the $_GET global variable. |
||
71 | * |
||
72 | * @return ShibError A new ShibError object. |
||
73 | */ |
||
74 | public function __construct() |
||
75 | { |
||
76 | $this->errorarray = array(); |
||
77 | foreach (self::$errorparams as $param) { |
||
78 | if (isset($_GET[$param])) { |
||
79 | $this->errorarray[$param] = rtrim($_GET[$param]); |
||
80 | } |
||
81 | } |
||
82 | |||
83 | if ($this->isError()) { |
||
84 | // CIL-410 Temporary fix for /secure/testidp Shibboleth error. |
||
85 | // Check for error and redirect to /testidp . |
||
86 | if ( |
||
87 | (isset($this->errorarray['errorType'])) && |
||
88 | ($this->errorarray['errorType'] == 'shibsp::ConfigurationException') && |
||
89 | (isset($this->errorarray['errorText'])) && |
||
90 | ($this->errorarray['errorText'] == |
||
91 | 'None of the configured SessionInitiators handled the request.') && |
||
92 | (preg_match('%/secure/testidp%', $this->errorarray['requestURL'])) |
||
93 | ) { |
||
94 | header('Location: https://' . Util::getDN() . '/testidp/'); |
||
95 | // CIL-480 Check for user IdP login failure and OAuth transaction |
||
96 | // and redirect appropriately |
||
97 | } elseif ( |
||
98 | (isset($this->errorarray['errorType'])) && |
||
99 | ($this->errorarray['errorType'] == 'opensaml::FatalProfileException') && |
||
100 | (isset($this->errorarray['errorText'])) && |
||
101 | ($this->errorarray['errorText'] == 'SAML response reported an IdP error.') && |
||
102 | (isset($this->errorarray['statusCode2'])) && |
||
103 | ($this->errorarray['statusCode2'] == 'urn:oasis:names:tc:SAML:2.0:status:AuthnFailed') |
||
104 | ) { |
||
105 | $clientparams = json_decode(Util::getSessionVar('clientparams'), true); // OAuth 2.0 |
||
106 | $failureuri = Util::getSessionVar('failureuri'); // OAuth 1.0a |
||
107 | if (array_key_exists('redirect_uri', $clientparams)) { |
||
108 | Util::unsetAllUserSessionVars(); |
||
109 | header('Location: ' . $clientparams['redirect_uri'] . |
||
110 | (preg_match('/\?/', $clientparams['redirect_uri']) ? '&' : '?') . |
||
111 | 'error=access_denied&error_description=' . |
||
112 | 'User%20denied%20authorization%20request' . |
||
113 | ((array_key_exists('state', $clientparams)) ? |
||
114 | '&state=' . $clientparams['state'] : '')); |
||
115 | } elseif (strlen($failureuri) > 0) { |
||
116 | Util::unsetAllUserSessionVars(); |
||
117 | header('Location: ' . $failureuri . '?reason=cancel'); |
||
118 | } else { |
||
119 | $this->printError(); |
||
120 | } |
||
121 | } else { |
||
122 | $this->printError(); |
||
123 | } |
||
124 | exit; // No further processing!!! |
||
0 ignored issues
–
show
|
|||
125 | } |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * isError |
||
130 | * |
||
131 | * This method returns true if several Shibboleth error parameters |
||
132 | * were passed in via the $_GET array. These parameters are sent |
||
133 | * by the Shibboleth SP software when there is an error. |
||
134 | * |
||
135 | * @return bool True if several Shibboleth error parameters were |
||
136 | * passed to the handler URL. False otherwise. |
||
137 | */ |
||
138 | public function isError() |
||
139 | { |
||
140 | return ((isset($this->errorarray['requestURL'])) && |
||
141 | (isset($this->errorarray['errorType'])) && |
||
142 | (isset($this->errorarray['errorText'])) && |
||
143 | (isset($this->errorarray['now'])) && |
||
144 | (strlen($this->errorarray['requestURL']) > 0) && |
||
145 | (strlen($this->errorarray['errorType']) > 0) && |
||
146 | (strlen($this->errorarray['errorText']) > 0) && |
||
147 | (strlen($this->errorarray['now']) > 0)); |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * printError |
||
152 | * |
||
153 | * This method prints out text for an error message page. It also |
||
154 | * logs the error and sends an alert email with the shibboleth |
||
155 | * error parameters. You should probably 'exit()' after you call |
||
156 | * this so more HTML doesn't get output. |
||
157 | */ |
||
158 | public function printError() |
||
159 | { |
||
160 | $errorstr1 = ''; // For logging - one line |
||
161 | $errorstr2 = ''; // For HTML and email - multi-line |
||
162 | foreach ($this->errorarray as $key => $value) { |
||
163 | $errorstr1 .= Util::htmlent($key . '="' . $value . '" '); |
||
164 | $errorstr2 .= Util::htmlent(sprintf("%-14s= %s\n", $key, $value)); |
||
165 | } |
||
166 | |||
167 | $log = new Loggit(); |
||
168 | $log->error('Shibboleth error: ' . $errorstr1); |
||
169 | |||
170 | Content::printHeader('Shiboleth Error'); |
||
171 | Content::printCollapseBegin('shiberror', 'Shibboleth Error', false); |
||
172 | |||
173 | echo ' |
||
174 | <div class="card-body px-5"> |
||
175 | <div class="card-text my-2"> |
||
176 | The CILogon Service has encountered a Shibboleth error. |
||
177 | </div> <!-- end card-text --> |
||
178 | '; |
||
179 | |||
180 | Content::printErrorBox('<pre>' . $errorstr2 . '</pre>'); |
||
181 | |||
182 | echo ' |
||
183 | <div class="card-text my-2"> |
||
184 | System administrators have been notified. |
||
185 | This may be a temporary error. Please try again later, or |
||
186 | contact us at the email address at the bottom of the page. |
||
187 | </div> <!-- end card-text --> |
||
188 | '; |
||
189 | |||
190 | $skin = Util::getSkin(); |
||
191 | $forceauthn = $skin->getConfigOption('forceauthn'); |
||
192 | if ((!is_null($forceauthn)) && ((int)$forceauthn == 1)) { |
||
193 | echo ' |
||
194 | <div class="card-text my-2"> |
||
195 | Note that this error may be due to your selected Identity |
||
196 | Provider (IdP) not fully supporting "forced |
||
197 | reauthentication". This setting forces users to log |
||
198 | in at the IdP every time, thus bypassing Single Sign-On |
||
199 | (SSO). |
||
200 | </div> <!-- end card-text --> |
||
201 | '; |
||
202 | } |
||
203 | |||
204 | Content::printFormHead(); |
||
205 | |||
206 | echo ' |
||
207 | <div class="card-text my-2"> |
||
208 | <div class="form-group"> |
||
209 | <div class="form-row align-items-center |
||
210 | justify-content-center"> |
||
211 | <div class="col-auto"> |
||
212 | <input type="submit" name="submit" |
||
213 | class="btn btn-primary submit form-control" |
||
214 | value="Proceed" /> |
||
215 | </div> |
||
216 | </div> <!-- end form-row align-items-center --> |
||
217 | </div> <!-- end form-group --> |
||
218 | </div> <!-- end card-text --> |
||
219 | </form> |
||
220 | </div> <!-- end card-body -->'; |
||
221 | |||
222 | Content::printCollapseEnd(); |
||
223 | Content::printFooter(); |
||
224 | |||
225 | Util::sendErrorAlert('Shibboleth Error', $errorstr2); |
||
226 | } |
||
227 | } |
||
228 |
In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.