Completed
Push — master ( 2cfb2f...9442e1 )
by Tomas
02:36
created

BearerTokenAuthorization::readAuthorizationToken()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 4
Bugs 0 Features 2
Metric Value
c 4
b 0
f 2
dl 0
loc 17
ccs 12
cts 12
cp 1
rs 9.2
cc 4
eloc 12
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|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 27
    public function __construct(BearerTokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
32
    {
33 27
        $this->tokenRepository = $tokenRepository;
34 27
        $this->ipDetector = $ipDetector;
35 27
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 27
    public function authorized()
41
    {
42 27
        $token = $this->readAuthorizationToken();
43 27
        if (!$token) {
44 9
            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 15
    public function getErrorMessage()
65
    {
66 15
        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     this ip will be verified
104
     * @param string $range  is in IP/CIDR format eg 127.0.0.1/24
105
     * @return boolean
106
     */
107 3
    private function ipInRange($ip, $range)
108
    {
109 3
        list( $range, $netmask ) = explode('/', $range, 2);
110 3
        $range_decimal = ip2long($range);
111 3
        $ip_decimal = ip2long($ip);
112 3
        $wildcard_decimal = pow(2, (32 - $netmask)) - 1;
113 3
        $netmask_decimal = ~ $wildcard_decimal;
114 3
        return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
115
    }
116
117
118
    /**
119
     * Read HTTP reader with authorization token
120
     * If everything is ok, it return token. In other situations returns false and set errorMessage.
121
     *
122
     * @return string|boolean
123
     */
124 27
    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...
125
    {
126 27
        if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
127 3
            $this->errorMessage = 'Authorization header HTTP_Authorization is not set';
128 3
            return false;
129
        }
130 24
        $parts = explode(' ', $_SERVER['HTTP_AUTHORIZATION']);
131 24
        if (count($parts) != 2) {
132 3
            $this->errorMessage = 'Authorization header contains invalid structure';
133 3
            return false;
134
        }
135 21
        if (strtolower($parts[0]) != 'bearer') {
136 3
            $this->errorMessage = 'Authorization header doesn\'t contains bearer token';
137 3
            return false;
138
        }
139 18
        return $parts[1];
140
    }
141
}
142