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