Completed
Push — master ( c24efe...a8d27d )
by Tomas
03:39
created

BearerTokenAuthorization::ipInRange()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.032

Importance

Changes 3
Bugs 0 Features 1
Metric Value
c 3
b 0
f 1
dl 0
loc 13
ccs 8
cts 10
cp 0.8
rs 9.4286
cc 2
eloc 9
nc 2
nop 2
crap 2.032
1
<?php
2
3
namespace Tomaj\NetteApi\Authorization;
4
5
use Tomaj\NetteApi\Misc\BearerTokenRepositoryInterface;
6
use Tomaj\NetteApi\Misc\IpDetectorInterface;
7
8
class BearerTokenAuthorization implements ApiAuthorizationInterface
9
{
10
    /**
11
     * @var BearerTokenRepositoryInterface
12
     */
13
    private $tokenRepository;
14
15
    /**
16
     * @var string|boolean
17
     */
18
    private $errorMessage = false;
19
20
    /**
21
     * @var IpDetectorInterface
22
     */
23
    private $ipDetector;
24
25
    /**
26
     * BearerTokenAuthorization constructor.
27
     *
28
     * @param BearerTokenRepositoryInterface $tokenRepository
29
     * @param IpDetectorInterface            $ipDetector
30
     */
31 24
    public function __construct(BearerTokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
32
    {
33 24
        $this->tokenRepository = $tokenRepository;
34 24
        $this->ipDetector = $ipDetector;
35 24
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 24
    public function authorized()
41
    {
42 24
        $token = $this->readAuthorizationToken();
43 24
        if (!$token) {
44 6
            return false;
45
        }
46
47 18
        $result = $this->tokenRepository->validToken($token);
48 18
        if (!$result) {
49 3
            $this->errorMessage = 'Token doesn\'t exists or isn\'t active';
50 3
            return false;
51
        }
52
53 15
        if (!$this->isValidIp($this->tokenRepository->ipRestrictions($token))) {
54 6
            $this->errorMessage = 'Invalid IP';
55 6
            return false;
56
        }
57
58 12
        return true;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 12
    public function getErrorMessage()
65
    {
66 12
        return $this->errorMessage;
67
    }
68
69
    /**
70
     * Check if actual IP from detector satisfies @ipRestristions
71
     * $ipRestrictions should contains multiple formats:
72
     *   '*'                  - accessible from anywhare
73
     *   '127.0.0.1'          - accessible from single IP
74
     *   '127.0.0.1,127.0.02' - accessible from multiple IP, separator could be new line or space
75
     *   '127.0.0.1/32'       - accessible from ip range
76
     *
77
     * @return boolean
78
     */
79 15
    private function isValidIp($ipRestrictions)
80
    {
81 15
        if ($ipRestrictions == '*' || $ipRestrictions == '' || $ipRestrictions == null) {
82 3
            return true;
83
        }
84 12
        $ip = $this->ipDetector->getRequestIp();
85
86 12
        $ipWhiteList = str_replace([',', ' ', "\n"], '#', $ipRestrictions);
87 12
        $ipWhiteList = explode('#', $ipWhiteList);
88 12
        foreach ($ipWhiteList as $whiteIp) {
89 12
            if ($whiteIp == $ip) {
90 6
                return true;
91
            }
92 9
            if (strpos($whiteIp, '/') !== false) {
93 3
                return $this->ipInRange($ip, $whiteIp);
94
            }
95 6
        }
96
97 6
        return false;
98
    }
99
100
    /**
101
     * Check if IP is in $range
102
     *
103
     * @param string $ip
104
     * @param string $range
105
     * @return boolean
106
     */
107 3
    private function ipInRange($ip, $range)
108
    {
109 3
        if (strpos($range, '/') == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($range, '/') of type integer to the boolean false. If you are specifically checking for 0, consider using something more explicit like === 0 instead.
Loading history...
110
            $range .= '/32';
111
        }
112
        // $range is in IP/CIDR format eg 127.0.0.1/24
113 3
        list( $range, $netmask ) = explode('/', $range, 2);
114 3
        $range_decimal = ip2long($range);
115 3
        $ip_decimal = ip2long($ip);
116 3
        $wildcard_decimal = pow(2, (32 - $netmask)) - 1;
117 3
        $netmask_decimal = ~ $wildcard_decimal;
118 3
        return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
119
    }
120
121
122
    /**
123
     * Read HTTP reader with authorization token
124
     * If everything is ok, it return token. In other situations returns false and set errorMessage.
125
     *
126
     * @return string|boolean
127
     */
128 24
    private function readAuthorizationToken()
0 ignored issues
show
Coding Style introduced by
readAuthorizationToken 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...
129
    {
130 24
        if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
131 3
            $this->errorMessage = 'Authorization header HTTP_Authorization is not set';
132 3
            return false;
133
        }
134 21
        $parts = explode(' ', $_SERVER['HTTP_AUTHORIZATION']);
135 21
        if (count($parts) != 2) {
136 3
            $this->errorMessage = 'Authorization header contains invalid structure';
137 3
            return false;
138
        }
139 18
        if (!strtolower($parts[0]) == 'bearer') {
140
            $this->errorMessage = 'Authorization header doesn\'t contains bearer token';
141
            return false;
142
        }
143 18
        return $parts[1];
144
    }
145
}
146