HTTPRedirect   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 171
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 77
dl 0
loc 171
rs 10
c 1
b 0
f 0
wmc 21

3 Methods

Rating   Name   Duplication   Size   Complexity  
B getRedirectURL() 0 51 7
A send() 0 7 1
C receive() 0 78 13
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2\Binding;
6
7
use Exception;
8
use Nyholm\Psr7\Response;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use SimpleSAML\SAML2\Assert\Assert;
12
use SimpleSAML\SAML2\Binding;
13
use SimpleSAML\SAML2\Binding\RelayStateTrait;
14
use SimpleSAML\SAML2\Compat\ContainerSingleton;
15
use SimpleSAML\SAML2\Constants as C;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\SAML2\Constants 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...
16
use SimpleSAML\SAML2\Exception\ProtocolViolationException;
17
use SimpleSAML\SAML2\Utils;
18
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
19
use SimpleSAML\SAML2\XML\samlp\AbstractRequest;
20
use SimpleSAML\SAML2\XML\samlp\MessageFactory;
21
use SimpleSAML\XML\DOMDocumentFactory;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XML\DOMDocumentFactory 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...
22
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSecurity\A...gnatureAlgorithmFactory 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...
23
use SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException;
24
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
0 ignored issues
show
Bug introduced by
The type SimpleSAML\XMLSecurity\T...ils\PEMCertificatesMock 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
26
use function array_key_exists;
27
use function base64_decode;
28
use function base64_encode;
29
use function gzdeflate;
30
use function gzinflate;
31
use function sprintf;
32
use function str_contains;
33
use function strlen;
34
use function urlencode;
35
36
/**
37
 * Class which implements the HTTP-Redirect binding.
38
 *
39
 * @package simplesamlphp/saml2
40
 */
41
class HTTPRedirect extends Binding implements AsynchronousBindingInterface, RelayStateInterface
42
{
43
    use RelayStateTrait;
44
45
46
    /**
47
     * Create the redirect URL for a message.
48
     *
49
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message.
50
     * @return string The URL the user should be redirected to in order to send a message.
51
     */
52
    public function getRedirectURL(AbstractMessage $message): string
53
    {
54
        if ($this->destination === null) {
55
            $destination = $message->getDestination();
56
            if ($destination === null) {
57
                throw new Exception('Cannot build a redirect URL, no destination set.');
58
            } else {
59
                $destination = $destination->getValue();
60
            }
61
        } else {
62
            $destination = $this->destination;
63
        }
64
65
        $relayState = $this->getRelayState();
66
        $msgStr = $message->toXML();
67
68
        Utils::getContainer()->debugMessage($msgStr, 'out');
69
        $msgStr = $msgStr->ownerDocument?->saveXML($msgStr);
70
71
        $msgStr = gzdeflate($msgStr);
72
        $msgStr = base64_encode($msgStr);
73
74
        /* Build the query string. */
75
76
        if ($message instanceof AbstractRequest) {
77
            $msg = 'SAMLRequest=';
78
        } else {
79
            $msg = 'SAMLResponse=';
80
        }
81
        $msg .= urlencode($msgStr);
82
83
        if ($relayState !== null) {
84
            $msg .= '&RelayState=' . urlencode($relayState);
85
        }
86
87
        $signature = $message->getSignature();
88
        if ($signature !== null) { // add the signature
89
            $signatureMethod = $signature->getSignedInfo()->getSignatureMethod();
90
            $signatureValue = $signature->getSignatureValue();
91
92
            $msg .= '&SigAlg=' . urlencode($signatureMethod->getAlgorithm()->getValue());
93
            $msg .= '&Signature=' . urlencode($signatureValue->getValue()->getValue());
94
        }
95
96
        if (str_contains($destination, '?')) {
97
            $destination .= '&' . $msg;
98
        } else {
99
            $destination .= '?' . $msg;
100
        }
101
102
        return $destination;
103
    }
104
105
106
    /**
107
     * Send a SAML 2 message using the HTTP-Redirect binding.
108
     *
109
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message
110
     * @return \Psr\Http\Message\ResponseInterface
111
     */
112
    public function send(AbstractMessage $message): ResponseInterface
113
    {
114
        $destination = $this->getRedirectURL($message);
115
        Utils::getContainer()->getLogger()->debug(
116
            'Redirect to ' . strlen($destination) . ' byte URL: ' . $destination,
117
        );
118
        return new Response(303, ['Location' => $destination]);
119
    }
120
121
122
    /**
123
     * Receive a SAML 2 message sent using the HTTP-Redirect binding.
124
     *
125
     * Throws an exception if it is unable receive the message.
126
     *
127
     * @param \Psr\Http\Message\ServerRequestInterface $request
128
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
129
     *
130
     * @throws \Exception
131
     *
132
     * NPath is currently too high but solving that just moves code around.
133
     */
134
    public function receive(ServerRequestInterface $request): AbstractMessage
135
    {
136
        $query = $request->getQueryParams();
137
138
        /**
139
         * Put the SAMLRequest/SAMLResponse from the actual query string into $message,
140
         * and assert that the result from parseQuery() in $query and the parsing of the SignedQuery in $res agree
141
         */
142
        if (array_key_exists('SAMLRequest', $query)) {
143
            $message = $query['SAMLRequest'];
144
            $signedQuery = 'SAMLRequest=' . urlencode($query['SAMLRequest']);
145
        } elseif (array_key_exists('SAMLResponse', $query)) {
146
            $message = $query['SAMLResponse'];
147
            $signedQuery = 'SAMLResponse=' . urlencode($query['SAMLResponse']);
148
        } else {
149
            throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
150
        }
151
152
        if (array_key_exists('SAMLRequest', $query) && array_key_exists('SAMLResponse', $query)) {
153
            throw new Exception('Both SAMLRequest and SAMLResponse provided.');
154
        }
155
156
        if (isset($query['SAMLEncoding']) && $query['SAMLEncoding'] !== C::BINDING_HTTP_REDIRECT_DEFLATE) {
157
            throw new Exception(sprintf('Unknown SAMLEncoding: %s', $query['SAMLEncoding']));
158
        }
159
160
        $message = base64_decode($message, true);
161
        if ($message === false) {
162
            throw new Exception('Error while base64 decoding SAML message.');
163
        }
164
165
        $message = gzinflate($message);
166
        if ($message === false) {
167
            throw new Exception('Error while inflating SAML message.');
168
        }
169
170
        $document = DOMDocumentFactory::fromString($message);
171
        Utils::getContainer()->debugMessage($document->documentElement, 'in');
172
        $message = MessageFactory::fromXML($document->documentElement);
173
174
        if (array_key_exists('RelayState', $query)) {
175
            $this->setRelayState($query['RelayState']);
176
            $signedQuery .= '&RelayState=' . urlencode($query['RelayState']);
177
        }
178
179
        if (!array_key_exists('Signature', $query)) {
180
            return $message;
181
        }
182
183
        /**
184
         * 3.4.5.2 - SAML Bindings
185
         *
186
         * If the message is signed, the Destination XML attribute in the root SAML element of the protocol
187
         * message MUST contain the URL to which the sender has instructed the user agent to deliver the
188
         * message.
189
         */
190
        Assert::notNull($message->getDestination(), ProtocolViolationException::class);
191
        // Validation of the Destination must be done upstream
192
193
        if (!array_key_exists('SigAlg', $query)) {
194
            throw new Exception('Missing signature algorithm.');
195
        } else {
196
            $signedQuery .= '&SigAlg=' . urlencode($query['SigAlg']);
197
        }
198
199
        $container = ContainerSingleton::getInstance();
200
        $blacklist = $container->getBlacklistedEncryptionAlgorithms();
201
        $verifier = (new SignatureAlgorithmFactory($blacklist))->getAlgorithm(
202
            $query['SigAlg'],
203
            // TODO:  Need to use the key from the metadata
204
            PEMCertificatesMock::getPublicKey(PEMCertificatesMock::SELFSIGNED_PUBLIC_KEY),
205
        );
206
207
        if ($verifier->verify($signedQuery, base64_decode($query['Signature'])) === false) {
208
            throw new SignatureVerificationFailedException('Failed to verify signature.');
209
        }
210
211
        return $message;
212
    }
213
}
214