Completed
Branch master (b51c85)
by Pierre
02:15
created

Jwt::peel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 2
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
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
64
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
                        $this->sendError(500, $e->getMessage());
77
                    }
78
                }
79
            }
80
        }
81
    }
82
83
    /**
84
     * send response and die
85
     *
86
     * @param integer $errorCode
87
     * @return void
88
     */
89 1
    protected function sendError(int $errorCode, string $errMsg)
90
    {
91
        $errorMsg = [
92 1
            self::_ERROR => true,
93 1
            self::_ERROR_MESSAGE => 'Auth failed : ' . $errMsg
94
        ];
95 1
        $this->response
96 1
            ->setCode($errorCode)
97 1
            ->setContent($errorMsg)
98 1
            ->send();
99 1
        if (false === $this->request->isCli()) {
100
            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...
101
        }
102
    }
103
104
    /**
105
     * isPreflight
106
     *
107
     * @return bool
108
     */
109 1
    protected function isPreflight(): bool
110
    {
111 1
        $isOptionsMethod = ($this->request->getMethod() == Request::METHOD_OPTIONS);
112 1
        $corsHeadersKeys = array_keys($this->headers);
113 1
        $hasOrigin = in_array('Origin', $corsHeadersKeys);
114 1
        $hasACRequestMethod = in_array(
115 1
            'Access-Control-Request-Method',
116 1
            $corsHeadersKeys
117
        );
118 1
        return ($isOptionsMethod && $hasOrigin && $hasACRequestMethod);
119
    }
120
121
    /**
122
     * isValidCredential
123
     *
124
     * @param object $decodedToken
125
     * @param array $user
126
     * @return boolean
127
     */
128 1
    protected function isValidCredential($decodedToken, $user): bool
129
    {
130 1
        $login = $decodedToken->{Token::_DATA}->{Token::_DATA_LOGIN};
131 1
        $passwordHash = $decodedToken->{Token::_DATA}->{Token::_DATA_PASSWORD_HASH};
132 1
        $checkLogin = ($login === $user[self::_EMAIL]);
133 1
        $checkPassword = password_verify($user[self::_PASSWORD], $passwordHash);
134 1
        $checkStatus = ($user[self::_USER_STATUS] === self::_VALID);
135 1
        return ($checkLogin && $checkPassword && $checkStatus);
136
    }
137
138
    /**
139
     * getUser
140
     *
141
     * @param int $userId
142
     * @return array
143
     */
144 1
    protected function getUser(int $userId): array
145
    {
146 1
        $authModel = new \App\Model\Users($this->config);
147 1
        $userList = $authModel->getById($userId);
148 1
        return isset($userList[0]) ? $userList[0] : $userList;
149
    }
150
151
    /**
152
     * isValidAuthorization
153
     *
154
     * @return boolean
155
     */
156 1
    protected function isValidAuthorization(): bool
157
    {
158 1
        return (isset($this->headers[self::_AUTORIZATION])
159 1
            && !empty($this->headers[self::_AUTORIZATION]));
160
    }
161
162
    /**
163
     * required
164
     *
165
     * @return boolean
166
     */
167 1
    protected function required(): bool
168
    {
169 1
        return (!$this->isExclude()
170 1
            && $this->requestUriPrefix() === $this->prefix
171 1
            && !$this->isPreflight());
172
    }
173
174
    /**
175
     * isExclude
176
     *
177
     * @return boolean
178
     */
179 1
    protected function isExclude(): bool
180
    {
181 1
        $disallowed = $this->configParams[self::_EXCLUDE];
182 1
        $count = count($disallowed);
183 1
        for ($c = 0; $c < $count; ++$c) {
184 1
            $composed = $this->prefix . $disallowed[$c];
185 1
            $isExclude = ($composed == $this->request->getUri());
186 1
            if ($isExclude) {
187 1
                return true;
188
            }
189
        }
190 1
        return false;
191
    }
192
193
    /**
194
     * uriPrefix
195
     *
196
     * @return string
197
     */
198 1
    protected function requestUriPrefix(): string
199
    {
200 1
        return substr($this->request->getUri(), 0, strlen($this->prefix));
201
    }
202
}
203