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