HTTPRedirect::receive()   C
last analyzed

Complexity

Conditions 13
Paths 25

Size

Total Lines 78
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 40
c 1
b 0
f 0
nc 25
nop 1
dl 0
loc 78
rs 6.6166

How to fix   Long Method    Complexity   

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 Exception;
8
use Nyholm\Psr7\Response;
9
use Psr\Http\Message\ResponseInterface;
10
use Psr\Http\Message\ServerRequestInterface;
11
use SimpleSAML\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;
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;
22
use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory;
23
use SimpleSAML\XMLSecurity\Exception\SignatureVerificationFailedException;
24
use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock;
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
            }
59
        } else {
60
            $destination = $this->destination;
61
        }
62
63
        $relayState = $this->getRelayState();
64
        $msgStr = $message->toXML();
65
66
        Utils::getContainer()->debugMessage($msgStr, 'out');
67
        $msgStr = $msgStr->ownerDocument?->saveXML($msgStr);
68
69
        $msgStr = gzdeflate($msgStr);
70
        $msgStr = base64_encode($msgStr);
71
72
        /* Build the query string. */
73
74
        if ($message instanceof AbstractRequest) {
75
            $msg = 'SAMLRequest=';
76
        } else {
77
            $msg = 'SAMLResponse=';
78
        }
79
        $msg .= urlencode($msgStr);
80
81
        if ($relayState !== null) {
82
            $msg .= '&RelayState=' . urlencode($relayState);
83
        }
84
85
        $signature = $message->getSignature();
86
        if ($signature !== null) { // add the signature
87
            $msg .= '&SigAlg=' . urlencode($signature->getSignedInfo()->getSignatureMethod()->getAlgorithm());
88
            $msg .= '&Signature=' . urlencode($signature->getSignatureValue()->getContent());
89
        }
90
91
        if (str_contains($destination, '?')) {
92
            $destination .= '&' . $msg;
93
        } else {
94
            $destination .= '?' . $msg;
95
        }
96
97
        return $destination;
98
    }
99
100
101
    /**
102
     * Send a SAML 2 message using the HTTP-Redirect binding.
103
     *
104
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message
105
     * @return \Psr\Http\Message\ResponseInterface
106
     */
107
    public function send(AbstractMessage $message): ResponseInterface
108
    {
109
        $destination = $this->getRedirectURL($message);
110
        Utils::getContainer()->getLogger()->debug(
111
            'Redirect to ' . strlen($destination) . ' byte URL: ' . $destination,
112
        );
113
        return new Response(303, ['Location' => $destination]);
114
    }
115
116
117
    /**
118
     * Receive a SAML 2 message sent using the HTTP-Redirect binding.
119
     *
120
     * Throws an exception if it is unable receive the message.
121
     *
122
     * @param \Psr\Http\Message\ServerRequestInterface $request
123
     * @return \SimpleSAML\SAML2\XML\samlp\AbstractMessage The received message.
124
     * @throws \Exception
125
     *
126
     * NPath is currently too high but solving that just moves code around.
127
     */
128
    public function receive(ServerRequestInterface $request): AbstractMessage
129
    {
130
        $query = $request->getQueryParams();
131
132
        /**
133
         * Put the SAMLRequest/SAMLResponse from the actual query string into $message,
134
         * and assert that the result from parseQuery() in $query and the parsing of the SignedQuery in $res agree
135
         */
136
        if (array_key_exists('SAMLRequest', $query)) {
137
            $message = $query['SAMLRequest'];
138
            $signedQuery = 'SAMLRequest=' . urlencode($query['SAMLRequest']);
139
        } elseif (array_key_exists('SAMLResponse', $query)) {
140
            $message = $query['SAMLResponse'];
141
            $signedQuery = 'SAMLResponse=' . urlencode($query['SAMLResponse']);
142
        } else {
143
            throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
144
        }
145
146
        if (array_key_exists('SAMLRequest', $query) && array_key_exists('SAMLResponse', $query)) {
147
            throw new Exception('Both SAMLRequest and SAMLResponse provided.');
148
        }
149
150
        if (isset($query['SAMLEncoding']) && $query['SAMLEncoding'] !== C::BINDING_HTTP_REDIRECT_DEFLATE) {
151
            throw new Exception(sprintf('Unknown SAMLEncoding: %s', $query['SAMLEncoding']));
152
        }
153
154
        $message = base64_decode($message, true);
155
        if ($message === false) {
156
            throw new Exception('Error while base64 decoding SAML message.');
157
        }
158
159
        $message = gzinflate($message);
160
        if ($message === false) {
161
            throw new Exception('Error while inflating SAML message.');
162
        }
163
164
        $document = DOMDocumentFactory::fromString($message);
165
        Utils::getContainer()->debugMessage($document->documentElement, 'in');
166
        $message = MessageFactory::fromXML($document->documentElement);
167
168
        if (array_key_exists('RelayState', $query)) {
169
            $this->setRelayState($query['RelayState']);
170
            $signedQuery .= '&RelayState=' . urlencode($query['RelayState']);
171
        }
172
173
        if (!array_key_exists('Signature', $query)) {
174
            return $message;
175
        }
176
177
        /**
178
         * 3.4.5.2 - SAML Bindings
179
         *
180
         * If the message is signed, the Destination XML attribute in the root SAML element of the protocol
181
         * message MUST contain the URL to which the sender has instructed the user agent to deliver the
182
         * message.
183
         */
184
        Assert::notNull($message->getDestination(), ProtocolViolationException::class);
185
        // Validation of the Destination must be done upstream
186
187
        if (!array_key_exists('SigAlg', $query)) {
188
            throw new Exception('Missing signature algorithm.');
189
        } else {
190
            $signedQuery .= '&SigAlg=' . urlencode($query['SigAlg']);
191
        }
192
193
        $container = ContainerSingleton::getInstance();
194
        $blacklist = $container->getBlacklistedEncryptionAlgorithms();
195
        $verifier = (new SignatureAlgorithmFactory($blacklist))->getAlgorithm(
0 ignored issues
show
Bug introduced by
It seems like $blacklist can also be of type null; however, parameter $blacklist of SimpleSAML\XMLSecurity\A...mFactory::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

195
        $verifier = (new SignatureAlgorithmFactory(/** @scrutinizer ignore-type */ $blacklist))->getAlgorithm(
Loading history...
196
            $query['SigAlg'],
197
            // TODO:  Need to use the key from the metadata
198
            PEMCertificatesMock::getPublicKey(PEMCertificatesMock::SELFSIGNED_PUBLIC_KEY),
199
        );
200
201
        if ($verifier->verify($signedQuery, base64_decode($query['Signature'])) === false) {
202
            throw new SignatureVerificationFailedException('Failed to verify signature.');
203
        }
204
205
        return $message;
206
    }
207
}
208