UriSigner   A
last analyzed

Complexity

Total Complexity 21

Size/Duplication

Total Lines 126
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 21
lcom 1
cbo 1
dl 0
loc 126
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A sign() 0 13 3
A check() 0 18 3
A computeHash() 0 8 2
C buildUrl() 0 17 12
1
<?php
2
3
/**
4
 * This file is part of tenside/core.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core
18
 * @filesource
19
 */
20
21
namespace Tenside\CoreBundle\Util;
22
23
use Tenside\Core\Config\TensideJsonConfig;
24
25
/**
26
 * Signs URIs.
27
 *
28
 * This service behaves exactly like the one in the symfony framework but is ready for phar use.
29
 * As the kernel.secret is not available during phar compile time, we need it lazy loaded.
30
 *
31
 * @author Fabien Potencier <[email protected]>
32
 */
33
class UriSigner
34
{
35
    /**
36
     * The resolved secret.
37
     *
38
     * @var string
39
     */
40
    private $secret;
41
42
    /**
43
     * The config to obtain the secret parameter from.
44
     *
45
     * @var TensideJsonConfig
46
     */
47
    private $tensideConfig;
48
49
    /**
50
     * Constructor.
51
     *
52
     * @param TensideJsonConfig $tensideConfig The tenside config.
53
     */
54
    public function __construct(TensideJsonConfig $tensideConfig)
55
    {
56
        $this->tensideConfig = $tensideConfig;
57
    }
58
59
    /**
60
     * Signs a URI.
61
     *
62
     * The given URI is signed by adding a _hash query string parameter
63
     * which value depends on the URI and the secret.
64
     *
65
     * @param string $uri A URI to sign.
66
     *
67
     * @return string The signed URI
68
     */
69
    public function sign($uri)
70
    {
71
        $url = parse_url($uri);
72
        if (isset($url['query'])) {
73
            parse_str($url['query'], $params);
74
        } else {
75
            $params = array();
76
        }
77
78
        $uri = $this->buildUrl($url, $params);
0 ignored issues
show
Security Bug introduced by
It seems like $url defined by parse_url($uri) on line 71 can also be of type false; however, Tenside\CoreBundle\Util\UriSigner::buildUrl() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
Bug introduced by
It seems like $params can also be of type null; however, Tenside\CoreBundle\Util\UriSigner::buildUrl() does only seem to accept array, 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...
79
80
        return $uri.(false === (strpos($uri, '?')) ? '?' : '&').'_hash='.$this->computeHash($uri);
81
    }
82
83
    /**
84
     * Checks that a URI contains the correct hash.
85
     *
86
     * The _hash query string parameter must be the last one
87
     * (as it is generated that way by the sign() method, it should
88
     * never be a problem).
89
     *
90
     * @param string $uri A signed URI.
91
     *
92
     * @return bool True if the URI is signed correctly, false otherwise
93
     */
94
    public function check($uri)
95
    {
96
        $url = parse_url($uri);
97
        if (isset($url['query'])) {
98
            parse_str($url['query'], $params);
99
        } else {
100
            $params = array();
101
        }
102
103
        if (empty($params['_hash'])) {
104
            return false;
105
        }
106
107
        $hash = urlencode($params['_hash']);
108
        unset($params['_hash']);
109
110
        return $this->computeHash($this->buildUrl($url, $params)) === $hash;
0 ignored issues
show
Security Bug introduced by
It seems like $url defined by parse_url($uri) on line 96 can also be of type false; however, Tenside\CoreBundle\Util\UriSigner::buildUrl() does only seem to accept array, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
111
    }
112
113
    /**
114
     * Calculate the hash.
115
     *
116
     * @param string $uri The uri to calculate the hash for.
117
     *
118
     * @return string
119
     */
120
    private function computeHash($uri)
121
    {
122
        if (!isset($this->secret)) {
123
            $this->secret = $this->tensideConfig->getSecret();
124
        }
125
126
        return urlencode(base64_encode(hash_hmac('sha256', $uri, $this->secret, true)));
127
    }
128
129
    /**
130
     * Build the url.
131
     *
132
     * @param array $url    The url values.
133
     *
134
     * @param array $params The url parameters.
135
     *
136
     * @return string
137
     *
138
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
139
     * @SuppressWarnings(PHPMD.NPathComplexity)
140
     */
141
    private function buildUrl(array $url, array $params = array())
142
    {
143
        ksort($params);
144
        $url['query'] = http_build_query($params, '', '&');
145
146
        $scheme   = isset($url['scheme']) ? $url['scheme'].'://' : '';
147
        $host     = isset($url['host']) ? $url['host'] : '';
148
        $port     = isset($url['port']) ? ':'.$url['port'] : '';
149
        $user     = isset($url['user']) ? $url['user'] : '';
150
        $pass     = isset($url['pass']) ? ':'.$url['pass']  : '';
151
        $pass     = ($user || $pass) ? $pass . '@' : '';
152
        $path     = isset($url['path']) ? $url['path'] : '';
153
        $query    = isset($url['query']) && $url['query'] ? '?'.$url['query'] : '';
154
        $fragment = isset($url['fragment']) ? '#'.$url['fragment'] : '';
155
156
        return $scheme.$user.$pass.$host.$port.$path.$query.$fragment;
157
    }
158
}
159