HTTPArtifact::receive()   B
last analyzed

Complexity

Conditions 9
Paths 17

Size

Total Lines 79
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 36
nc 17
nop 1
dl 0
loc 79
rs 8.0555
c 0
b 0
f 0

How to fix   Long Method   

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\Binding;
6
7
use DateInterval;
8
use Exception;
9
use Nyholm\Psr7\Response;
10
use Psr\Http\Message\ResponseInterface;
11
use Psr\Http\Message\ServerRequestInterface;
12
use SimpleSAML\Assert\Assert;
13
use SimpleSAML\Configuration;
14
use SimpleSAML\Metadata\MetaDataStorageHandler;
15
use SimpleSAML\Module\saml\Message as MSG;
16
use SimpleSAML\SAML2\Binding;
17
use SimpleSAML\SAML2\Binding\RelayStateTrait;
18
use SimpleSAML\SAML2\SOAPClient;
19
use SimpleSAML\SAML2\Utils;
20
use SimpleSAML\SAML2\XML\saml\Issuer;
21
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
22
use SimpleSAML\SAML2\XML\samlp\Artifact;
23
use SimpleSAML\SAML2\XML\samlp\ArtifactResolve;
24
use SimpleSAML\SAML2\XML\samlp\ArtifactResponse;
25
use SimpleSAML\Store\StoreFactory;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\Store\StoreFactory 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...
26
use SimpleSAML\Utils\HTTP;
27
use SimpleSAML\XMLSecurity\XMLSecurityKey;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSecurity\XMLSecurityKey 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...
28
29
use function array_key_exists;
30
use function base64_decode;
31
use function base64_encode;
32
use function bin2hex;
33
use function hexdec;
34
use function openssl_random_pseudo_bytes;
35
use function pack;
36
use function sha1;
37
use function substr;
38
use function var_export;
39
40
/**
41
 * Class which implements the HTTP-Artifact binding.
42
 *
43
 * @package simplesamlphp/saml2
44
 */
45
class HTTPArtifact extends Binding
46
{
47
    use RelayStateTrait;
48
49
    /**
50
     * @psalm-suppress UndefinedDocblockClass
51
     * @psalm-suppress UndefinedClass
52
     * @var \SimpleSAML\Configuration
53
     */
54
    private Configuration $spMetadata;
55
56
57
    /**
58
     * Create the redirect URL for a message.
59
     *
60
     * @param  \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message.
61
     * @throws \Exception
62
     * @return string        The URL the user should be redirected to in order to send a message.
63
     */
64
    public function getRedirectURL(AbstractMessage $message): string
65
    {
66
        /** @psalm-suppress UndefinedClass */
67
        $config = Configuration::getInstance();
68
69
        /** @psalm-suppress UndefinedClass */
70
        $store = StoreFactory::getInstance($config->getString('store.type'));
71
        if ($store === false) {
72
            throw new Exception('Unable to send artifact without a datastore configured.');
73
        }
74
75
        $generatedId = pack('H*', bin2hex(openssl_random_pseudo_bytes(20)));
76
        $issuer = $message->getIssuer();
77
        if ($issuer === null) {
78
            throw new Exception('Cannot get redirect URL, no Issuer set in the message.');
79
        }
80
        $artifact = base64_encode("\x00\x04\x00\x00" . sha1($issuer->getContent(), true) . $generatedId);
81
        $artifactData = $message->toXML();
82
        $artifactDataString = $artifactData->ownerDocument?->saveXML($artifactData);
83
84
        $clock = Utils::getContainer()->getClock();
85
        $store->set('artifact', $artifact, $artifactDataString, $clock->now()->add(new DateInterval('PT15M')));
86
87
        $destination = $message->getDestination();
88
        if ($destination === null) {
89
            throw new Exception('Cannot get redirect URL, no destination set in the message.');
90
        }
91
92
        $params = ['SAMLart' => $artifact];
93
94
        $relayState = $this->getRelayState();
95
        if ($relayState !== null) {
96
            $params['RelayState'] = $relayState;
97
        }
98
99
        /** @psalm-suppress UndefinedClass */
100
        $httpUtils = new HTTP();
101
        return $httpUtils->addURLparameters($destination, $params);
102
    }
103
104
105
    /**
106
     * Send a SAML 2 message using the HTTP-Redirect binding.
107
     *
108
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
109
     * @return \Psr\Http\Message\ResponseInterface
110
     */
111
    public function send(AbstractMessage $message): ResponseInterface
112
    {
113
        $destination = $this->getRedirectURL($message);
114
        return new Response(303, ['Location' => $destination]);
115
    }
116
117
118
    /**
119
     * Receive a SAML 2 message sent using the HTTP-Artifact binding.
120
     *
121
     * Throws an exception if it is unable receive the message.
122
     *
123
     * @param \Psr\Http\Message\ServerRequestInterface $request
124
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
125
     *
126
     * @throws \Exception
127
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
128
     */
129
    public function receive(ServerRequestInterface $request): AbstractMessage
130
    {
131
        $query = $request->getQueryParams();
132
        if (array_key_exists('SAMLart', $query)) {
133
            $artifact = base64_decode($query['SAMLart'], true);
134
            $endpointIndex = bin2hex(substr($artifact, 2, 2));
135
            $sourceId = bin2hex(substr($artifact, 4, 20));
136
        } else {
137
            throw new Exception('Missing SAMLart parameter.');
138
        }
139
140
        /** @psalm-suppress UndefinedClass */
141
        $metadataHandler = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance());
142
143
        $idpMetadata = $metadataHandler->getMetaDataConfigForSha1($sourceId, 'saml20-idp-remote');
144
145
        if ($idpMetadata === null) {
146
            throw new Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($sourceId, true));
147
        }
148
149
        $endpoint = null;
150
        foreach ($idpMetadata->getEndpoints('ArtifactResolutionService') as $ep) {
151
            if ($ep['index'] === hexdec($endpointIndex)) {
152
                $endpoint = $ep;
153
                break;
154
            }
155
        }
156
157
        if ($endpoint === null) {
158
            throw new Exception('No ArtifactResolutionService with the correct index.');
159
        }
160
161
        Utils::getContainer()->getLogger()->debug(
162
            "ArtifactResolutionService endpoint being used is := " . $endpoint['Location'],
163
        );
164
165
        /**
166
         * @psalm-suppress UndefinedClass
167
         * @psalm-suppress DocblockTypeContradiction
168
         */
169
        Assert::notEmpty($this->spMetadata, 'Cannot process received message without SP metadata.');
170
171
        /**
172
         * Set the request attributes
173
         */
174
        $issuer = new Issuer($this->spMetadata->getString('entityid'));
175
176
        // Construct the ArtifactResolve Request
177
        $ar = new ArtifactResolve(new Artifact($artifact), null, $issuer, null, '2.0', $endpoint['Location']);
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type DateTimeImmutable expected by parameter $issueInstant of SimpleSAML\SAML2\XML\sam...tResolve::__construct(). ( Ignorable by Annotation )

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

177
        $ar = new ArtifactResolve(new Artifact($artifact), /** @scrutinizer ignore-type */ null, $issuer, null, '2.0', $endpoint['Location']);
Loading history...
178
179
        // sign the request
180
        /** @psalm-suppress UndefinedClass */
181
        MSG::addSign($this->spMetadata, $idpMetadata, $ar); // Shoaib - moved from the SOAPClient.
182
183
        $soap = new SOAPClient();
184
185
        // Send message through SoapClient
186
        /** @var \SimpleSAML\SAML2\XML\samlp\ArtifactResponse $artifactResponse */
187
        $artifactResponse = $soap->send($ar, $this->spMetadata, $idpMetadata);
188
189
        if (!$artifactResponse->isSuccess()) {
190
            throw new Exception('Received error from ArtifactResolutionService.');
191
        }
192
193
        $samlResponse = $artifactResponse->getMessage();
194
        if ($samlResponse === null) {
195
            /* Empty ArtifactResponse - possibly because of Artifact replay? */
196
197
            throw new Exception('Empty ArtifactResponse received, maybe a replay?');
198
        }
199
200
        $samlResponse->addValidator([get_class($this), 'validateSignature'], $artifactResponse);
0 ignored issues
show
Bug introduced by
The method addValidator() does not exist on 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

200
        $samlResponse->/** @scrutinizer ignore-call */ 
201
                       addValidator([get_class($this), 'validateSignature'], $artifactResponse);

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...
201
202
        $query = $request->getQueryParams();
203
        if (isset($query['RelayState'])) {
204
            $this->setRelayState($query['RelayState']);
205
        }
206
207
        return $samlResponse;
208
    }
209
210
211
    /**
212
     * @param \SimpleSAML\Configuration $sp
213
     *
214
     * @psalm-suppress UndefinedClass
215
     */
216
    public function setSPMetadata(Configuration $sp): void
217
    {
218
        $this->spMetadata = $sp;
219
    }
220
221
222
    /**
223
     * A validator which returns true if the ArtifactResponse was signed with the given key
224
     *
225
     * @param \SimpleSAML\SAML2\XML\samlp\ArtifactResponse $message
226
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key
227
     * @return bool
228
     */
229
    public static function validateSignature(ArtifactResponse $message, XMLSecurityKey $key): bool
230
    {
231
        // @todo verify if this works and/or needs to do anything more. Ref. HTTPRedirect binding
232
        return $message->validate($key);
0 ignored issues
show
Bug introduced by
The method validate() does not exist on SimpleSAML\SAML2\XML\samlp\ArtifactResponse. Did you maybe mean validateReference()? ( Ignorable by Annotation )

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

232
        return $message->/** @scrutinizer ignore-call */ validate($key);

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...
233
    }
234
}
235