Passed
Pull Request — develop (#301)
by Michiel
12:29 queued 07:31
created

ResponseContext::getAuthenticatingIdp()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 4
nop 0
dl 0
loc 17
rs 9.9666
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A ResponseContext::saveSelectedSecondFactor() 0 5 1
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
 */
0 ignored issues
show
Coding Style introduced by
PHP version not specified
Loading history...
Coding Style introduced by
Missing @category tag in file comment
Loading history...
Coding Style introduced by
Missing @package tag in file comment
Loading history...
Coding Style introduced by
Missing @author tag in file comment
Loading history...
Coding Style introduced by
Missing @license tag in file comment
Loading history...
Coding Style introduced by
Missing @link tag in file comment
Loading history...
18
19
namespace Surfnet\StepupGateway\GatewayBundle\Saml;
20
21
use DateTime;
22
use DateTimeZone;
23
use DOMDocument;
24
use Exception;
25
use Psr\Log\LoggerInterface;
26
use SAML2\Assertion;
27
use SAML2\XML\saml\Issuer;
28
use Surfnet\SamlBundle\Entity\IdentityProvider;
29
use Surfnet\StepupGateway\GatewayBundle\Entity\SecondFactor;
30
use Surfnet\StepupGateway\GatewayBundle\Entity\ServiceProvider;
31
use Surfnet\StepupGateway\GatewayBundle\Saml\Exception\RuntimeException;
32
use Surfnet\StepupGateway\GatewayBundle\Saml\Proxy\ProxyStateHandler;
33
use Surfnet\StepupGateway\GatewayBundle\Service\SamlEntityService;
34
use Surfnet\StepupGateway\SecondFactorOnlyBundle\Adfs\Exception\AcsLocationNotAllowedException;
35
36
/**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
37
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
38
 */
0 ignored issues
show
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
39
class ResponseContext
40
{
41
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
42
     * @var IdentityProvider
43
     */
44
    private $hostedIdentityProvider;
0 ignored issues
show
Coding Style introduced by
Private member variable "hostedIdentityProvider" must be prefixed with an underscore
Loading history...
45
46
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
47
     * @var SamlEntityService
48
     */
49
    private $samlEntityService;
0 ignored issues
show
Coding Style introduced by
Private member variable "samlEntityService" must be prefixed with an underscore
Loading history...
50
51
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
52
     * @var ProxyStateHandler
53
     */
54
    private $stateHandler;
0 ignored issues
show
Coding Style introduced by
Private member variable "stateHandler" must be prefixed with an underscore
Loading history...
55
56
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
57
     * @var LoggerInterface
58
     */
59
    private $logger;
0 ignored issues
show
Coding Style introduced by
Private member variable "logger" must be prefixed with an underscore
Loading history...
60
61
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
62
     * @var DateTime
63
     */
64
    private $generationTime;
0 ignored issues
show
Coding Style introduced by
Private member variable "generationTime" must be prefixed with an underscore
Loading history...
65
66
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
67
     * @var ServiceProvider
68
     */
69
    private $targetServiceProvider;
0 ignored issues
show
Coding Style introduced by
Private member variable "targetServiceProvider" must be prefixed with an underscore
Loading history...
70
71
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
Parameter $identityProvider should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $samlEntityService should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $stateHandler should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $logger should have a doc-comment as per coding-style.
Loading history...
Coding Style introduced by
Parameter $now should have a doc-comment as per coding-style.
Loading history...
72
     * @throws Exception
73
     */
74
    public function __construct(
75
        IdentityProvider $identityProvider,
76
        SamlEntityService $samlEntityService,
77
        ProxyStateHandler $stateHandler,
78
        LoggerInterface $logger,
79
        DateTime $now = null
80
    ) {
81
        $this->hostedIdentityProvider = $identityProvider;
82
        $this->samlEntityService      = $samlEntityService;
83
        $this->stateHandler           = $stateHandler;
84
        $this->logger                 = $logger;
85
        $this->generationTime         = is_null($now) ? new DateTime('now', new DateTimeZone('UTC')): $now;
86
    }
87
88
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
89
     * @return string|null
90
     */
91
    public function getDestination(): ?string
92
    {
93
        $requestAcsUrl = $this->stateHandler->getRequestAssertionConsumerServiceUrl();
94
95
        return $this->getServiceProvider()->determineAcsLocation($requestAcsUrl, $this->logger);
96
    }
97
98
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
99
     * @return string
100
     * @throws AcsLocationNotAllowedException
101
     */
102
    public function getDestinationForAdfs(): string
103
    {
104
        $requestAcsUrl = $this->stateHandler->getRequestAssertionConsumerServiceUrl();
105
106
        return $this->getServiceProvider()->determineAcsLocationForAdfs($requestAcsUrl);
107
    }
108
109
    public function getIssuer(): Issuer
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getIssuer()
Loading history...
110
    {
111
        $issuer = new Issuer();
112
        $issuer->setValue($this->hostedIdentityProvider->getEntityId());
0 ignored issues
show
Bug introduced by
It seems like $this->hostedIdentityProvider->getEntityId() can also be of type null; however, parameter $value of SAML2\XML\saml\NameIDType::setValue() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

112
        $issuer->setValue(/** @scrutinizer ignore-type */ $this->hostedIdentityProvider->getEntityId());
Loading history...
113
        return $issuer;
114
    }
115
116
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
117
     * @return int
118
     */
119
    public function getIssueInstant(): int
120
    {
121
        return $this->generationTime->getTimestamp();
122
    }
123
124
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
125
     * @return null|string
126
     */
127
    public function getInResponseTo(): ?string
128
    {
129
        return $this->stateHandler->getRequestId();
130
    }
131
132
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
133
     * @return null|string
134
     */
135
    public function getExpectedInResponseTo(): ?string
136
    {
137
        return $this->stateHandler->getGatewayRequestId();
138
    }
139
140
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
141
     * @return null|string
142
     */
143
    public function getRequiredLoa(): ?string
144
    {
145
        return $this->stateHandler->getRequiredLoaIdentifier();
146
    }
147
148
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
149
     * @return IdentityProvider
150
     */
151
    public function getIdentityProvider(): IdentityProvider
152
    {
153
        return $this->hostedIdentityProvider;
154
    }
155
156
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
157
     * @return null|ServiceProvider
158
     */
159
    public function getServiceProvider(): ?ServiceProvider
160
    {
161
        if (isset($this->targetServiceProvider)) {
162
            return $this->targetServiceProvider;
163
        }
164
165
        $serviceProviderId = $this->stateHandler->getRequestServiceProvider();
166
167
        return $this->targetServiceProvider = $this->samlEntityService->getServiceProvider($serviceProviderId);
168
    }
169
170
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
171
     * @return null|string
172
     */
173
    public function getRelayState(): ?string
174
    {
175
        return $this->stateHandler->getRelayState();
176
    }
177
178
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
179
     * @param Assertion $assertion
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Tag value for @param tag indented incorrectly; expected 2 spaces but found 1
Loading history...
180
     * @throws Exception
0 ignored issues
show
Coding Style introduced by
Tag @throws cannot be grouped with parameter tags in a doc comment
Loading history...
181
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
182
    public function saveAssertion(Assertion $assertion): void
183
    {
184
        $this->stateHandler->saveIdentityNameId($this->resolveNameIdValue($assertion));
185
        // same for the entityId of the authenticating Authority
186
        $authenticatingAuthorities = $assertion->getAuthenticatingAuthority();
187
        if (!empty($authenticatingAuthorities)) {
188
            $this->stateHandler->setAuthenticatingIdp(reset($authenticatingAuthorities));
189
        }
190
191
        // And also attempt to save the user's schacHomeOrganization
192
        $attributes = $assertion->getAttributes();
193
        if (!empty($attributes['urn:mace:terena.org:attribute-def:schacHomeOrganization'])) {
194
            $schacHomeOrganization = $attributes['urn:mace:terena.org:attribute-def:schacHomeOrganization'];
195
            $this->stateHandler->setSchacHomeOrganization(reset($schacHomeOrganization));
196
        }
197
198
        $this->stateHandler->saveAssertion($assertion->toXML()->ownerDocument->saveXML());
0 ignored issues
show
Bug introduced by
The method saveXML() does not exist on null. ( Ignorable by Annotation )

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

198
        $this->stateHandler->saveAssertion($assertion->toXML()->ownerDocument->/** @scrutinizer ignore-call */ saveXML());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
199
    }
200
201
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
202
     * @return Assertion
203
     * @throws Exception
204
     */
205
    public function reconstituteAssertion(): Assertion
206
    {
207
        $assertionAsXML    = $this->stateHandler->getAssertion();
208
        $assertionDocument = new DOMDocument();
209
        $assertionDocument->loadXML($assertionAsXML);
210
211
        return new Assertion($assertionDocument->documentElement);
212
    }
213
214
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
215
     * @return null|string
216
     */
217
    public function getIdentityNameId(): string
218
    {
219
        return $this->stateHandler->getIdentityNameId();
220
    }
221
222
    /**
223
     * Return the lower-cased schacHomeOrganization value from the assertion.
224
     *
225
     * Comparisons on SHO values should always be case-insensitive. Stepup
226
     * configuration always contains SHO values lower-cased, so this getter
227
     * can be used to compare the SHO with configured values.
228
     *
229
     * @see StepUpAuthenticationService::resolveHighestRequiredLoa()
230
     *
231
     * @return null|string
232
     */
233
    public function getNormalizedSchacHomeOrganization(): ?string
234
    {
235
        return strtolower(
236
            $this->stateHandler->getSchacHomeOrganization()
0 ignored issues
show
Bug introduced by
It seems like $this->stateHandler->getSchacHomeOrganization() can also be of type null; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

236
            /** @scrutinizer ignore-type */ $this->stateHandler->getSchacHomeOrganization()
Loading history...
237
        );
238
    }
239
240
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
241
     * @param SecondFactor $secondFactor
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
242
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
243
    public function saveSelectedSecondFactor(SecondFactor $secondFactor): void
244
    {
245
        $this->stateHandler->setSelectedSecondFactorId($secondFactor->secondFactorId);
246
        $this->stateHandler->setSecondFactorVerified(false);
247
        $this->stateHandler->setPreferredLocale($secondFactor->displayLocale);
248
    }
249
250
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
251
     * @return null|string
252
     */
253
    public function getSelectedSecondFactor(): ?string
254
    {
255
        return $this->stateHandler->getSelectedSecondFactorId();
256
    }
257
258
    public function markSecondFactorVerified(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function markSecondFactorVerified()
Loading history...
259
    {
260
        $this->stateHandler->setSecondFactorVerified(true);
261
    }
262
263
    public function finalizeAuthentication(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function finalizeAuthentication()
Loading history...
264
    {
265
        // The second factor ID is used right before sending the response to verify if the SSO on
266
        // 2FA cookies Second Factor is still known on the platform Thats why it is forgotten at
267
        // this point during authentication.
268
        $this->stateHandler->setSelectedSecondFactorId(null);
269
        // Right before sending the response, we check if we need to update the SSO on 2FA cookie
270
        // One of the triggers for storing a new cookie is if the authentication was performed with
271
        // a real Second Factor token. That's why this value is purged from state at this very late
272
        // point in time.
273
        $this->stateHandler->setVerifiedBySsoOn2faCookie(null);
274
    }
275
276
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
277
     * @return bool
278
     */
279
    public function isSecondFactorVerified(): bool
280
    {
281
        return $this->stateHandler->getSelectedSecondFactorId() && $this->stateHandler->isSecondFactorVerified();
282
    }
283
284
    public function getResponseAction(): ?string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getResponseAction()
Loading history...
285
    {
286
        return $this->stateHandler->getResponseAction();
287
    }
288
289
    /**
290
     * Resets some state after the response is sent
291
     * (e.g. resets which second factor was selected and whether it was verified).
292
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
293
    public function responseSent()
294
    {
295
        $this->stateHandler->setSecondFactorVerified(false);
296
        $this->stateHandler->setSsoOn2faCookieFingerprint('');
297
    }
298
299
    /**
300
     * Retrieve the ResponseContextServiceId from state
301
     *
302
     * Used to determine we are dealing with an SFO or regular authentication. Both have different ResponseContext
303
     * instances, and it's imperative that successive consumers use the correct service.
304
     *
305
     * @return string|null
306
     */
307
    public function getResponseContextServiceId(): ?string
308
    {
309
        return $this->stateHandler->getResponseContextServiceId();
310
    }
311
312
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $assertion should have a doc-comment as per coding-style.
Loading history...
313
     * Either gets the internal-collabPersonId if present or falls back on the regular name id attribute
314
     * @throws Exception
0 ignored issues
show
Coding Style introduced by
There must be exactly one blank line before the tags in a doc comment
Loading history...
315
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
316
    private function resolveNameIdValue(Assertion $assertion): string
0 ignored issues
show
Coding Style introduced by
Private method name "ResponseContext::resolveNameIdValue" must be prefixed with an underscore
Loading history...
317
    {
318
        $attributes = $assertion->getAttributes();
319
        if (array_key_exists('urn:mace:surf.nl:attribute-def:internal-collabPersonId', $attributes)) {
320
            return reset($attributes['urn:mace:surf.nl:attribute-def:internal-collabPersonId']);
321
        }
322
        $nameId = $assertion->getNameId();
323
        if ($nameId && is_string($nameId->getValue())) {
324
            return $nameId->getValue();
325
        }
326
327
        throw new RuntimeException('Unable to resolve an identifier from internalCollabPersonId or the Subject NameId');
328
    }
329
330
    public function isForceAuthn(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function isForceAuthn()
Loading history...
331
    {
332
        return $this->stateHandler->isForceAuthn();
333
    }
334
335
    public function markVerifiedBySsoOn2faCookie(string $fingerprint)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function markVerifiedBySsoOn2faCookie()
Loading history...
336
    {
337
        $this->logger->notice('Marking markVerifiedBySsoOn2faCookie');
338
        $this->stateHandler->setVerifiedBySsoOn2faCookie(true);
339
        $this->stateHandler->setSsoOn2faCookieFingerprint($fingerprint);
340
    }
341
342
    public function isVerifiedBySsoOn2faCookie(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function isVerifiedBySsoOn2faCookie()
Loading history...
343
    {
344
        $isVerified = $this->stateHandler->isVerifiedBySsoOn2faCookie() ? 'YES': 'NO';
345
        $this->logger->notice(sprintf('Read isVerifiedBySsoOn2faCookie %s', $isVerified));
346
        return $this->stateHandler->isVerifiedBySsoOn2faCookie();
347
    }
348
}
349