Passed
Branch master (28f442)
by Pierre
09:11 queued 06:31
created

Jwt   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Test Coverage

Coverage 95.65%

Importance

Changes 6
Bugs 0 Features 1
Metric Value
eloc 74
c 6
b 0
f 1
dl 0
loc 184
ccs 66
cts 69
cp 0.9565
rs 10
wmc 28

10 Methods

Rating   Name   Duplication   Size   Complexity  
A peel() 0 5 1
A getUser() 0 5 2
A isValidCredential() 0 8 3
A isValidAuthorization() 0 4 2
A sendError() 0 12 2
A isExclude() 0 12 3
A required() 0 5 3
A requestUriPrefix() 0 3 1
B process() 0 28 8
A isPreflight() 0 10 3
1
<?php
2
3
namespace App\Middlewares;
4
5
use App\Http\Request;
6
use App\Tools\Jwt\Token;
7
use App\Http\Interfaces\Middleware\ILayer;
8
use App\Container;
9
10
/**
11
 * App\Middleware\Jwt
12
 *
13
 * Intercept jwt header and auth if required
14
 */
15
class Jwt implements ILayer
16
{
17
18
    use \App\Middlewares\Reuse\TInit;
19
20
    const _SIGN = 'X-Middleware-Jwt';
21
    const _PASSWORD = 'password';
22
    const _EMAIL = 'email';
23
    const _USER_STATUS = 'status';
24
    const _VALID = 'valid';
25
    const _AUTORIZATION = 'Authorization';
26
    const _ERROR = 'error';
27
    const _ERROR_MESSAGE = 'errorMessage';
28
29
    /**
30
     * peel
31
     *
32
     * @param array $container
33
     * @param \Closure $next
34
     * @return \Closure
35
     */
36 14
    public function peel(Container $container, \Closure $next)
37
    {
38 14
        $this->init($container);
39 14
        $this->process();
40 14
        return $next($container);
41
    }
42
43
    /**
44
     * process
45
     *
46
     */
47 4
    protected function process()
48
    {
49 4
        if ($this->enabled) {
50 4
            $this->response->getHeaderManager()->add(
51 4
                self::_SIGN,
52 4
                microtime(true)
53
            );
54 4
            if ($this->required()) {
55 4
                if ($this->isValidAuthorization()) {
56 4
                    $toolJwtToken = new Token($this->config, $this->request);
57
                    try {
58 4
                        $tFrags = explode(' ', $this->headers[self::_AUTORIZATION]);
59 4
                        $decodedToken = $toolJwtToken->decode(trim($tFrags[1]));
60 4
                        if (isset($decodedToken->{Token::_DATA}->{Token::_DATA_ID})) {
61 4
                            $userId = $decodedToken->{Token::_DATA}->{Token::_DATA_ID};
62 4
                            $user = $this->getUser($userId);
63 4
                            if (!empty($user)) {
64 2
                                if ($this->isValidCredential($decodedToken, $user)) {
65 1
                                    $this->request->setSession('auth', $user, 'user');
66
                                } else {
67 2
                                    $this->sendError(403, 'bad credentials');
68
                                }
69
                            } else {
70 4
                                $this->sendError(403, 'bad user');
71
                            }
72
                        }
73
                    } catch (\Exception $e) {
74
                        $this->sendError(500, $e->getMessage());
75
                    }
76
                }
77
            }
78
        }
79
    }
80
81
    /**
82
     * send response and die
83
     *
84
     * @param integer $errorCode
85
     * @return void
86
     */
87 1
    protected function sendError(int $errorCode, string $errMsg)
88
    {
89
        $errorMsg = [
90 1
            self::_ERROR => true,
91 1
            self::_ERROR_MESSAGE => 'Auth failed : ' . $errMsg
92
        ];
93 1
        $this->response
94 1
            ->setCode($errorCode)
95 1
            ->setContent($errorMsg)
96 1
            ->send();
97 1
        if (false === $this->request->isCli()) {
98
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
99
        }
100
    }
101
102
    /**
103
     * isPreflight
104
     *
105
     * @return bool
106
     */
107 1
    protected function isPreflight(): bool
108
    {
109 1
        $isOptionsMethod = ($this->request->getMethod() == Request::METHOD_OPTIONS);
110 1
        $corsHeadersKeys = array_keys($this->headers);
111 1
        $hasOrigin = in_array('Origin', $corsHeadersKeys);
112 1
        $hasACRequestMethod = in_array(
113 1
            'Access-Control-Request-Method',
114
            $corsHeadersKeys
115
        );
116 1
        return ($isOptionsMethod && $hasOrigin && $hasACRequestMethod);
117
    }
118
119
    /**
120
     * isValidCredential
121
     *
122
     * @param object $decodedToken
123
     * @param array $user
124
     * @return boolean
125
     */
126 1
    protected function isValidCredential($decodedToken, $user): bool
127
    {
128 1
        $login = $decodedToken->{Token::_DATA}->{Token::_DATA_LOGIN};
129 1
        $passwordHash = $decodedToken->{Token::_DATA}->{Token::_DATA_PASSWORD_HASH};
130 1
        $checkLogin = ($login === $user[self::_EMAIL]);
131 1
        $checkPassword = password_verify($user[self::_PASSWORD], $passwordHash);
132 1
        $checkStatus = ($user[self::_USER_STATUS] === self::_VALID);
133 1
        return ($checkLogin && $checkPassword && $checkStatus);
134
    }
135
136
    /**
137
     * getUser
138
     *
139
     * @param int $userId
140
     * @return array
141
     */
142 1
    protected function getUser(int $userId): array
143
    {
144 1
        $authModel = new \App\Model\Users($this->config);
145 1
        $userList = $authModel->getById($userId);
146 1
        return isset($userList[0]) ? $userList[0] : $userList;
147
    }
148
149
    /**
150
     * isValidAuthorization
151
     *
152
     * @return boolean
153
     */
154 1
    protected function isValidAuthorization(): bool
155
    {
156 1
        return (isset($this->headers[self::_AUTORIZATION])
157 1
            && !empty($this->headers[self::_AUTORIZATION]));
158
    }
159
160
    /**
161
     * required
162
     *
163
     * @return boolean
164
     */
165 1
    protected function required(): bool
166
    {
167 1
        return (!$this->isExclude()
168 1
            && $this->requestUriPrefix() === $this->prefix
169 1
            && !$this->isPreflight());
170
    }
171
172
    /**
173
     * isExclude
174
     *
175
     * @return boolean
176
     */
177 1
    protected function isExclude(): bool
178
    {
179 1
        $disallowed = $this->configParams[self::_EXCLUDE];
180 1
        $count = count($disallowed);
181 1
        for ($c = 0; $c < $count; ++$c) {
182 1
            $composed = $this->prefix . $disallowed[$c];
183 1
            $isExclude = ($composed == $this->request->getUri());
184 1
            if ($isExclude) {
185 1
                return true;
186
            }
187
        }
188 1
        return false;
189
    }
190
191
    /**
192
     * uriPrefix
193
     *
194
     * @return string
195
     */
196 1
    protected function requestUriPrefix(): string
197
    {
198 1
        return substr($this->request->getUri(), 0, strlen($this->prefix));
199
    }
200
}
201