Passed
Push — master ( 0a52e0...b2bf8f )
by Pierre
04:01
created

Jwt::isExclude()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3

Importance

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