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

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

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

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