Completed
Pull Request — master (#61)
by Michal
01:53
created

BearerTokenAuthorization::authorized()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 20
ccs 12
cts 12
cp 1
rs 9.6
c 0
b 0
f 0
cc 4
nc 4
nop 0
crap 4
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|null
17
     */
18
    private $errorMessage = null;
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 33
    public function __construct(BearerTokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
32
    {
33 33
        $this->tokenRepository = $tokenRepository;
34 33
        $this->ipDetector = $ipDetector;
35 33
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 33
    public function authorized(): bool
41
    {
42 33
        $token = $this->readAuthorizationToken();
43 33
        if ($token === null) {
44 12
            return false;
45
        }
46
47 21
        $result = $this->tokenRepository->validToken($token);
48 21
        if (!$result) {
49 3
            $this->errorMessage = 'Token doesn\'t exists or isn\'t active';
50 3
            return false;
51
        }
52
53 18
        if (!$this->isValidIp($this->tokenRepository->ipRestrictions($token))) {
54 9
            $this->errorMessage = 'Invalid IP';
55 9
            return false;
56
        }
57
58 12
        return true;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64 18
    public function getErrorMessage(): ?string
65
    {
66 18
        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
     *   null                 - disabled access
77
     *
78
     * @return boolean
79
     */
80 18
    private function isValidIp(?string $ipRestrictions): bool
81
    {
82 18
        if ($ipRestrictions === null) {
83 3
            return false;
84
        }
85 15
        if ($ipRestrictions === '*' || $ipRestrictions === '') {
86 3
            return true;
87
        }
88 12
        $ip = $this->ipDetector->getRequestIp();
89
90 12
        $ipWhiteList = str_replace([',', ' ', "\n"], '#', $ipRestrictions);
91 12
        $ipWhiteList = explode('#', $ipWhiteList);
92 12
        foreach ($ipWhiteList as $whiteIp) {
93 12
            if ($whiteIp === $ip) {
94 6
                return true;
95
            }
96 9
            if (strpos($whiteIp, '/') !== false) {
97 7
                return $this->ipInRange($ip, $whiteIp);
98
            }
99
        }
100
101 6
        return false;
102
    }
103
104
    /**
105
     * Check if IP is in $range
106
     *
107
     * @param string $ip     this ip will be verified
108
     * @param string $range  is in IP/CIDR format eg 127.0.0.1/24
109
     * @return boolean
110
     */
111 3
    private function ipInRange(string $ip, string $range): bool
112
    {
113 3
        list($range, $netmask) = explode('/', $range, 2);
114 3
        $range_decimal = ip2long($range);
115 3
        $ipDecimal = ip2long($ip);
116 3
        $wildcard_decimal = pow(2, (32 - (int)$netmask)) - 1;
117 3
        $netmask_decimal = ~ $wildcard_decimal;
118 3
        return (($ipDecimal & $netmask_decimal) === ($range_decimal & $netmask_decimal));
119
    }
120
121
    /**
122
     * Read HTTP reader with authorization token
123
     * If everything is ok, it return token. In other situations returns false and set errorMessage.
124
     *
125
     * @return string|null
126
     */
127 33
    private function readAuthorizationToken(): ?string
128
    {
129 33
        if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
130 6
            $this->errorMessage = 'Authorization header HTTP_Authorization is not set';
131 6
            return null;
132
        }
133 27
        $parts = explode(' ', $_SERVER['HTTP_AUTHORIZATION']);
134 27
        if (count($parts) !== 2) {
135 3
            $this->errorMessage = 'Authorization header contains invalid structure';
136 3
            return null;
137
        }
138 24
        if (strtolower($parts[0]) !== 'bearer') {
139 3
            $this->errorMessage = 'Authorization header doesn\'t contains bearer token';
140 3
            return null;
141
        }
142 21
        return $parts[1];
143
    }
144
}
145