Jwt::isPreflight()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 3
eloc 7
c 2
b 0
f 1
nc 3
nop 0
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace App\Middlewares;
6
7
use Nymfonya\Component\Container;
8
use Nymfonya\Component\Http\Interfaces\MiddlewareInterface;
9
use Nymfonya\Component\Http\Request;
10
use App\Component\Jwt\Token;
11
use App\Model\Users;
12
13
/**
14
 * App\Middleware\Jwt
15
 *
16
 * Intercept jwt header and auth if required
17
 */
18
class Jwt implements MiddlewareInterface
19
{
20
    use \App\Middlewares\Reuse\TInit;
21
22
    const _SIGN = 'X-Middleware-Jwt';
23
    const _PASSWORD = 'password';
24
    const _EMAIL = 'email';
25
    const _USER_STATUS = 'status';
26
    const _VALID = 'valid';
27
    const _AUTORIZATION = 'Authorization';
28
    const _ERROR = 'error';
29
    const _ERROR_MESSAGE = 'errorMessage';
30
31
    /**
32
     * peel
33
     *
34
     * @param array $container
35
     * @param \Closure $next
36
     * @return \Closure
37
     */
38 15
    public function peel(Container $container, \Closure $next)
39
    {
40 15
        $this->init($container);
41 15
        $this->process();
42 15
        return $next($container);
43
    }
44
45
    /**
46
     * process
47
     *
48
     */
49 5
    protected function process()
50
    {
51 5
        if ($this->enabled) {
52 5
            $this->response->getHeaderManager()->add(
53 5
                self::_SIGN,
54 5
                strval(microtime(true))
55
            );
56 5
            if ($this->required()) {
57 5
                if ($this->isValidAuthorization()) {
58 4
                    $toolJwtToken = new Token($this->config, $this->request);
59
                    try {
60 4
                        $tFrags = explode(' ', $this->headers[self::_AUTORIZATION]);
61 4
                        $decodedToken = $toolJwtToken->decode(trim($tFrags[1]));
62 4
                        if (isset($decodedToken->{Token::_DATA}->{Token::_DATA_ID})) {
63 4
                            $userId = $decodedToken->{Token::_DATA}->{Token::_DATA_ID};
64 4
                            $user = $this->getUser($userId);
65 4
                            if (!empty($user)) {
66 2
                                if ($this->isValidCredential($decodedToken, $user)) {
67 1
                                    $this->request->setSession('auth', $user, 'user');
68
                                } else {
69 2
                                    $this->sendError(403, 'bad credentials');
70
                                }
71
                            } else {
72 4
                                $this->sendError(403, 'bad user');
73
                            }
74
                        }
75
                    } catch (\Exception $e) {
76 4
                        $this->sendError(500, $e->getMessage());
77
                    }
78
                } else {
79 1
                    $this->sendError(403, 'Token required');
80
                }
81
            }
82
        }
83
    }
84
85
    /**
86
     * send response and die
87
     *
88
     * @param integer $errorCode
89
     * @return void
90
     */
91 1
    protected function sendError(int $errorCode, string $errMsg)
92
    {
93
        $errorMsg = [
94 1
            self::_ERROR => true,
95 1
            self::_ERROR_MESSAGE => 'Auth failed : ' . $errMsg
96
        ];
97 1
        $this->response
98 1
            ->setCode($errorCode)
99 1
            ->setContent($errorMsg)
100 1
            ->send();
101 1
        if (false === $this->request->isCli()) {
102
            $this->kernel->shutdown();
103
        }
104
    }
105
106
    /**
107
     * isPreflight
108
     *
109
     * @return bool
110
     */
111 1
    protected function isPreflight(): bool
112
    {
113 1
        $isOptionsMethod = ($this->request->getMethod() == Request::METHOD_OPTIONS);
114 1
        $corsHeadersKeys = array_keys($this->headers);
115 1
        $hasOrigin = in_array('Origin', $corsHeadersKeys);
116 1
        $hasACRequestMethod = in_array(
117 1
            'Access-Control-Request-Method',
118
            $corsHeadersKeys
119
        );
120 1
        return ($isOptionsMethod && $hasOrigin && $hasACRequestMethod);
121
    }
122
123
    /**
124
     * isValidCredential
125
     *
126
     * @param object $decodedToken
127
     * @param array $user
128
     * @return boolean
129
     */
130 1
    protected function isValidCredential($decodedToken, $user): bool
131
    {
132 1
        $login = $decodedToken->{Token::_DATA}->{Token::_DATA_LOGIN};
133 1
        $passwordHash = $decodedToken->{Token::_DATA}->{Token::_DATA_PASSWORD_HASH};
134 1
        $checkLogin = ($login === $user[self::_EMAIL]);
135 1
        $checkPassword = password_verify($user[self::_PASSWORD], $passwordHash);
136 1
        $checkStatus = ($user[self::_USER_STATUS] === self::_VALID);
137 1
        return ($checkLogin && $checkPassword && $checkStatus);
138
    }
139
140
    /**
141
     * getUser
142
     *
143
     * @param int $userId
144
     * @return array
145
     */
146 1
    protected function getUser(int $userId): array
147
    {
148 1
        $authModel = new Users($this->config);
149 1
        $userList = $authModel->getById($userId);
150 1
        return isset($userList[0]) ? $userList[0] : $userList;
151
    }
152
153
    /**
154
     * isValidAuthorization
155
     *
156
     * @return boolean
157
     */
158 1
    protected function isValidAuthorization(): bool
159
    {
160 1
        return (isset($this->headers[self::_AUTORIZATION])
161 1
            && !empty($this->headers[self::_AUTORIZATION]));
162
    }
163
164
    /**
165
     * required
166
     *
167
     * @return boolean
168
     */
169 1
    protected function required(): bool
170
    {
171
        if (
172 1
            $this->request->isCli()
173 1
            && $this->request->getMethod() === Request::METHOD_TRACE
174
        ) {
175 1
            return false;
176
        }
177
        return (!$this->isExclude()
178
            && $this->requestUriPrefix() === $this->prefix
179
            && !$this->isPreflight());
180
    }
181
182
    /**
183
     * isExclude
184
     *
185
     * @return boolean
186
     */
187 1
    protected function isExclude(): bool
188
    {
189 1
        $disallowed = $this->configParams[self::_EXCLUDE];
190 1
        $count = count($disallowed);
191 1
        for ($c = 0; $c < $count; ++$c) {
192 1
            $composed = $this->prefix . $disallowed[$c];
193 1
            $isExclude = ($composed == $this->request->getUri());
194 1
            if ($isExclude) {
195 1
                return true;
196
            }
197
        }
198 1
        return false;
199
    }
200
201
    /**
202
     * uriPrefix
203
     *
204
     * @return string
205
     */
206 1
    protected function requestUriPrefix(): string
207
    {
208 1
        return substr($this->request->getUri(), 0, strlen($this->prefix));
209
    }
210
}
211