Passed
Pull Request — master (#268)
by Tim
02:17
created

HTTPRedirect::parseQuery()   B

Complexity

Conditions 7
Paths 11

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 26
nc 11
nop 0
dl 0
loc 40
rs 8.5706
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A HTTPRedirect::validateSignature() 0 19 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\SAML2;
6
7
use DOMElement;
8
use Exception;
9
use Psr\Http\Message\ServerRequestInterface;
10
use SimpleSAML\Assert\Assert;
11
use SimpleSAML\SAML2\XML\samlp\AbstractMessage;
12
use SimpleSAML\SAML2\XML\samlp\AbstractRequest;
13
use SimpleSAML\SAML2\XML\samlp\MessageFactory;
14
use SimpleSAML\XML\DOMDocumentFactory;
15
use SimpleSAML\XMLSecurity\Utils\Security;
16
use SimpleSAML\XMLSecurity\XMLSecurityKey;
17
18
use function array_key_exists;
19
use function base64_decode;
20
use function base64_encode;
21
use function count;
22
use function explode;
23
use function get_class;
24
use function gzdeflate;
25
use function gzinflate;
26
use function strlen;
27
use function strpos;
28
use function urlencode;
29
use function urldecode;
30
use function var_export;
31
32
/**
33
 * Class which implements the HTTP-Redirect binding.
34
 *
35
 * @package simplesamlphp/saml2
36
 */
37
class HTTPRedirect extends Binding
38
{
39
    public const DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE';
40
41
    /**
42
     * Create the redirect URL for a message.
43
     *
44
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message.
45
     * @return string The URL the user should be redirected to in order to send a message.
46
     */
47
    public function getRedirectURL(AbstractMessage $message): string
48
    {
49
        if ($this->destination === null) {
50
            $destination = $message->getDestination();
51
            if ($destination === null) {
52
                throw new Exception('Cannot build a redirect URL, no destination set.');
53
            }
54
        } else {
55
            $destination = $this->destination;
56
        }
57
58
        $relayState = $message->getRelayState();
59
60
        $key = $message->getSigningKey();
0 ignored issues
show
Bug introduced by
The method getSigningKey() 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

60
        /** @scrutinizer ignore-call */ 
61
        $key = $message->getSigningKey();

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...
61
62
        $msgStr = $message->toXML();
63
64
        Utils::getContainer()->debugMessage($msgStr, 'out');
65
        $msgStr = $msgStr->ownerDocument->saveXML($msgStr);
0 ignored issues
show
Bug introduced by
The method saveXML() does not exist on null. ( Ignorable by Annotation )

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

65
        /** @scrutinizer ignore-call */ 
66
        $msgStr = $msgStr->ownerDocument->saveXML($msgStr);

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...
66
67
        $msgStr = gzdeflate($msgStr);
68
        $msgStr = base64_encode($msgStr);
69
70
        /* Build the query string. */
71
72
        if ($message instanceof AbstractRequest) {
73
            $msg = 'SAMLRequest=';
74
        } else {
75
            $msg = 'SAMLResponse=';
76
        }
77
        $msg .= urlencode($msgStr);
78
79
        if ($relayState !== null) {
80
            $msg .= '&RelayState=' . urlencode($relayState);
81
        }
82
83
        if ($key !== null) { // add the signature
84
            /** @psalm-suppress PossiblyInvalidArgument */
85
            $msg .= '&SigAlg=' . urlencode($key->type);
86
87
            $signature = $key->signData($msg);
88
            $msg .= '&Signature=' . urlencode(base64_encode($signature));
89
        }
90
91
        if (strpos($destination, '?') === false) {
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
     * Note: This function never returns.
104
     *
105
     * @param \SimpleSAML\SAML2\XML\samlp\AbstractMessage $message The message we should send.
106
     */
107
    public function send(AbstractMessage $message): void
108
    {
109
        $destination = $this->getRedirectURL($message);
110
        Utils::getContainer()->getLogger()->debug('Redirect to ' . strlen($destination) . ' byte URL: ' . $destination);
111
        Utils::getContainer()->redirect($destination);
112
    }
113
114
115
    /**
116
     * Receive a SAML 2 message sent using the HTTP-Redirect 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
     * @throws \Exception
123
     *
124
     * NPath is currently too high but solving that just moves code around.
125
     */
126
    public function receive(ServerRequestInterface $request): AbstractMessage
127
    {
128
        $query = $request->getQueryParams();
129
        if (array_key_exists('SAMLRequest', $query)) {
130
            $message = $query['SAMLRequest'];
131
            $signedQuery = 'SAMLRequest=' . urlencode($query['SAMLRequest']);
132
        } elseif (array_key_exists('SAMLResponse', $query)) {
133
            $message = $query['SAMLResponse'];
134
            $signedQuery = 'SAMLResponse=' . urlencode($query['SAMLResponse']);
135
        } else {
136
            throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
137
        }
138
139
        if (isset($query['SAMLEncoding']) && $query['SAMLEncoding'] !== self::DEFLATE) {
140
            throw new Exception('Unknown SAMLEncoding: ' . var_export($query['SAMLEncoding'], true));
141
        }
142
143
        $message = base64_decode($message);
144
        if ($message === false) {
145
            throw new Exception('Error while base64 decoding SAML message.');
146
        }
147
148
        $message = gzinflate($message);
149
        if ($message === false) {
150
            throw new Exception('Error while inflating SAML message.');
151
        }
152
153
        $document = DOMDocumentFactory::fromString($message);
154
        Utils::getContainer()->debugMessage($document->documentElement, 'in');
155
        $message = MessageFactory::fromXML($document->documentElement);
156
157
        if (array_key_exists('RelayState', $query)) {
158
            $message->setRelayState($query['RelayState']);
159
            $signedQuery .= '&RelayState=' . urlencode($query['RelayState']);
160
        }
161
162
        if (!array_key_exists('Signature', $query)) {
163
            return $message;
164
        }
165
166
        if (!array_key_exists('SigAlg', $query)) {
167
            throw new Exception('Missing signature algorithm.');
168
        }
169
        $signedQuery .= '&SigAlg=' . urlencode($query['SigAlg']);
170
171
        $signData = [
172
            'Signature' => $query['Signature'],
173
            'SigAlg'    => $query['SigAlg'],
174
            'Query'     => $signedQuery,
175
        ];
176
177
        $message->addValidator([get_class($this), 'validateSignature'], $signData);
178
179
        return $message;
180
    }
181
182
183
    /**
184
     * Validate the signature on a HTTP-Redirect message.
185
     *
186
     * Throws an exception if we are unable to validate the signature.
187
     *
188
     * @param array $data The data we need to validate the query string.
189
     * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key  The key we should validate the query against.
190
     *
191
     * @throws \Exception
192
     * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false
193
     */
194
    public static function validateSignature(array $data, XMLSecurityKey $key): void
195
    {
196
        Assert::keyExists($data, "Query");
197
        Assert::keyExists($data, "SigAlg");
198
        Assert::keyExists($data, "Signature");
199
        Assert::same($key->type, XMLSecurityKey::RSA_SHA256, 'Invalid key type for validating signature on query string.');
200
201
        $query = $data['Query'];
202
        $sigAlg = $data['SigAlg'];
203
        $signature = $data['Signature'];
204
205
        $signature = base64_decode($signature);
206
207
        if ($key->type !== $sigAlg) {
208
            $key = Security::castKey($key, $sigAlg);
209
        }
210
211
        if ($key->verifySignature($query, $signature) !== 1) {
212
            throw new Exception('Unable to validate signature on query string.');
213
        }
214
    }
215
}
216