HttpRedirectBinding   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 40
eloc 101
c 1
b 0
f 0
dl 0
loc 261
rs 9.2

16 Methods

Rating   Name   Duplication   Size   Complexity  
A addMessageToUrl() 0 10 2
A loadSignature() 0 8 3
A processData() 0 15 1
A addSignatureToUrl() 0 9 3
A receive() 0 5 1
A addRelayStateToUrl() 0 4 2
A send() 0 7 2
A getDestinationUrl() 0 10 3
A getRedirectURL() 0 14 3
A getMessageStringFromData() 0 8 3
A decodeMessageString() 0 9 2
A loadRelayState() 0 4 2
A parseQueryString() 0 12 4
A getMessageEncodedXml() 0 14 1
A getEncodingFromData() 0 6 2
A parseQuery() 0 28 6

How to fix   Complexity   

Complex Class

Complex classes like HttpRedirectBinding often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HttpRedirectBinding, and based on these observations, apply Extract Interface, too.

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\SignatureStringReader;
20
use LightSaml\Model\XmlDSig\SignatureWriter;
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 string|null $destination
30
     *
31
     * @return \Symfony\Component\HttpFoundation\Response
32
     */
33
    public function send(MessageContext $context, $destination = null)
34
    {
35
        $destination = $context->getMessage()->getDestination() ? $context->getMessage()->getDestination() : $destination;
36
37
        $url = $this->getRedirectURL($context, $destination);
38
39
        return new RedirectResponse($url);
40
    }
41
42
    public function receive(Request $request, MessageContext $context)
43
    {
44
        $data = $this->parseQuery($request);
45
46
        $this->processData($data, $context);
47
    }
48
49
    /**
50
     * @throws \Exception
51
     */
52
    protected function processData(array $data, MessageContext $context)
53
    {
54
        $msg = $this->getMessageStringFromData($data);
55
        $encoding = $this->getEncodingFromData($data);
56
        $msg = $this->decodeMessageString($msg, $encoding);
57
58
        $this->dispatchReceive($msg);
59
60
        $deserializationContext = $context->getDeserializationContext();
61
        $message = SamlMessage::fromXML($msg, $deserializationContext);
62
63
        $this->loadRelayState($message, $data);
64
        $this->loadSignature($message, $data);
65
66
        $context->setMessage($message);
67
    }
68
69
    /**
70
     * @return string
71
     *
72
     * @throws LightSamlBindingException
73
     */
74
    protected function getMessageStringFromData(array $data)
75
    {
76
        if (array_key_exists('SAMLRequest', $data)) {
77
            return $data['SAMLRequest'];
78
        } elseif (array_key_exists('SAMLResponse', $data)) {
79
            return $data['SAMLResponse'];
80
        } else {
81
            throw new LightSamlBindingException('Missing SAMLRequest or SAMLResponse parameter');
82
        }
83
    }
84
85
    /**
86
     * @return string
87
     */
88
    protected function getEncodingFromData(array $data)
89
    {
90
        if (array_key_exists('SAMLEncoding', $data)) {
91
            return $data['SAMLEncoding'];
92
        } else {
93
            return SamlConstants::ENCODING_DEFLATE;
94
        }
95
    }
96
97
    /**
98
     * @param string $msg
99
     * @param string $encoding
100
     *
101
     * @throws \LightSaml\Error\LightSamlBindingException
102
     *
103
     * @return string
104
     */
105
    protected function decodeMessageString($msg, $encoding)
106
    {
107
        $msg = base64_decode($msg);
108
        switch ($encoding) {
109
            case SamlConstants::ENCODING_DEFLATE:
110
                return gzinflate($msg);
111
                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...
112
            default:
113
                throw new LightSamlBindingException(sprintf("Unknown encoding '%s'", $encoding));
114
        }
115
    }
116
117
    protected function loadRelayState(SamlMessage $message, array $data)
118
    {
119
        if (array_key_exists('RelayState', $data)) {
120
            $message->setRelayState($data['RelayState']);
121
        }
122
    }
123
124
    protected function loadSignature(SamlMessage $message, array $data)
125
    {
126
        if (array_key_exists('Signature', $data)) {
127
            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...
128
                throw new LightSamlBindingException('Missing signature algorithm');
129
            }
130
            $message->setSignature(
131
                new SignatureStringReader($data['Signature'], $data['SigAlg'], $data['SignedQuery'])
132
            );
133
        }
134
    }
135
136
    /**
137
     * @param string|null $destination
138
     *
139
     * @return string
140
     */
141
    protected function getRedirectURL(MessageContext $context, $destination)
142
    {
143
        $message = MessageContextHelper::asSamlMessage($context);
144
        $signature = $message->getSignature();
145
        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...
146
            throw new LightSamlBindingException('Signature must be SignatureWriter');
147
        }
148
149
        $xml = $this->getMessageEncodedXml($message, $context);
150
        $msg = $this->addMessageToUrl($message, $xml);
151
        $this->addRelayStateToUrl($msg, $message);
152
        $this->addSignatureToUrl($msg, $signature);
153
154
        return $this->getDestinationUrl($msg, $message, $destination);
155
    }
156
157
    /**
158
     * @return string
159
     */
160
    protected function getMessageEncodedXml(SamlMessage $message, MessageContext $context)
161
    {
162
        $message->setSignature(null);
163
164
        $serializationContext = $context->getSerializationContext();
165
        $message->serialize($serializationContext->getDocument(), $serializationContext);
166
        $xml = $serializationContext->getDocument()->saveXML();
167
168
        $this->dispatchSend($xml);
169
170
        $xml = gzdeflate($xml);
171
        $xml = base64_encode($xml);
172
173
        return $xml;
174
    }
175
176
    /**
177
     * @param string $xml
178
     *
179
     * @return string
180
     */
181
    protected function addMessageToUrl(SamlMessage $message, $xml)
182
    {
183
        if ($message instanceof AbstractRequest) {
184
            $msg = 'SAMLRequest=';
185
        } else {
186
            $msg = 'SAMLResponse=';
187
        }
188
        $msg .= urlencode($xml);
189
190
        return $msg;
191
    }
192
193
    /**
194
     * @param string $msg
195
     */
196
    protected function addRelayStateToUrl(&$msg, SamlMessage $message)
197
    {
198
        if (null !== $message->getRelayState()) {
199
            $msg .= '&RelayState='.urlencode($message->getRelayState());
200
        }
201
    }
202
203
    /**
204
     * @param string $msg
205
     */
206
    protected function addSignatureToUrl(&$msg, SignatureWriter $signature = null)
207
    {
208
        /** @var $key XMLSecurityKey */
209
        $key = $signature ? $signature->getXmlSecurityKey() : null;
210
211
        if (null != $key) {
212
            $msg .= '&SigAlg='.urlencode($key->type);
213
            $signature = $key->signData($msg);
214
            $msg .= '&Signature='.urlencode(base64_encode($signature));
0 ignored issues
show
Bug introduced by
It seems like $signature can also be of type null; however, parameter $string of base64_encode() does only seem to accept string, 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

214
            $msg .= '&Signature='.urlencode(base64_encode(/** @scrutinizer ignore-type */ $signature));
Loading history...
215
        }
216
    }
217
218
    /**
219
     * @param string      $msg
220
     * @param string|null $destination
221
     *
222
     * @return string
223
     */
224
    protected function getDestinationUrl($msg, SamlMessage $message, $destination)
225
    {
226
        $destination = $message->getDestination() ? $message->getDestination() : $destination;
227
        if (false === strpos($destination, '?')) {
0 ignored issues
show
Bug introduced by
It seems like $destination can also be of type null; however, parameter $haystack of strpos() does only seem to accept string, 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

227
        if (false === strpos(/** @scrutinizer ignore-type */ $destination, '?')) {
Loading history...
228
            $destination .= '?'.$msg;
229
        } else {
230
            $destination .= '&'.$msg;
231
        }
232
233
        return $destination;
234
    }
235
236
    /**
237
     * @return array
238
     */
239
    protected function parseQuery(Request $request)
240
    {
241
        /*
242
         * Parse the query string. We need to do this ourself, so that we get access
243
         * to the raw (urlencoded) values. This is required because different software
244
         * can urlencode to different values.
245
         */
246
        $sigQuery = $relayState = $sigAlg = '';
247
        $data = $this->parseQueryString($request->server->get('QUERY_STRING'), false);
248
        $result = [];
249
        foreach ($data as $name => $value) {
250
            $result[$name] = urldecode($value);
251
            switch ($name) {
252
                case 'SAMLRequest':
253
                case 'SAMLResponse':
254
                    $sigQuery = $name.'='.$value;
255
                    break;
256
                case 'RelayState':
257
                    $relayState = '&RelayState='.$value;
258
                    break;
259
                case 'SigAlg':
260
                    $sigAlg = '&SigAlg='.$value;
261
                    break;
262
            }
263
        }
264
        $result['SignedQuery'] = $sigQuery.$relayState.$sigAlg;
265
266
        return $result;
267
    }
268
269
    /**
270
     * @param string $queryString
271
     * @param bool   $urlDecodeValues
272
     *
273
     * @return array
274
     */
275
    protected function parseQueryString($queryString, $urlDecodeValues = false)
276
    {
277
        $result = [];
278
        foreach (explode('&', $queryString) as $e) {
279
            $tmp = explode('=', $e, 2);
280
            $name = $tmp[0];
281
            $value = 2 === count($tmp) ? $value = $tmp[1] : '';
0 ignored issues
show
Unused Code introduced by
The assignment to $value is dead and can be removed.
Loading history...
282
            $name = urldecode($name);
283
            $result[$name] = $urlDecodeValues ? urldecode($value) : $value;
284
        }
285
286
        return $result;
287
    }
288
}
289