Passed
Push — master ( ec27ff...f3e803 )
by Tim
02:29
created

HTTPArtifact::validateSignature()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

229
        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...
230
    }
231
}
232