HTTPArtifact   A
last analyzed

Complexity

Total Complexity 17

Size/Duplication

Total Lines 176
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 64
dl 0
loc 176
rs 10
c 0
b 0
f 0
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
A send() 0 4 1
A getRedirectURL() 0 35 5
A validateSignature() 0 4 1
A setSPMetadata() 0 3 1
B receive() 0 73 9
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\Configuration;
13
use SimpleSAML\Metadata\MetaDataStorageHandler;
14
use SimpleSAML\Module\saml\Message as MSG;
15
use SimpleSAML\SAML2\Assert\Assert;
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
     * @var \SimpleSAML\Configuration
52
     */
53
    private Configuration $spMetadata;
54
55
56
    /**
57
     * Create the redirect URL for a message.
58
     *
59
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message.
60
     * @return string The URL the user should be redirected to in order to send a message.
61
     *
62
     * @throws \Exception
63
     */
64
    public function getRedirectURL(AbstractMessage $message): string
65
    {
66
        $config = Configuration::getInstance();
67
68
        $store = StoreFactory::getInstance($config->getString('store.type'));
69
        if ($store === false) {
70
            throw new Exception('Unable to send artifact without a datastore configured.');
71
        }
72
73
        $generatedId = pack('H*', bin2hex(openssl_random_pseudo_bytes(20)));
74
        $issuer = $message->getIssuer();
75
        if ($issuer === null) {
76
            throw new Exception('Cannot get redirect URL, no Issuer set in the message.');
77
        }
78
        $artifact = base64_encode("\x00\x04\x00\x00" . sha1($issuer->getContent(), true) . $generatedId);
79
        $artifactData = $message->toXML();
80
        $artifactDataString = $artifactData->ownerDocument?->saveXML($artifactData);
81
82
        $clock = Utils::getContainer()->getClock();
83
        $store->set('artifact', $artifact, $artifactDataString, $clock->now()->add(new DateInterval('PT15M')));
84
85
        $destination = $message->getDestination();
86
        if ($destination === null) {
87
            throw new Exception('Cannot get redirect URL, no destination set in the message.');
88
        }
89
90
        $params = ['SAMLart' => $artifact];
91
92
        $relayState = $this->getRelayState();
93
        if ($relayState !== null) {
94
            $params['RelayState'] = $relayState;
95
        }
96
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
        $metadataHandler = MetaDataStorageHandler::getMetadataHandler(Configuration::getInstance());
138
139
        $idpMetadata = $metadataHandler->getMetaDataConfigForSha1($sourceId, 'saml20-idp-remote');
140
141
        if ($idpMetadata === null) {
142
            throw new Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($sourceId, true));
143
        }
144
145
        $endpoint = null;
146
        foreach ($idpMetadata->getEndpoints('ArtifactResolutionService') as $ep) {
147
            if ($ep['index'] === hexdec($endpointIndex)) {
148
                $endpoint = $ep;
149
                break;
150
            }
151
        }
152
153
        if ($endpoint === null) {
154
            throw new Exception('No ArtifactResolutionService with the correct index.');
155
        }
156
157
        Utils::getContainer()->getLogger()->debug(
158
            "ArtifactResolutionService endpoint being used is := " . $endpoint['Location'],
159
        );
160
161
        Assert::notEmpty($this->spMetadata, 'Cannot process received message without SP metadata.');
162
163
        /**
164
         * Set the request attributes
165
         */
166
        $issuer = new Issuer($this->spMetadata->getString('entityid'));
167
168
        // Construct the ArtifactResolve Request
169
        $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 SimpleSAML\SAML2\XML\samlp\Artifact expected by parameter $artifact 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

169
        $ar = new ArtifactResolve(new Artifact($artifact), /** @scrutinizer ignore-type */ null, $issuer, null, '2.0', $endpoint['Location']);
Loading history...
Bug introduced by
'2.0' of type string is incompatible with the type SimpleSAML\SAML2\Type\SAMLAnyURIValue|null expected by parameter $destination 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

169
        $ar = new ArtifactResolve(new Artifact($artifact), null, $issuer, null, /** @scrutinizer ignore-type */ '2.0', $endpoint['Location']);
Loading history...
170
171
        // sign the request
172
        MSG::addSign($this->spMetadata, $idpMetadata, $ar); // Shoaib - moved from the SOAPClient.
173
174
        $soap = new SOAPClient();
175
176
        // Send message through SoapClient
177
        /** @var \SimpleSAML\SAML2\XML\samlp\ArtifactResponse $artifactResponse */
178
        $artifactResponse = $soap->send($ar, $this->spMetadata, $idpMetadata);
179
180
        if (!$artifactResponse->isSuccess()) {
181
            throw new Exception('Received error from ArtifactResolutionService.');
182
        }
183
184
        $samlResponse = $artifactResponse->getMessage();
185
        if ($samlResponse === null) {
186
            /* Empty ArtifactResponse - possibly because of Artifact replay? */
187
188
            throw new Exception('Empty ArtifactResponse received, maybe a replay?');
189
        }
190
191
        $samlResponse->addValidator([get_class($this), 'validateSignature'], $artifactResponse);
192
193
        $query = $request->getQueryParams();
194
        if (isset($query['RelayState'])) {
195
            $this->setRelayState($query['RelayState']);
196
        }
197
198
        return $samlResponse;
199
    }
200
201
202
    /**
203
     * @param \SimpleSAML\Configuration $sp
204
     */
205
    public function setSPMetadata(Configuration $sp): void
206
    {
207
        $this->spMetadata = $sp;
208
    }
209
210
211
    /**
212
     * A validator which returns true if the ArtifactResponse was signed with the given key
213
     *
214
     * @param \SimpleSAML\SAML2\XML\samlp\ArtifactResponse $message
215
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key
216
     */
217
    public static function validateSignature(ArtifactResponse $message, XMLSecurityKey $key): bool
218
    {
219
        // @todo verify if this works and/or needs to do anything more. Ref. HTTPRedirect binding
220
        return $message->validate($key);
221
    }
222
}
223