HttpRedirectBinding::parseQuery()   B
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 29
c 0
b 0
f 0
ccs 19
cts 19
cp 1
rs 8.8337
cc 6
nc 6
nop 1
crap 6
1
<?php
2
3
/*
4
 * This file is part of the LightSAML-Core package.
5
 *
6
 * (c) Milos Tomic <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace LightSaml\Binding;
13
14
use LightSaml\Context\Profile\Helper\MessageContextHelper;
15
use LightSaml\Context\Profile\MessageContext;
16
use LightSaml\Error\LightSamlBindingException;
17
use LightSaml\Model\Protocol\AbstractRequest;
18
use LightSaml\Model\Protocol\SamlMessage;
19
use LightSaml\Model\XmlDSig\SignatureWriter;
20
use LightSaml\Model\XmlDSig\SignatureStringReader;
21
use LightSaml\SamlConstants;
22
use RobRichards\XMLSecLibs\XMLSecurityKey;
23
use Symfony\Component\HttpFoundation\RedirectResponse;
24
use Symfony\Component\HttpFoundation\Request;
25
26
class HttpRedirectBinding extends AbstractBinding
27
{
28
    /**
29
     * @param MessageContext $context
30
     * @param null|string    $destination
31
     *
32
     * @return \Symfony\Component\HttpFoundation\Response
33
     */
34 2
    public function send(MessageContext $context, $destination = null)
35
    {
36 2
        $destination = $context->getMessage()->getDestination() ? $context->getMessage()->getDestination() : $destination;
37
38 2
        $url = $this->getRedirectURL($context, $destination);
39
40 2
        return new RedirectResponse($url);
41
    }
42
43
    /**
44
     * @param Request        $request
45
     * @param MessageContext $context
46
     */
47 2
    public function receive(Request $request, MessageContext $context)
48
    {
49 2
        $data = $this->parseQuery($request);
50
51 2
        $this->processData($data, $context);
52 1
    }
53
54
    /**
55
     * @param array          $data
56
     * @param MessageContext $context
57
     *
58
     * @throws \Exception
59
     */
60 2
    protected function processData(array $data, MessageContext $context)
61
    {
62 2
        $msg = $this->getMessageStringFromData($data);
63 1
        $encoding = $this->getEncodingFromData($data);
64 1
        $msg = $this->decodeMessageString($msg, $encoding);
65
66 1
        $this->dispatchReceive($msg);
67
68 1
        $deserializationContext = $context->getDeserializationContext();
69 1
        $message = SamlMessage::fromXML($msg, $deserializationContext);
0 ignored issues
show
Documentation introduced by
$deserializationContext is of type object<LightSaml\Context\ContextInterface>|null, but the function expects a object<LightSaml\Model\C...DeserializationContext>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
70
71 1
        $this->loadRelayState($message, $data);
72 1
        $this->loadSignature($message, $data);
73
74 1
        $context->setMessage($message);
75 1
    }
76
77
    /**
78
     * @param array $data
79
     *
80
     * @return string
81
     *
82
     * @throws LightSamlBindingException
83
     */
84 2
    protected function getMessageStringFromData(array $data)
85
    {
86 2
        if (array_key_exists('SAMLRequest', $data)) {
87 1
            return $data['SAMLRequest'];
88 1
        } elseif (array_key_exists('SAMLResponse', $data)) {
89
            return $data['SAMLResponse'];
90
        } else {
91 1
            throw new LightSamlBindingException('Missing SAMLRequest or SAMLResponse parameter');
92
        }
93
    }
94
95
    /**
96
     * @param array $data
97
     *
98
     * @return string
99
     */
100 1
    protected function getEncodingFromData(array $data)
101
    {
102 1
        if (array_key_exists('SAMLEncoding', $data)) {
103
            return $data['SAMLEncoding'];
104
        } else {
105 1
            return SamlConstants::ENCODING_DEFLATE;
106
        }
107
    }
108
109
    /**
110
     * @param string $msg
111
     * @param string $encoding
112
     *
113
     * @throws \LightSaml\Error\LightSamlBindingException
114
     *
115
     * @return string
116
     */
117 1
    protected function decodeMessageString($msg, $encoding)
118
    {
119 1
        $msg = base64_decode($msg);
120 1
        switch ($encoding) {
121
            case SamlConstants::ENCODING_DEFLATE:
122 1
                return gzinflate($msg);
123
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
124
            default:
125
                throw new LightSamlBindingException(sprintf("Unknown encoding '%s'", $encoding));
126
        }
127
    }
128
129 1
    protected function loadRelayState(SamlMessage $message, array $data)
130
    {
131 1
        if (array_key_exists('RelayState', $data)) {
132 1
            $message->setRelayState($data['RelayState']);
133
        }
134 1
    }
135
136 1
    protected function loadSignature(SamlMessage $message, array $data)
137
    {
138 1
        if (array_key_exists('Signature', $data)) {
139 1
            if (false == array_key_exists('SigAlg', $data)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
140
                throw new LightSamlBindingException('Missing signature algorithm');
141
            }
142 1
            $message->setSignature(
143 1
                new SignatureStringReader($data['Signature'], $data['SigAlg'], $data['SignedQuery'])
144
            );
145
        }
146 1
    }
147
148
    /**
149
     * @param MessageContext $context
150
     * @param string|null    $destination
151
     *
152
     * @return string
153
     */
154 2
    protected function getRedirectURL(MessageContext $context, $destination)
155
    {
156 2
        $message = MessageContextHelper::asSamlMessage($context);
157 2
        $signature = $message->getSignature();
158 2
        if ($signature && false == $signature instanceof SignatureWriter) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
159
            throw new LightSamlBindingException('Signature must be SignatureWriter');
160
        }
161
162 2
        $xml = $this->getMessageEncodedXml($message, $context);
163 2
        $msg = $this->addMessageToUrl($message, $xml);
164 2
        $this->addRelayStateToUrl($msg, $message);
165 2
        $this->addSignatureToUrl($msg, $signature);
0 ignored issues
show
Bug introduced by
It seems like $signature defined by $message->getSignature() on line 157 can also be of type object<LightSaml\Model\XmlDSig\Signature>; however, LightSaml\Binding\HttpRe...ng::addSignatureToUrl() does only seem to accept null|object<LightSaml\Mo...mlDSig\SignatureWriter>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
166
167 2
        return $this->getDestinationUrl($msg, $message, $destination);
168
    }
169
170
    /**
171
     * @param SamlMessage    $message
172
     * @param MessageContext $context
173
     *
174
     * @return string
175
     */
176 2
    protected function getMessageEncodedXml(SamlMessage $message, MessageContext $context)
177
    {
178 2
        $message->setSignature(null);
179
180 2
        $serializationContext = $context->getSerializationContext();
181 2
        $message->serialize($serializationContext->getDocument(), $serializationContext);
0 ignored issues
show
Bug introduced by
The method getDocument() does not seem to exist on object<LightSaml\Context\ContextInterface>.

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...
Documentation introduced by
$serializationContext is of type object<LightSaml\Context\ContextInterface>|null, but the function expects a object<LightSaml\Model\C...t\SerializationContext>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
182 2
        $xml = $serializationContext->getDocument()->saveXML();
0 ignored issues
show
Bug introduced by
The method getDocument() does not seem to exist on object<LightSaml\Context\ContextInterface>.

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...
183
184 2
        $this->dispatchSend($xml);
185
186 2
        $xml = gzdeflate($xml);
187 2
        $xml = base64_encode($xml);
188
189 2
        return $xml;
190
    }
191
192
    /**
193
     * @param SamlMessage $message
194
     * @param string      $xml
195
     *
196
     * @return string
197
     */
198 2
    protected function addMessageToUrl(SamlMessage $message, $xml)
199
    {
200 2
        if ($message instanceof AbstractRequest) {
201 2
            $msg = 'SAMLRequest=';
202
        } else {
203
            $msg = 'SAMLResponse=';
204
        }
205 2
        $msg .= urlencode($xml);
206
207 2
        return $msg;
208
    }
209
210
    /**
211
     * @param string      $msg
212
     * @param SamlMessage $message
213
     */
214 2
    protected function addRelayStateToUrl(&$msg, SamlMessage $message)
215
    {
216 2
        if (null !== $message->getRelayState()) {
217 1
            $msg .= '&RelayState='.urlencode($message->getRelayState());
218
        }
219 2
    }
220
221
    /**
222
     * @param string               $msg
223
     * @param SignatureWriter|null $signature
224
     */
225 2
    protected function addSignatureToUrl(&$msg, SignatureWriter $signature = null)
226
    {
227
        /** @var $key XMLSecurityKey */
228 2
        $key = $signature ? $signature->getXmlSecurityKey() : null;
229
230 2
        if (null != $key) {
231 2
            $msg .= '&SigAlg='.urlencode($key->type);
232 2
            $signature = $key->signData($msg);
233 2
            $msg .= '&Signature='.urlencode(base64_encode($signature));
234
        }
235 2
    }
236
237
    /**
238
     * @param string      $msg
239
     * @param SamlMessage $message
240
     * @param string|null $destination
241
     *
242
     * @return string
243
     */
244 2
    protected function getDestinationUrl($msg, SamlMessage $message, $destination)
245
    {
246 2
        $destination = $message->getDestination() ? $message->getDestination() : $destination;
247 2
        if (false === strpos($destination, '?')) {
248 2
            $destination .= '?'.$msg;
249
        } else {
250
            $destination .= '&'.$msg;
251
        }
252
253 2
        return $destination;
254
    }
255
256
    /**
257
     * @param Request $request
258
     *
259
     * @return array
260
     */
261 2
    protected function parseQuery(Request $request)
262
    {
263
        /*
264
         * Parse the query string. We need to do this ourself, so that we get access
265
         * to the raw (urlencoded) values. This is required because different software
266
         * can urlencode to different values.
267
         */
268 2
        $sigQuery = $relayState = $sigAlg = '';
269 2
        $data = $this->parseQueryString($request->server->get('QUERY_STRING'), false);
270 2
        $result = array();
271 2
        foreach ($data as $name => $value) {
272 2
            $result[$name] = urldecode($value);
273 2
            switch ($name) {
274 2
                case 'SAMLRequest':
275 2
                case 'SAMLResponse':
276 1
                    $sigQuery = $name.'='.$value;
277 1
                    break;
278 2
                case 'RelayState':
279 1
                    $relayState = '&RelayState='.$value;
280 1
                    break;
281 2
                case 'SigAlg':
282 1
                    $sigAlg = '&SigAlg='.$value;
283 2
                    break;
284
            }
285
        }
286 2
        $result['SignedQuery'] = $sigQuery.$relayState.$sigAlg;
287
288 2
        return $result;
289
    }
290
291
    /**
292
     * @param string $queryString
293
     * @param bool   $urlDecodeValues
294
     *
295
     * @return array
296
     */
297 2
    protected function parseQueryString($queryString, $urlDecodeValues = false)
298
    {
299 2
        $result = array();
300 2
        foreach (explode('&', $queryString) as $e) {
301 2
            $tmp = explode('=', $e, 2);
302 2
            $name = $tmp[0];
303 2
            $value = 2 === count($tmp) ? $value = $tmp[1] : '';
304 2
            $name = urldecode($name);
305 2
            $result[$name] = $urlDecodeValues ? urldecode($value) : $value;
306
        }
307
308 2
        return $result;
309
    }
310
}
311