Completed
Push — master ( d701a1...2bc8ba )
by Thijs
06:22
created

SAML2_HTTPRedirect::receive()   C

Complexity

Conditions 10
Paths 19

Size

Total Lines 52
Code Lines 32

Duplication

Lines 7
Ratio 13.46 %
Metric Value
dl 7
loc 52
rs 6.2553
cc 10
eloc 32
nc 19
nop 0

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
/**
4
 * Class which implements the HTTP-Redirect binding.
5
 *
6
 * @package SimpleSAMLphp
7
 */
8
class SAML2_HTTPRedirect extends SAML2_Binding
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
    const DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE';
11
12
    /**
13
     * Create the redirect URL for a message.
14
     *
15
     * @param  SAML2_Message $message The message.
16
     * @return string        The URL the user should be redirected to in order to send a message.
17
     */
18
    public function getRedirectURL(SAML2_Message $message)
19
    {
20
        if ($this->destination === NULL) {
21
            $destination = $message->getDestination();
22
        } else {
23
            $destination = $this->destination;
24
        }
25
26
        $relayState = $message->getRelayState();
27
28
        $key = $message->getSignatureKey();
29
30
        $msgStr = $message->toUnsignedXML();
31
        $msgStr = $msgStr->ownerDocument->saveXML($msgStr);
32
33
        SAML2_Utils::getContainer()->debugMessage($msgStr, 'out');
34
35
        $msgStr = gzdeflate($msgStr);
36
        $msgStr = base64_encode($msgStr);
37
38
        /* Build the query string. */
39
40
        if ($message instanceof SAML2_Request) {
41
            $msg = 'SAMLRequest=';
42
        } else {
43
            $msg = 'SAMLResponse=';
44
        }
45
        $msg .= urlencode($msgStr);
46
47
        if ($relayState !== NULL) {
48
            $msg .= '&RelayState=' . urlencode($relayState);
49
        }
50
51
        if ($key !== NULL) {
52
            /* Add the signature. */
53
            $msg .= '&SigAlg=' . urlencode($key->type);
54
55
            $signature = $key->signData($msg);
56
            $msg .= '&Signature=' . urlencode(base64_encode($signature));
57
        }
58
59
        if (strpos($destination, '?') === FALSE) {
60
            $destination .= '?' . $msg;
61
        } else {
62
            $destination .= '&' . $msg;
63
        }
64
65
        return $destination;
66
    }
67
68
    /**
69
     * Send a SAML 2 message using the HTTP-Redirect binding.
70
     *
71
     * Note: This function never returns.
72
     *
73
     * @param SAML2_Message $message The message we should send.
74
     */
75
    public function send(SAML2_Message $message)
76
    {
77
        $destination = $this->getRedirectURL($message);
78
        SAML2_Utils::getContainer()->getLogger()->debug('Redirect to ' . strlen($destination) . ' byte URL: ' . $destination);
79
        SAML2_Utils::getContainer()->redirect($destination);
80
    }
81
82
    /**
83
     * Receive a SAML 2 message sent using the HTTP-Redirect binding.
84
     *
85
     * Throws an exception if it is unable receive the message.
86
     *
87
     * @return SAML2_Message The received message.
88
     * @throws Exception
89
     *
90
     * NPath is currently too high but solving that just moves code around.
91
     * @SuppressWarnings(PHPMD.NPathComplexity)
92
     */
93
    public function receive()
94
    {
95
        $data = self::parseQuery();
96 View Code Duplication
        if (array_key_exists('SAMLRequest', $data)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
97
            $message = $data['SAMLRequest'];
98
        } elseif (array_key_exists('SAMLResponse', $data)) {
99
            $message = $data['SAMLResponse'];
100
        } else {
101
            throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
102
        }
103
104
        if (isset($data['SAMLEncoding']) && $data['SAMLEncoding'] !== self::DEFLATE) {
105
            throw new Exception('Unknown SAMLEncoding: ' . var_export($data['SAMLEncoding'], TRUE));
106
        }
107
108
        $message = base64_decode($message);
109
        if ($message === FALSE) {
110
            throw new Exception('Error while base64 decoding SAML message.');
111
        }
112
113
        $message = gzinflate($message);
114
        if ($message === FALSE) {
115
            throw new Exception('Error while inflating SAML message.');
116
        }
117
118
        SAML2_Utils::getContainer()->debugMessage($message, 'in');
119
        $document = SAML2_DOMDocumentFactory::fromString($message);
120
        $xml      = $document->firstChild;
121
        $message  = SAML2_Message::fromXML($xml);
0 ignored issues
show
Compatibility introduced by
$xml of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
122
123
        if (array_key_exists('RelayState', $data)) {
124
            $message->setRelayState($data['RelayState']);
125
        }
126
127
        if (!array_key_exists('Signature', $data)) {
128
            return $message;
129
        }
130
131
        if (!array_key_exists('SigAlg', $data)) {
132
            throw new Exception('Missing signature algorithm.');
133
        }
134
135
        $signData = array(
136
            'Signature' => $data['Signature'],
137
            'SigAlg'    => $data['SigAlg'],
138
            'Query'     => $data['SignedQuery'],
139
        );
140
141
        $message->addValidator(array(get_class($this), 'validateSignature'), $signData);
142
143
        return $message;
144
    }
145
146
    /**
147
     * Helper function to parse query data.
148
     *
149
     * This function returns the query string split into key=>value pairs.
150
     * It also adds a new parameter, SignedQuery, which contains the data that is
151
     * signed.
152
     *
153
     * @return string The query data that is signed.
154
     */
155
    private static function parseQuery()
0 ignored issues
show
Coding Style introduced by
parseQuery uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
156
    {
157
        /*
158
         * Parse the query string. We need to do this ourself, so that we get access
159
         * to the raw (urlencoded) values. This is required because different software
160
         * can urlencode to different values.
161
         */
162
        $data = array();
163
        $relayState = '';
164
        $sigAlg = '';
165
        $sigQuery = '';
166
        foreach (explode('&', $_SERVER['QUERY_STRING']) as $e) {
167
            $tmp = explode('=', $e, 2);
168
            $name = $tmp[0];
169
            if (count($tmp) === 2) {
170
                $value = $tmp[1];
171
            } else {
172
                /* No value for this parameter. */
173
                $value = '';
174
            }
175
            $name = urldecode($name);
176
            $data[$name] = urldecode($value);
177
178
            switch ($name) {
179
                case 'SAMLRequest':
180
                case 'SAMLResponse':
181
                    $sigQuery = $name . '=' . $value;
182
                    break;
183
                case 'RelayState':
184
                    $relayState = '&RelayState=' . $value;
185
                    break;
186
                case 'SigAlg':
187
                    $sigAlg = '&SigAlg=' . $value;
188
                    break;
189
            }
190
        }
191
192
        $data['SignedQuery'] = $sigQuery . $relayState . $sigAlg;
193
194
        return $data;
195
    }
196
197
    /**
198
     * Validate the signature on a HTTP-Redirect message.
199
     *
200
     * Throws an exception if we are unable to validate the signature.
201
     *
202
     * @param array          $data The data we need to validate the query string.
203
     * @param XMLSecurityKey $key  The key we should validate the query against.
204
     * @throws Exception
205
     */
206
    public static function validateSignature(array $data, XMLSecurityKey $key)
207
    {
208
        assert('array_key_exists("Query", $data)');
209
        assert('array_key_exists("SigAlg", $data)');
210
        assert('array_key_exists("Signature", $data)');
211
212
        $query = $data['Query'];
213
        $sigAlg = $data['SigAlg'];
214
        $signature = $data['Signature'];
215
216
        $signature = base64_decode($signature);
217
218
        if ($key->type !== XMLSecurityKey::RSA_SHA1) {
219
            throw new Exception('Invalid key type for validating signature on query string.');
220
        }
221
        if ($key->type !== $sigAlg) {
222
            $key = SAML2_Utils::castKey($key, $sigAlg);
223
        }
224
225
        if (!$key->verifySignature($query, $signature)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $key->verifySignature($query, $signature) of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
226
            throw new Exception('Unable to validate signature on query string.');
227
        }
228
    }
229
230
}
231