Completed
Branch master (b52e58)
by Pierre
03:02 queued 37s
created

Jwt::process()   B

Complexity

Conditions 8
Paths 13

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 1
Metric Value
cc 8
eloc 20
c 5
b 0
f 1
nc 13
nop 0
dl 0
loc 26
rs 8.4444
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
    public function peel(Container $container, \Closure $next)
37
    {
38
        $this->init($container);
39
        $this->process();
40
        return $next($container);
41
    }
42
43
    /**
44
     * process
45
     *
46
     */
47
    protected function process()
48
    {
49
        if ($this->enabled) {
50
            $this->response->getHeaderManager()->add(
51
                self::_SIGN,
52
                microtime(true)
53
            );
54
            if ($this->required()) {
55
                if ($this->isValidAuthorization()) {
56
                    $toolJwtToken = new Token($this->config, $this->request);
57
                    try {
58
                        $tFrags = explode(' ', $this->headers[self::_AUTORIZATION]);
59
                        $decodedToken = $toolJwtToken->decode(trim($tFrags[1]));
60
                        if (isset($decodedToken->{Token::_DATA}->{Token::_DATA_ID})) {
61
                            $userId = $decodedToken->{Token::_DATA}->{Token::_DATA_ID};
62
                            $user = $this->getUser($userId);
63
                            if ($user !== false) {
0 ignored issues
show
introduced by
The condition $user !== false is always true.
Loading history...
64
                                if ($this->isValidCredential($decodedToken, $user)) {
65
                                    $this->request->setSession('auth', $user, 'user');
66
                                } else {
67
                                    $this->sendError(403, 'bad credentials');
68
                                }
69
                            }
70
                        }
71
                    } catch (\Exception $e) {
72
                        $this->sendError(500, $e->getMessage());
73
                    }
74
                }
75
            }
76
        }
77
    }
78
79
    /**
80
     * send response and die
81
     *
82
     * @param integer $errorCode
83
     * @return void
84
     */
85
    protected function sendError(int $errorCode, string $errMsg)
86
    {
87
        $errorMsg = [
88
            self::_ERROR => true,
89
            self::_ERROR_MESSAGE => 'Auth failed : ' . $errMsg
90
        ];
91
        $this->response
92
            ->setCode($errorCode)
93
            ->setContent($errorMsg)
94
            ->send();
95
        if (false === $this->request->isCli()) {
96
            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...
97
        }
98
    }
99
100
    /**
101
     * isPreflight
102
     *
103
     * @return bool
104
     */
105
    protected function isPreflight(): bool
106
    {
107
        $isOptionsMethod = ($this->request->getMethod() == Request::METHOD_OPTIONS);
108
        $corsHeadersKeys = array_keys($this->headers);
109
        $hasOrigin = in_array('Origin', $corsHeadersKeys);
110
        $hasACRequestMethod = in_array(
111
            'Access-Control-Request-Method',
112
            $corsHeadersKeys
113
        );
114
        return ($isOptionsMethod && $hasOrigin && $hasACRequestMethod);
115
    }
116
117
    /**
118
     * isValidCredential
119
     *
120
     * @param object $decodedToken
121
     * @param array $user
122
     * @return boolean
123
     */
124
    protected function isValidCredential($decodedToken, $user): bool
125
    {
126
        $login = $decodedToken->{Token::_DATA}->{Token::_DATA_LOGIN};
127
        $passwordHash = $decodedToken->{Token::_DATA}->{Token::_DATA_PASSWORD_HASH};
128
        $checkLogin = ($login === $user[self::_EMAIL]);
129
        $checkPassword = password_verify($user[self::_PASSWORD], $passwordHash);
130
        $checkStatus = ($user[self::_USER_STATUS] === self::_VALID);
131
        return ($checkLogin && $checkPassword && $checkStatus);
132
    }
133
134
    /**
135
     * getUser
136
     *
137
     * @param int $userId
138
     * @return array
139
     */
140
    protected function getUser(int $userId): array
141
    {
142
        $authModel = new \App\Model\Users($this->config);
143
        $userList = $authModel->getById($userId);
144
        return isset($userList[0]) ? $userList[0] : $userList;
145
    }
146
147
    /**
148
     * isValidAuthorization
149
     *
150
     * @return boolean
151
     */
152
    protected function isValidAuthorization(): bool
153
    {
154
        return (isset($this->headers[self::_AUTORIZATION])
155
            && !empty($this->headers[self::_AUTORIZATION]));
156
    }
157
158
    /**
159
     * required
160
     *
161
     * @return boolean
162
     */
163
    protected function required(): bool
164
    {
165
        return (!$this->isExclude()
166
            && $this->requestUriPrefix() === $this->prefix
167
            && !$this->isPreflight());
168
    }
169
170
    /**
171
     * isExclude
172
     *
173
     * @return boolean
174
     */
175
    protected function isExclude(): bool
176
    {
177
        $disallowed = $this->configParams[self::_EXCLUDE];
178
        for ($c = 0; $c < count($disallowed); ++$c) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
179
            $composed = $this->prefix . $disallowed[$c];
180
            $isExclude = ($composed == $this->request->getUri());
181
            if ($isExclude) {
182
                return true;
183
            }
184
        }
185
        return false;
186
    }
187
188
    /**
189
     * uriPrefix
190
     *
191
     * @return string
192
     */
193
    protected function requestUriPrefix(): string
194
    {
195
        return substr($this->request->getUri(), 0, strlen($this->prefix));
196
    }
197
}
198