Passed
Pull Request — master (#363)
by Tim
02:37
created

ServiceProvider::decryptAndVerifyAssertions()   B

Complexity

Conditions 11
Paths 49

Size

Total Lines 72
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 11
eloc 37
c 1
b 0
f 0
nc 49
nop 1
dl 0
loc 72
rs 7.3166

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\Entity;
6
7
use Exception;
8
use Psr\Http\Message\ServerRequestInterface;
9
use SimpleSAML\Assert\Assert;
10
use SimpleSAML\SAML2\{
11
    Binding,
12
    Metadata,
13
    MetadataProviderInterface,
14
    StateProviderInterface,
15
    StorageProviderInterface,
16
    Utils,
17
};
18
use SimpleSAML\SAML2\Binding\HTTPArtifact;
19
use SimpleSAML\SAML2\Exception\{MetadataNotFoundException, RemoteException, RuntimeException};
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\Exception\RemoteException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
use SimpleSAML\SAML2\Exception\Protocol\{RequestDeniedException, ResourceNotRecognizedException};
21
use SimpleSAML\SAML2\Process\Validator\ResponseValidator;
22
use SimpleSAML\SAML2\XML\saml\{
23
    Assertion,
24
    AttributeStatement,
25
    EncryptedAssertion,
26
    EncryptedAttribute,
27
    EncryptedID,
28
    Subject,
29
};
30
use SimpleSAML\SAML2\XML\samlp\Response;
31
use SimpleSAML\XMLSecurity\Alg\Encryption\EncryptionAlgorithmFactory;
32
use SimpleSAML\XMLSecurity\Alg\KeyTransport\KeyTransportAlgorithmFactory;
33
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
34
use SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException;
35
use SimpleSAML\XMLSecurity\XML\{
36
    EncryptableElementInterface,
37
    EncryptedElementInterface,
38
    SignableElementInterface,
39
    SignedElementInterface,
40
};
41
42
use function sprintf;
43
44
/**
45
 * Class representing a SAML 2 Service Provider.
46
 *
47
 * @package simplesamlphp/saml2
48
 */
49
final class ServiceProvider
50
{
51
    protected ?StateProviderInterface $stateProvider = null;
52
    protected ?StorageProviderInterface $storageProvider = null;
53
    protected ?Metadata\IdentityProvider $idpMetadata = null;
54
    protected SignatureAlgorithmFactory $signatureAlgorithmFactory;
55
    protected EncryptionAlgorithmFactory $encryptionAlgorithmFactory;
56
    protected KeyTransportAlgorithmFactory $keyTransportAlgorithmFactory;
57
    protected bool $responseWasSigned;
58
59
    /**
60
     * @param bool $performSchemaValidation  Whether messages must be validated against the schema
61
     * @param bool $encryptedAssertions  Whether assertions must be encrypted
62
     * @param bool $disableScoping  Whether to send the samlp:Scoping element in requests
63
     * @param bool $enableUnsolicited  Whether to process unsolicited responses
64
     * @param bool $encryptNameId  Whether to encrypt the NameID sent
65
     * @param bool $signAuthnRequest  Whether to sign the AuthnRequest sent
66
     * @param bool $signLogout  Whether to sign the LogoutRequest/LogoutResponse sent
67
     * @param bool $validateLogout  Whether to validate the signature of LogoutRequest/LogoutResponse received
68
     */
69
    public function __construct(
70
        protected MetadataProviderInterface $metadataProvider,
71
        protected Metadata\ServiceProvider $spMetadata,
72
        protected readonly bool $performSchemaValidation = true,
73
        protected readonly bool $encryptedAssertions = false,
74
        protected readonly bool $disableScoping = false,
75
        protected readonly bool $enableUnsolicited = false,
76
        protected readonly bool $encryptNameId = false,
77
        protected readonly bool $signAuthnRequest = false,
78
        protected readonly bool $signLogout = false,
79
        protected readonly bool $validateLogout = true,
80
        // Use with caution - will leave any form of signature verification or token decryption up to the implementer
81
        protected readonly bool $bypassResponseVerification = false,
82
        // Use with caution - will leave any form of constraint validation up to the implementer
83
        protected readonly bool $bypassConstraintValidation = false,
84
    ) {
85
        $this->signatureAlgorithmFactory = new SignatureAlgorithmFactory();
86
        $this->encryptionAlgorithmFactory = new EncryptionAlgorithmFactory();
87
        $this->keyTransportAlgorithmFactory = new KeyTransportAlgorithmFactory();
88
    }
89
90
91
    /**
92
     */
93
    public function setStateProvider(StateProviderInterface $stateProvider): void
94
    {
95
        $this->stateProvider = $stateProvider;
96
    }
97
98
99
    /**
100
     */
101
    public function setStorageProvider(StorageProviderInterface $storageProvider): void
102
    {
103
        $this->storageProvider = $storageProvider;
104
    }
105
106
107
    /**
108
     * Receive a verified, and optionally validated Response.
109
     *
110
     * Upon receiving the response from the binding, the signature will be validated first.
111
     * Once the signature checks out, the assertions are decrypted, their signatures verified
112
     *  and then any encrypted NameID's and/or attributes are decrypted.
113
     *
114
     * @param \Psr\Http\Message\ServerRequestInterface $request
115
     * @return \SimpleSAML\SAML2\XML\samlp\Response The validated response.
116
     *
117
     * @throws \SimpleSAML\SAML2\Exception\Protocol\UnsupportedBindingException
118
     */
119
    public function receiveResponse(ServerRequestInterface $request): Response
120
    {
121
        $binding = Binding::getCurrentBinding($request);
122
123
        if ($binding instanceof HTTPArtifact) {
124
            if ($this->storageProvider === null) {
125
                throw new RuntimeException(
126
                    "A StorageProvider is required to use the HTTP-Artifact binding.",
127
                );
128
            }
129
130
            $artifact = $binding->receiveArtifact($request);
0 ignored issues
show
Bug introduced by
The method receiveArtifact() does not exist on SimpleSAML\SAML2\Binding\HTTPArtifact. Did you maybe mean receive()? ( Ignorable by Annotation )

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

130
            /** @scrutinizer ignore-call */ 
131
            $artifact = $binding->receiveArtifact($request);

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...
131
            $this->idpMetadata = $this->metadataProvider->getIdPMetadataForSha1($artifact->getSourceId());
132
133
            if ($this->idpMetadata === null) {
134
                throw new MetadataNotFoundException(sprintf(
135
                    'No metadata found for remote entity with SHA1 ID: %s',
136
                    $artifact->getSourceId(),
137
                ));
138
            }
139
140
            $binding->setIdpMetadata($this->idpMetadata);
0 ignored issues
show
Bug introduced by
The method setIdpMetadata() does not exist on SimpleSAML\SAML2\Binding\HTTPArtifact. ( Ignorable by Annotation )

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

140
            $binding->/** @scrutinizer ignore-call */ 
141
                      setIdpMetadata($this->idpMetadata);

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...
141
            $binding->setSPMetadata($this->spMetadata);
142
        }
143
144
        $binding->setSchemaValidation($this->performSchemaValidation);
145
        $rawResponse = $binding->receive($request);
146
        Assert::isInstanceOf($rawResponse, Response::class, ResourceNotRecognizedException::class); // Wrong type of msg
147
148
        // Will return a raw Response prior to any form of verification
149
        if ($this->bypassResponseVerification === true) {
150
            return $rawResponse;
151
        }
152
153
        // Fetch the metadata for the remote entity
154
        if (!($binding instanceof HTTPArtifact)) {
155
            $this->idpMetadata = $this->metadataProvider->getIdPMetadata($rawResponse->getIssuer()->getContent());
156
157
            if ($this->idpMetadata === null) {
158
                throw new MetadataNotFoundException(sprintf(
159
                    'No metadata found for remote entity with entityID: %s',
160
                    $rawResponse->getIssuer()->getContent(),
161
                ));
162
            }
163
        }
164
165
        // Verify the signature (if any)
166
        $this->responseWasSigned = $rawResponse->isSigned();
167
        $verifiedResponse = $this->responseWasSigned ? $this->verifyElementSignature($rawResponse) : $rawResponse;
168
169
        $state = null;
170
        $stateId = $verifiedResponse->getInResponseTo();
0 ignored issues
show
Bug introduced by
The method getInResponseTo() does not exist on SimpleSAML\SAML2\XML\samlp\AbstractMessage. It seems like you code against a sub-type of SimpleSAML\SAML2\XML\samlp\AbstractMessage such as SimpleSAML\SAML2\XML\samlp\AbstractStatusResponse. ( Ignorable by Annotation )

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

170
        /** @scrutinizer ignore-call */ 
171
        $stateId = $verifiedResponse->getInResponseTo();
Loading history...
Bug introduced by
The method getInResponseTo() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResolve. ( Ignorable by Annotation )

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

170
        /** @scrutinizer ignore-call */ 
171
        $stateId = $verifiedResponse->getInResponseTo();

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...
Bug introduced by
The method getInResponseTo() does not exist on SimpleSAML\SAML2\XML\samlp\AttributeQuery. ( Ignorable by Annotation )

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

170
        /** @scrutinizer ignore-call */ 
171
        $stateId = $verifiedResponse->getInResponseTo();

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...
Bug introduced by
The method getInResponseTo() does not exist on SimpleSAML\SAML2\XML\samlp\AuthnRequest. ( Ignorable by Annotation )

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

170
        /** @scrutinizer ignore-call */ 
171
        $stateId = $verifiedResponse->getInResponseTo();

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...
Bug introduced by
The method getInResponseTo() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractStatusResponse. ( Ignorable by Annotation )

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

170
        /** @scrutinizer ignore-call */ 
171
        $stateId = $verifiedResponse->getInResponseTo();
Loading history...
Bug introduced by
The method getInResponseTo() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutRequest. ( Ignorable by Annotation )

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

170
        /** @scrutinizer ignore-call */ 
171
        $stateId = $verifiedResponse->getInResponseTo();

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...
171
172
        if (!empty($stateId)) {
173
            if ($this->stateProvider === null) {
174
                throw new RuntimeException(
175
                    "A StateProvider is required to correlate responses to their initial request.",
176
                );
177
            }
178
179
            // this should be a response to a request we sent earlier
180
            try {
181
                $state = $this->stateProvider::loadState($stateId, 'saml:sp:sso');
182
            } catch (RuntimeException $e) {
183
                // something went wrong,
184
                Utils::getContainer()->getLogger()->warning(sprintf(
185
                    'Could not load state specified by InResponseTo: %s; processing response as unsolicited.',
186
                    $e->getMessage(),
187
                ));
188
            }
189
        }
190
191
        $issuer = $verifiedResponse->getIssuer()->getContent();
0 ignored issues
show
Bug introduced by
The method getIssuer() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractMessage or SimpleSAML\SAML2\XML\saml\Assertion. ( Ignorable by Annotation )

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

191
        $issuer = $verifiedResponse->/** @scrutinizer ignore-call */ getIssuer()->getContent();
Loading history...
192
        if ($state === null) {
193
            if ($this->enableUnsolicited === false) {
194
                throw new RequestDeniedException('Unsolicited responses are denied by configuration.');
195
            }
196
        } else {
197
            // check that the issuer is the one we are expecting
198
            Assert::keyExists($state, 'ExpectedIssuer');
199
200
            if ($state['ExpectedIssuer'] !== $issuer) {
201
                throw new ResourceNotRecognizedException("Issuer doesn't match the one the AuthnRequest was sent to.");
202
            }
203
        }
204
205
        $this->idpMetadata = $this->metadataProvider->getIdPMetadata($issuer);
206
        if ($this->idpMetadata === null) {
207
            throw new MetadataNotFoundException(sprintf(
208
                'No metadata found for remote identity provider with entityID: %s',
209
                $issuer,
210
            ));
211
        }
212
213
        $responseValidator = ResponseValidator::createResponseValidator(
214
            $this->idpMetadata,
215
            $this->spMetadata,
216
            $binding,
217
        );
218
        $responseValidator->validate($verifiedResponse);
219
220
        if ($this->encryptedAssertions === true) {
221
            Assert::allIsInstanceOf($verifiedResponse->getAssertions(), EncryptedAssertion::class);
0 ignored issues
show
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResponse. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);

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...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\AbstractMessage. It seems like you code against a sub-type of SimpleSAML\SAML2\XML\samlp\AbstractMessage such as SimpleSAML\SAML2\XML\samlp\Response. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);
Loading history...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\AttributeQuery. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);

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...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\Response. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);
Loading history...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutResponse. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);

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...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\AuthnRequest. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);

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...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutRequest. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);

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...
Bug introduced by
The method getAssertions() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResolve. ( Ignorable by Annotation )

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

221
            Assert::allIsInstanceOf($verifiedResponse->/** @scrutinizer ignore-call */ getAssertions(), EncryptedAssertion::class);

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...
222
        }
223
224
        // Decrypt and verify assertions, then rebuild the response.
225
        $verifiedAssertions = $this->decryptAndVerifyAssertions($verifiedResponse->getAssertions());
226
        $decryptedResponse = new Response(
227
            $verifiedResponse->getStatus(),
0 ignored issues
show
Bug introduced by
The method getStatus() does not exist on SimpleSAML\SAML2\XML\samlp\LogoutRequest. ( Ignorable by Annotation )

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

227
            $verifiedResponse->/** @scrutinizer ignore-call */ 
228
                               getStatus(),

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...
Bug introduced by
The method getStatus() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractStatusResponse. ( Ignorable by Annotation )

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

227
            $verifiedResponse->/** @scrutinizer ignore-call */ 
228
                               getStatus(),
Loading history...
Bug introduced by
The method getStatus() does not exist on SimpleSAML\SAML2\XML\samlp\AbstractMessage. It seems like you code against a sub-type of SimpleSAML\SAML2\XML\samlp\AbstractMessage such as SimpleSAML\SAML2\XML\samlp\AbstractStatusResponse. ( Ignorable by Annotation )

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

227
            $verifiedResponse->/** @scrutinizer ignore-call */ 
228
                               getStatus(),
Loading history...
Bug introduced by
The method getStatus() does not exist on SimpleSAML\SAML2\XML\samlp\AttributeQuery. ( Ignorable by Annotation )

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

227
            $verifiedResponse->/** @scrutinizer ignore-call */ 
228
                               getStatus(),

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...
Bug introduced by
The method getStatus() does not exist on SimpleSAML\SAML2\XML\samlp\AuthnRequest. ( Ignorable by Annotation )

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

227
            $verifiedResponse->/** @scrutinizer ignore-call */ 
228
                               getStatus(),

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...
Bug introduced by
The method getStatus() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResolve. ( Ignorable by Annotation )

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

227
            $verifiedResponse->/** @scrutinizer ignore-call */ 
228
                               getStatus(),

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...
228
            $verifiedResponse->getIssueInstant(),
0 ignored issues
show
Bug introduced by
The method getIssueInstant() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractMessage or SimpleSAML\SAML2\XML\saml\Assertion. ( Ignorable by Annotation )

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

228
            $verifiedResponse->/** @scrutinizer ignore-call */ 
229
                               getIssueInstant(),
Loading history...
229
            $verifiedResponse->getIssuer(),
230
            $verifiedResponse->getID(),
231
            $verifiedResponse->getVersion(),
0 ignored issues
show
Bug introduced by
The method getVersion() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractMessage. ( Ignorable by Annotation )

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

231
            $verifiedResponse->/** @scrutinizer ignore-call */ 
232
                               getVersion(),
Loading history...
232
            $verifiedResponse->getInResponseTo(),
233
            $verifiedResponse->getDestination(),
0 ignored issues
show
Bug introduced by
The method getDestination() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractMessage. ( Ignorable by Annotation )

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

233
            $verifiedResponse->/** @scrutinizer ignore-call */ 
234
                               getDestination(),
Loading history...
234
            $verifiedResponse->getConsent(),
0 ignored issues
show
Bug introduced by
The method getConsent() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractMessage. ( Ignorable by Annotation )

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

234
            $verifiedResponse->/** @scrutinizer ignore-call */ 
235
                               getConsent(),
Loading history...
235
            $verifiedResponse->getExtensions(),
0 ignored issues
show
Bug introduced by
The method getExtensions() does not exist on SimpleSAML\XMLSecurity\X...ignableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...ignableElementInterface such as SimpleSAML\SAML2\XML\samlp\AbstractMessage or SimpleSAML\SAML2\XML\md\AbstractMetadataDocument. ( Ignorable by Annotation )

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

235
            $verifiedResponse->/** @scrutinizer ignore-call */ 
236
                               getExtensions(),
Loading history...
236
            $verifiedAssertions,
237
        );
238
239
240
        // Will return a verified and fully decrypted Response prior to any form of validation
241
        if ($this->bypassConstraintValidation === true) {
242
            return $decryptedResponse;
243
        }
244
245
        // TODO: Validate assertions
246
        return $decryptedResponse;
247
    }
248
249
250
    /**
251
     * Process the assertions and decrypt any encrypted elements inside.
252
     *
253
     * @param \SimpleSAML\SAML2\XML\saml\Assertion[] $unverifiedAssertions
254
     * @return \SimpleSAML\SAML2\XML\saml\Assertion[]
255
     *
256
     * @throws \SimpleSAML\SAML2\Exception\RuntimeException if none of the keys could be used to decrypt the element
257
     */
258
    protected function decryptAndVerifyAssertions(array $unverifiedAssertions): array
259
    {
260
        $wantAssertionsSigned = $this->spMetadata->getWantAssertionsSigned();
261
262
        /**
263
         * See paragraph 6.2 of the SAML 2.0 core specifications for the applicable processing rules
264
         *
265
         * Long story short - Decrypt the assertion first, then validate it's signature
266
         * Once the signature is verified, decrypt any BaseID, NameID or Attribute that's encrypted
267
         */
268
        $verifiedAssertions = [];
269
        foreach ($unverifiedAssertions as $i => $assertion) {
270
            // Decrypt the assertions
271
            $decryptedAssertion = ($assertion instanceof EncryptedAssertion)
272
                ? $this->decryptElement($assertion)
273
                : $assertion;
274
275
            // Verify that the request is signed, if we require this by configuration
276
            if ($wantAssertionsSigned === true) {
277
                Assert::true($decryptedAssertion->isSigned(), RuntimeException::class);
0 ignored issues
show
Bug introduced by
The method isSigned() does not exist on SimpleSAML\XMLSecurity\X...yptableElementInterface. It seems like you code against a sub-type of SimpleSAML\XMLSecurity\X...yptableElementInterface such as SimpleSAML\SAML2\XML\saml\Assertion. ( Ignorable by Annotation )

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

277
                Assert::true($decryptedAssertion->/** @scrutinizer ignore-call */ isSigned(), RuntimeException::class);
Loading history...
278
            }
279
280
            // Verify the signature on the assertions (if any)
281
            $verifiedAssertion = $this->verifyElementSignature($decryptedAssertion);
0 ignored issues
show
Bug introduced by
It seems like $decryptedAssertion can also be of type SimpleSAML\XMLSecurity\X...yptableElementInterface; however, parameter $element of SimpleSAML\SAML2\Entity\...erifyElementSignature() does only seem to accept SimpleSAML\XMLSecurity\XML\SignedElementInterface, 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

281
            $verifiedAssertion = $this->verifyElementSignature(/** @scrutinizer ignore-type */ $decryptedAssertion);
Loading history...
282
283
            // Decrypt the NameID and replace it inside the assertion's Subject
284
            $nameID = $verifiedAssertion->getSubject()?->getIdentifier();
285
286
            if ($nameID instanceof EncryptedID) {
287
                $decryptedNameID = $this->decryptElement($nameID);
288
                // Anything we can't decrypt, we leave up for the application to deal with
289
                try {
290
                    $subject = new Subject($decryptedNameID, $verifiedAssertion->getSubjectConfirmation());
0 ignored issues
show
Bug introduced by
The method getSubjectConfirmation() does not exist on SimpleSAML\SAML2\XML\saml\Assertion. Did you maybe mean getSubject()? ( Ignorable by Annotation )

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

290
                    $subject = new Subject($decryptedNameID, $verifiedAssertion->/** @scrutinizer ignore-call */ getSubjectConfirmation());

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...
291
                } catch (RuntimeException) {
292
                    $subject = $verifiedAssertion->getSubject();
293
                }
294
            } else {
295
                $subject = $verifiedAssertion->getSubject();
296
            }
297
298
            // Decrypt any occurrences of EncryptedAttribute and replace them inside the assertion's AttributeStatement
299
            $statements = $verifiedAssertion->getStatements();
300
            foreach ($verifiedAssertion->getStatements() as $j => $statement) {
301
                if ($statement instanceof AttributeStatement) {
302
                    $attributes = $statement->getAttributes();
303
                    if ($statement->hasEncryptedAttributes()) {
304
                        foreach ($statement->getEncryptedAttributes() as $encryptedAttribute) {
305
                            // Anything we can't decrypt, we leave up for the application to deal with
306
                            try {
307
                                $attributes[] = $this->decryptElement($encryptedAttribute);
308
                            } catch (RuntimeException) {
309
                                $attributes[] = $encryptedAttribute;
310
                            }
311
                        }
312
                    }
313
314
                    $statements[$j] = new AttributeStatement($attributes);
315
                }
316
            }
317
318
            // Rebuild the Assertion
319
            $verifiedAssertions[] = new Assertion(
320
                $verifiedAssertion->getIssuer(),
321
                $verifiedAssertion->getIssueInstant(),
322
                $verifiedAssertion->getID(),
323
                $subject,
324
                $verifiedAssertion->getConditions(),
325
                $statements,
326
            );
327
        }
328
329
        return $verifiedAssertions;
330
    }
331
332
333
    /**
334
     * Decrypt the given element using the decryption keys provided to us.
335
     *
336
     * @param \SimpleSAML\XMLSecurity\XML\EncryptedElementInterface $element
337
     * @return \SimpleSAML\XMLSecurity\EncryptableElementInterface
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSecurity\EncryptableElementInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
338
     *
339
     * @throws \SimpleSAML\SAML2\Exception\RuntimeException if none of the keys could be used to decrypt the element
340
     */
341
    protected function decryptElement(EncryptedElementInterface $element): EncryptableElementInterface
342
    {
343
        $factory = $this->encryptionAlgorithmFactory;
344
345
        // If the IDP has a pre-shared key, try decrypting with that
346
        $preSharedKey = $this->idpMetadata->getPreSharedKey();
0 ignored issues
show
Bug introduced by
The method getPreSharedKey() 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

346
        /** @scrutinizer ignore-call */ 
347
        $preSharedKey = $this->idpMetadata->getPreSharedKey();

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...
347
        if ($preSharedKey !== null) {
348
            $encryptionAlgorithm = $element?->getEncryptedKey()?->getEncryptionMethod()
349
              ?? $this->idpMetadata->getPreSharedKeyAlgorithm();
350
351
            $decryptor = $factory->getAlgorithm($encryptionAlgorithm, $preSharedKey);
352
            try {
353
                return $element->decrypt($decryptor);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $element->decrypt($decryptor) returns the type SimpleSAML\XML\ElementInterface which is incompatible with the type-hinted return SimpleSAML\XMLSecurity\X...yptableElementInterface.
Loading history...
354
            } catch (Exception $e) {
355
                // Continue to try decrypting with asymmetric keys.
356
            }
357
        }
358
359
        $encryptionAlgorithm = $element->getEncryptedKey()->getEncryptionMethod()->getAlgorithm();
360
        foreach ($this->spMetadata->getDecryptionKeys() as $decryptionKey) {
361
            $factory = $this->keyTransportAlgorithmFactory;
362
            $decryptor = $factory->getAlgorithm($encryptionAlgorithm, $decryptionKey);
363
            try {
364
                return $element->decrypt($decryptor);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $element->decrypt($decryptor) returns the type SimpleSAML\XML\ElementInterface which is incompatible with the type-hinted return SimpleSAML\XMLSecurity\X...yptableElementInterface.
Loading history...
365
            } catch (Exception $e) {
366
                continue;
367
            }
368
        }
369
370
        throw new RuntimeException(sprintf(
371
            'Unable to decrypt %s with any of the available keys.',
372
            $element::class,
373
        ));
374
    }
375
376
377
    /**
378
     * Verify the signature of an element using the available validation keys.
379
     *
380
     * @param \SimpleSAML\XMLSecurity\XML\SignedElementInterface $element
381
     * @return \SimpleSAML\XMLSecurity\XML\SignableElementInterface The validated element.
382
     *
383
     * @throws \SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException
384
     */
385
    protected function verifyElementSignature(SignedElementInterface $element): SignableElementInterface
386
    {
387
        $signatureAlgorithm = $element->getSignature()->getSignedInfo()->getSignatureMethod()->getAlgorithm();
388
389
        foreach ($this->idpMetadata->getValidatingKeys() as $validatingKey) {
390
            $verifier = $this->signatureAlgorithmFactory->getAlgorithm($signatureAlgorithm, $validatingKey);
391
392
            try {
393
                return $element->verify($verifier);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $element->verify($verifier) returns the type SimpleSAML\XMLSecurity\XML\SignedElementInterface which is incompatible with the type-hinted return SimpleSAML\XMLSecurity\X...ignableElementInterface.
Loading history...
394
            } catch (SignatureVerificationFailedException $e) {
395
                continue;
396
            }
397
        }
398
399
        throw new SignatureVerificationFailedException();
400
    }
401
}
402