1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Copyright 2014 SURFnet bv |
5
|
|
|
* |
6
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
7
|
|
|
* you may not use this file except in compliance with the License. |
8
|
|
|
* You may obtain a copy of the License at |
9
|
|
|
* |
10
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0 |
11
|
|
|
* |
12
|
|
|
* Unless required by applicable law or agreed to in writing, software |
13
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS, |
14
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
15
|
|
|
* See the License for the specific language governing permissions and |
16
|
|
|
* limitations under the License. |
17
|
|
|
*/ |
18
|
|
|
|
19
|
|
|
namespace Surfnet\StepupGateway\GatewayBundle\Saml; |
20
|
|
|
|
21
|
|
|
use DateTime; |
22
|
|
|
use DateTimeZone; |
23
|
|
|
use DOMDocument; |
24
|
|
|
use Psr\Log\LoggerInterface; |
25
|
|
|
use SAML2\Assertion; |
26
|
|
|
use Surfnet\SamlBundle\Entity\IdentityProvider; |
27
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor; |
28
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Entity\ServiceProvider; |
29
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler; |
30
|
|
|
use Surfnet\StepupGateway\GatewayBundle\Service\SamlEntityService; |
31
|
|
|
|
32
|
|
|
class ResponseContext |
33
|
|
|
{ |
34
|
|
|
/** |
35
|
|
|
* @var IdentityProvider |
36
|
|
|
*/ |
37
|
|
|
private $hostedIdentityProvider; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var \Surfnet\StepupGateway\GatewayBundle\Service\SamlEntityService |
41
|
|
|
*/ |
42
|
|
|
private $samlEntityService; |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @var ProxyStateHandler |
46
|
|
|
*/ |
47
|
|
|
private $stateHandler; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* @var LoggerInterface |
51
|
|
|
*/ |
52
|
|
|
private $logger; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @var DateTime |
56
|
|
|
*/ |
57
|
|
|
private $generationTime; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* @var IdentityProvider|null |
61
|
|
|
*/ |
62
|
|
|
private $authenticatingIdp; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var ServiceProvider |
66
|
|
|
*/ |
67
|
|
|
private $targetServiceProvider; |
68
|
|
|
|
69
|
|
View Code Duplication |
public function __construct( |
|
|
|
|
70
|
|
|
IdentityProvider $identityProvider, |
71
|
|
|
SamlEntityService $samlEntityService, |
72
|
|
|
ProxyStateHandler $stateHandler, |
73
|
|
|
LoggerInterface $logger, |
74
|
|
|
DateTime $now = null |
75
|
|
|
) { |
76
|
|
|
$this->hostedIdentityProvider = $identityProvider; |
77
|
|
|
$this->samlEntityService = $samlEntityService; |
78
|
|
|
$this->stateHandler = $stateHandler; |
79
|
|
|
$this->logger = $logger; |
80
|
|
|
$this->generationTime = is_null($now) ? new DateTime('now', new DateTimeZone('UTC')): $now; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* @return string |
85
|
|
|
*/ |
86
|
|
|
public function getDestination() |
87
|
|
|
{ |
88
|
|
|
$requestAcsUrl = $this->stateHandler->getRequestAssertionConsumerServiceUrl(); |
89
|
|
|
|
90
|
|
|
return $this->getServiceProvider()->determineAcsLocation($requestAcsUrl, $this->logger); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @return string |
95
|
|
|
*/ |
96
|
|
|
public function getDestinationForAdfs() |
97
|
|
|
{ |
98
|
|
|
$requestAcsUrl = $this->stateHandler->getRequestAssertionConsumerServiceUrl(); |
99
|
|
|
|
100
|
|
|
return $this->getServiceProvider()->determineAcsLocationForAdfs($requestAcsUrl); |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* @return null|string |
105
|
|
|
*/ |
106
|
|
|
public function getIssuer() |
107
|
|
|
{ |
108
|
|
|
return $this->hostedIdentityProvider->getEntityId(); |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
/** |
112
|
|
|
* @return int |
113
|
|
|
*/ |
114
|
|
|
public function getIssueInstant() |
115
|
|
|
{ |
116
|
|
|
return $this->generationTime->getTimestamp(); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @return null|string |
121
|
|
|
*/ |
122
|
|
|
public function getInResponseTo() |
123
|
|
|
{ |
124
|
|
|
return $this->stateHandler->getRequestId(); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* @return null|string |
129
|
|
|
*/ |
130
|
|
|
public function getExpectedInResponseTo() |
131
|
|
|
{ |
132
|
|
|
return $this->stateHandler->getGatewayRequestId(); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @return null|string |
137
|
|
|
*/ |
138
|
|
|
public function getRequiredLoa() |
139
|
|
|
{ |
140
|
|
|
return $this->stateHandler->getRequiredLoaIdentifier(); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @return IdentityProvider |
145
|
|
|
*/ |
146
|
|
|
public function getIdentityProvider() |
147
|
|
|
{ |
148
|
|
|
return $this->hostedIdentityProvider; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* @return null|ServiceProvider |
153
|
|
|
*/ |
154
|
|
|
public function getServiceProvider() |
155
|
|
|
{ |
156
|
|
|
if (isset($this->targetServiceProvider)) { |
157
|
|
|
return $this->targetServiceProvider; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
$serviceProviderId = $this->stateHandler->getRequestServiceProvider(); |
161
|
|
|
|
162
|
|
|
return $this->targetServiceProvider = $this->samlEntityService->getServiceProvider($serviceProviderId); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @return null|string |
167
|
|
|
*/ |
168
|
|
|
public function getRelayState() |
169
|
|
|
{ |
170
|
|
|
return $this->stateHandler->getRelayState(); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @param Assertion $assertion |
175
|
|
|
*/ |
176
|
|
|
public function saveAssertion(Assertion $assertion) |
177
|
|
|
{ |
178
|
|
|
// we pluck the NameId to make it easier to access it without having to reconstitute the assertion |
179
|
|
|
$nameId = $assertion->getNameId(); |
180
|
|
|
if (!is_null($nameId->value)) { |
181
|
|
|
$this->stateHandler->saveIdentityNameId($nameId->value); |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
// same for the entityId of the authenticating Authority |
185
|
|
|
$authenticatingAuthorities = $assertion->getAuthenticatingAuthority(); |
186
|
|
|
if (!empty($authenticatingAuthorities)) { |
187
|
|
|
$this->stateHandler->setAuthenticatingIdp(reset($authenticatingAuthorities)); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
// And also attempt to save the user's schacHomeOrganization |
191
|
|
|
$attributes = $assertion->getAttributes(); |
192
|
|
|
if (!empty($attributes['urn:mace:terena.org:attribute-def:schacHomeOrganization'])) { |
193
|
|
|
$schacHomeOrganization = $attributes['urn:mace:terena.org:attribute-def:schacHomeOrganization']; |
194
|
|
|
$this->stateHandler->setSchacHomeOrganization(reset($schacHomeOrganization)); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
$this->stateHandler->saveAssertion($assertion->toXML()->ownerDocument->saveXML()); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @return Assertion |
202
|
|
|
*/ |
203
|
|
|
public function reconstituteAssertion() |
204
|
|
|
{ |
205
|
|
|
$assertionAsXML = $this->stateHandler->getAssertion(); |
206
|
|
|
$assertionDocument = new DOMDocument(); |
207
|
|
|
$assertionDocument->loadXML($assertionAsXML); |
208
|
|
|
|
209
|
|
|
return new Assertion($assertionDocument->documentElement); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* @return null|string |
214
|
|
|
*/ |
215
|
|
|
public function getIdentityNameId() |
216
|
|
|
{ |
217
|
|
|
return $this->stateHandler->getIdentityNameId(); |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* Return the lower-cased schacHomeOrganization value from the assertion. |
222
|
|
|
* |
223
|
|
|
* Comparisons on SHO values should always be case insensitive. Stepup |
224
|
|
|
* configuration always contains SHO values lower-cased, so this getter |
225
|
|
|
* can be used to compare the SHO with configured values. |
226
|
|
|
* |
227
|
|
|
* @see StepUpAuthenticationService::resolveHighestRequiredLoa() |
228
|
|
|
* |
229
|
|
|
* @return null|string |
230
|
|
|
*/ |
231
|
|
|
public function getNormalizedSchacHomeOrganization() |
232
|
|
|
{ |
233
|
|
|
return strtolower( |
234
|
|
|
$this->stateHandler->getSchacHomeOrganization() |
235
|
|
|
); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* @return null|IdentityProvider |
240
|
|
|
*/ |
241
|
|
|
public function getAuthenticatingIdp() |
242
|
|
|
{ |
243
|
|
|
$entityId = $this->stateHandler->getAuthenticatingIdp(); |
244
|
|
|
|
245
|
|
|
if (!$entityId) { |
246
|
|
|
return null; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
if (isset($this->authenticatingIdp)) { |
250
|
|
|
return $this->authenticatingIdp; |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
$this->authenticatingIdp = $this->samlEntityService->hasIdentityProvider($entityId) |
254
|
|
|
? $this->samlEntityService->getIdentityProvider($entityId) |
255
|
|
|
: null; |
256
|
|
|
|
257
|
|
|
return $this->authenticatingIdp; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* @param SecondFactor $secondFactor |
262
|
|
|
*/ |
263
|
|
|
public function saveSelectedSecondFactor(SecondFactor $secondFactor) |
264
|
|
|
{ |
265
|
|
|
$this->stateHandler->setSelectedSecondFactorId($secondFactor->secondFactorId); |
266
|
|
|
$this->stateHandler->setSecondFactorVerified(false); |
267
|
|
|
$this->stateHandler->setPreferredLocale($secondFactor->displayLocale); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* @return null|string |
272
|
|
|
*/ |
273
|
|
|
public function getSelectedSecondFactor() |
274
|
|
|
{ |
275
|
|
|
return $this->stateHandler->getSelectedSecondFactorId(); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
public function markSecondFactorVerified() |
279
|
|
|
{ |
280
|
|
|
$this->stateHandler->setSecondFactorVerified(true); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* @return bool |
285
|
|
|
*/ |
286
|
|
|
public function isSecondFactorVerified() |
287
|
|
|
{ |
288
|
|
|
return $this->stateHandler->getSelectedSecondFactorId() && $this->stateHandler->isSecondFactorVerified(); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
public function getResponseAction() |
292
|
|
|
{ |
293
|
|
|
return $this->stateHandler->getResponseAction(); |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** |
297
|
|
|
* Resets some state after the response is sent |
298
|
|
|
* (e.g. resets which second factor was selected and whether it was verified). |
299
|
|
|
*/ |
300
|
|
|
public function responseSent() |
301
|
|
|
{ |
302
|
|
|
$this->stateHandler->setSelectedSecondFactorId(null); |
303
|
|
|
$this->stateHandler->setSecondFactorVerified(false); |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
/** |
307
|
|
|
* Retrieve the ResponseContextServiceId from state |
308
|
|
|
* |
309
|
|
|
* Used to determine we are dealing with a SFO or regular authentication. Both have different ResponseContext |
310
|
|
|
* instances, and it's imperative that successive consumers use the correct service. |
311
|
|
|
* |
312
|
|
|
* @return string|null |
313
|
|
|
*/ |
314
|
|
|
public function getResponseContextServiceId() |
315
|
|
|
{ |
316
|
|
|
return $this->stateHandler->getResponseContextServiceId(); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.