Passed
Push — feature/store-usage-of-sso-coo... ( 838c4d...39300e )
by Michiel
04:53
created

ResponseContext::isSecondFactorVerified()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
class ResponseContext
0 ignored issues
show
Coding Style introduced by
Missing doc comment for class ResponseContext
Loading history...
37
{
38
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
39
     * @var IdentityProvider
40
     */
41
    private $hostedIdentityProvider;
0 ignored issues
show
Coding Style introduced by
Private member variable "hostedIdentityProvider" must be prefixed with an underscore
Loading history...
42
43
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
44
     * @var SamlEntityService
45
     */
46
    private $samlEntityService;
0 ignored issues
show
Coding Style introduced by
Private member variable "samlEntityService" must be prefixed with an underscore
Loading history...
47
48
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
49
     * @var ProxyStateHandler
50
     */
51
    private $stateHandler;
0 ignored issues
show
Coding Style introduced by
Private member variable "stateHandler" must be prefixed with an underscore
Loading history...
52
53
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
54
     * @var LoggerInterface
55
     */
56
    private $logger;
0 ignored issues
show
Coding Style introduced by
Private member variable "logger" must be prefixed with an underscore
Loading history...
57
58
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
59
     * @var DateTime
60
     */
61
    private $generationTime;
0 ignored issues
show
Coding Style introduced by
Private member variable "generationTime" must be prefixed with an underscore
Loading history...
62
63
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
64
     * @var ServiceProvider
65
     */
66
    private $targetServiceProvider;
0 ignored issues
show
Coding Style introduced by
Private member variable "targetServiceProvider" must be prefixed with an underscore
Loading history...
67
68
    /**
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...
69
     * @throws Exception
70
     */
71
    public function __construct(
72
        IdentityProvider $identityProvider,
73
        SamlEntityService $samlEntityService,
74
        ProxyStateHandler $stateHandler,
75
        LoggerInterface $logger,
76
        DateTime $now = null
77
    ) {
78
        $this->hostedIdentityProvider = $identityProvider;
79
        $this->samlEntityService      = $samlEntityService;
80
        $this->stateHandler           = $stateHandler;
81
        $this->logger                 = $logger;
82
        $this->generationTime         = is_null($now) ? new DateTime('now', new DateTimeZone('UTC')): $now;
83
    }
84
85
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
86
     * @return string
87
     */
88
    public function getDestination(): string
89
    {
90
        $requestAcsUrl = $this->stateHandler->getRequestAssertionConsumerServiceUrl();
91
92
        return $this->getServiceProvider()->determineAcsLocation($requestAcsUrl, $this->logger);
93
    }
94
95
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
96
     * @return string
97
     * @throws AcsLocationNotAllowedException
98
     */
99
    public function getDestinationForAdfs(): string
100
    {
101
        $requestAcsUrl = $this->stateHandler->getRequestAssertionConsumerServiceUrl();
102
103
        return $this->getServiceProvider()->determineAcsLocationForAdfs($requestAcsUrl);
104
    }
105
106
    public function getIssuer(): Issuer
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getIssuer()
Loading history...
107
    {
108
        $issuer = new Issuer();
109
        $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

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

195
        $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...
196
    }
197
198
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
199
     * @return Assertion
200
     * @throws Exception
201
     */
202
    public function reconstituteAssertion(): Assertion
203
    {
204
        $assertionAsXML    = $this->stateHandler->getAssertion();
205
        $assertionDocument = new DOMDocument();
206
        $assertionDocument->loadXML($assertionAsXML);
207
208
        return new Assertion($assertionDocument->documentElement);
209
    }
210
211
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
212
     * @return null|string
213
     */
214
    public function getIdentityNameId(): string
215
    {
216
        return $this->stateHandler->getIdentityNameId();
217
    }
218
219
    /**
220
     * Return the lower-cased schacHomeOrganization value from the assertion.
221
     *
222
     * Comparisons on SHO values should always be case-insensitive. Stepup
223
     * configuration always contains SHO values lower-cased, so this getter
224
     * can be used to compare the SHO with configured values.
225
     *
226
     * @see StepUpAuthenticationService::resolveHighestRequiredLoa()
227
     *
228
     * @return null|string
229
     */
230
    public function getNormalizedSchacHomeOrganization(): ?string
231
    {
232
        return strtolower(
233
            $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

233
            /** @scrutinizer ignore-type */ $this->stateHandler->getSchacHomeOrganization()
Loading history...
234
        );
235
    }
236
237
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
238
     * @param SecondFactor $secondFactor
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
239
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
240
    public function saveSelectedSecondFactor(SecondFactor $secondFactor): void
241
    {
242
        $this->stateHandler->setSelectedSecondFactorId($secondFactor->secondFactorId);
243
        $this->stateHandler->setSecondFactorVerified(false);
244
        $this->stateHandler->setPreferredLocale($secondFactor->displayLocale);
245
    }
246
247
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
248
     * @return null|string
249
     */
250
    public function getSelectedSecondFactor(): ?string
251
    {
252
        return $this->stateHandler->getSelectedSecondFactorId();
253
    }
254
255
    public function markSecondFactorVerified(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function markSecondFactorVerified()
Loading history...
256
    {
257
        $this->stateHandler->setSecondFactorVerified(true);
258
    }
259
260
    public function finalizeAuthentication(): void
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function finalizeAuthentication()
Loading history...
261
    {
262
        $this->stateHandler->setSelectedSecondFactorId(null);
263
    }
264
265
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
266
     * @return bool
267
     */
268
    public function isSecondFactorVerified(): bool
269
    {
270
        return $this->stateHandler->getSelectedSecondFactorId() && $this->stateHandler->isSecondFactorVerified();
271
    }
272
273
    public function getResponseAction(): ?string
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function getResponseAction()
Loading history...
274
    {
275
        return $this->stateHandler->getResponseAction();
276
    }
277
278
    /**
279
     * Resets some state after the response is sent
280
     * (e.g. resets which second factor was selected and whether it was verified).
281
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
282
    public function responseSent()
283
    {
284
        $this->stateHandler->setSecondFactorVerified(false);
285
        $this->stateHandler->setVerifiedBySsoOn2faCookie(false);
286
        $this->stateHandler->setSsoOn2faCookieFingerprint('');
287
    }
288
289
    /**
290
     * Retrieve the ResponseContextServiceId from state
291
     *
292
     * Used to determine we are dealing with an SFO or regular authentication. Both have different ResponseContext
293
     * instances, and it's imperative that successive consumers use the correct service.
294
     *
295
     * @return string|null
296
     */
297
    public function getResponseContextServiceId(): ?string
298
    {
299
        return $this->stateHandler->getResponseContextServiceId();
300
    }
301
302
    /**
0 ignored issues
show
Coding Style introduced by
Parameter $assertion should have a doc-comment as per coding-style.
Loading history...
303
     * Either gets the internal-collabPersonId if present or falls back on the regular name id attribute
304
     * @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...
305
     */
0 ignored issues
show
Coding Style introduced by
Missing @return tag in function comment
Loading history...
306
    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...
307
    {
308
        $attributes = $assertion->getAttributes();
309
        if (array_key_exists('urn:mace:surf.nl:attribute-def:internal-collabPersonId', $attributes)) {
310
            return reset($attributes['urn:mace:surf.nl:attribute-def:internal-collabPersonId']);
311
        }
312
        $nameId = $assertion->getNameId();
313
        if ($nameId && is_string($nameId->getValue())) {
314
            return $nameId->getValue();
315
        }
316
317
        throw new RuntimeException('Unable to resolve an identifier from internalCollabPersonId or the Subject NameId');
318
    }
319
320
    public function isForceAuthn(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function isForceAuthn()
Loading history...
321
    {
322
        return $this->stateHandler->isForceAuthn();
323
    }
324
325
    public function markVerifiedBySsoOn2faCookie(string $fingerprint)
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function markVerifiedBySsoOn2faCookie()
Loading history...
326
    {
327
        $this->stateHandler->setVerifiedBySsoOn2faCookie(true);
328
        $this->stateHandler->setSsoOn2faCookieFingerprint($fingerprint);
329
    }
330
331
    public function isVerifiedBySsoOn2faCookie(): bool
0 ignored issues
show
Coding Style introduced by
Missing doc comment for function isVerifiedBySsoOn2faCookie()
Loading history...
332
    {
333
        return $this->stateHandler->isVerifiedBySsoOn2faCookie();
334
    }
335
}
336