Test Failed
Push — master ( da5486...b4738e )
by LEUNG
06:50
created

Jwt::remove()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
ccs 0
cts 0
cp 0
crap 2
1
<?php
2
3
namespace xiaodi;
4
5
use think\App;
6
use Lcobucci\JWT\Token;
7
use Lcobucci\JWT\Parser;
8
use Lcobucci\JWT\Builder;
9
use Lcobucci\JWT\Signer\Key;
10
use xiaodi\Blacklist;
11
use xiaodi\Exception\JWTException;
12
use xiaodi\Exception\HasLoggedException;
13
use xiaodi\Exception\TokenAlreadyEexpired;
14
use xiaodi\Exception\JWTInvalidArgumentException;
15
16
class Jwt
17
{
18
    private $token;
19
20
    private $blacklist;
21
22
    use \xiaodi\Traits\Jwt;
23
24
    public function __construct(App $app, Blacklist $blacklist)
25
    {
26
        $this->app = $app;
0 ignored issues
show
Bug Best Practice introduced by
The property app does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
27
        $this->blacklist = $blacklist;
28
        $this->builder = new Builder();
0 ignored issues
show
Bug Best Practice introduced by
The property builder does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
29
30
        $config = $this->getConfig();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $config is correct as $this->getConfig() targeting xiaodi\Jwt::getConfig() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
31
        foreach ($config as $key => $v) {
0 ignored issues
show
Bug introduced by
The expression $config of type void is not traversable.
Loading history...
32
            $this->$key = $v;
33
        }
34
    }
35
36
    /**
37 15
     * 获取jwt配置
38
     *
39 15
     * @return void
40 15
     */
41
    public function getConfig()
42 15
    {
43 15
        return $this->app->config->get('jwt');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->app->config->get('jwt') also could return the type array which is incompatible with the documented return type void.
Loading history...
44 15
    }
45
46 15
    /**
47
     * 生成 Token.
48 15
     *
49
     * @param array $claims
50 15
     *
51
     * @return \Lcobucci\JWT\Token
52
     */
53
    public function token(array $claims)
54
    {
55
        $time = time();
56
        $uniqid = uniqid();
57
58
        // 单点登录
59
        if ($this->sso()) {
60 12
            $sso_key = $this->ssoKey();
61
62 12
            if (empty($claims[$sso_key])) {
63 12
                throw new JWTInvalidArgumentException("未设置 \$claims['{$this->ssoKey}']值", 500);
64
            }
65
            $uniqid = $claims[$sso_key];
66 12
        }
67 4
68
        $this->builder->issuedAt($time)
69 3
            ->identifiedBy($uniqid, true)
70 1
            ->canOnlyBeUsedAfter($time + $this->notBefore())
71
            ->expiresAt($time + $this->ttl());
72 2
73
        foreach ($claims as $key => $claim) {
74
            $this->builder->withClaim($key, $claim);
75 10
        }
76 10
77 10
        $token = $this->builder->getToken($this->getSigner(), $this->makeKey());
78 10
79
        if (true === $this->sso()) {
80 10
            $this->setCacheIssuedAt($uniqid, $time);
0 ignored issues
show
Bug introduced by
The method setCacheIssuedAt() does not exist on xiaodi\Jwt. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

80
            $this->/** @scrutinizer ignore-call */ 
81
                   setCacheIssuedAt($uniqid, $time);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
81 10
        }
82
83
        return $token;
84 10
    }
85
86 9
    /**
87 2
     * 解析Token.
88
     *
89
     * @param string $token
90 9
     *
91
     * @return Token
92
     */
93
    public function parse(string $token)
94
    {
95
        try {
96
            $token = (new Parser())->parse($token);
97
        } catch (\InvalidArgumentException $e) {
98
            throw new JWTInvalidArgumentException('此 Token 解析失败', 500);
99
        }
100 6
101
        return $token;
102
    }
103 6
104 1
    protected function getRequestToken()
105 1
    {
106
        switch ($this->type) {
107
            case 'Header':
108 6
                $bearer = new BearerToken($this->app);
109
                $token = $bearer->getToken();
110
                break;
111
            case 'Cookie':
112
                $token = $this->app->cookie->get('token');
113
                break;
114
            case 'Url':
115
                $token = $this->app->request->param('token');
116
                break;
117
            default:
118
                $token = $this->app->request->param('token');
119
                break;
120
        }
121
122
        if (!$token) {
123
            throw new JwtException('获取Token失败.', 500);
124
        }
125
126
        return $token;
127
    }
128
129
    /**
130
     * 验证 Token.
131
     *
132
     * @param string $token
133
     *
134
     * @return bool
135
     */
136
    public function verify(string $token = '')
137
    {
138
        // 自动获取请求token
139
        if ($token == '') {
140
            $token = $this->getRequestToken();
141
        }
142
143
        // 解析Token
144
        $this->token = $this->parse($token);
145
146
        try {
147
            $this->validateToken();
148 5
            // 是否已过期
149
            if ($this->token->isExpired()) {
150
                throw new TokenAlreadyEexpired('Token 已过期', 401, $this->getAlreadyCode());
151 5
            }
152
153
            // 单点登录
154
            if ($this->sso()) {
155
                $jwt_id = $this->token->getHeader('jti');
156 5
                // 当前Token签发时间
157
                $issued_at = $this->token->getClaim('iat');
158
                // 最新Token签发时间
159 5
                $cache_issued_at = $this->getCacheIssuedAt($jwt_id);
0 ignored issues
show
Bug introduced by
The method getCacheIssuedAt() does not exist on xiaodi\Jwt. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

159
                /** @scrutinizer ignore-call */ 
160
                $cache_issued_at = $this->getCacheIssuedAt($jwt_id);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
160
                if ($issued_at != $cache_issued_at) {
161 3
                    throw new HasLoggedException('已在其它终端登录,请重新登录', 401, $this->getHasLoggedCode());
162 1
                }
163
            }
164
        } catch (\BadMethodCallException $e) {
165
            throw new JWTException('此 Token 未进行签名', 500);
166 2
        }
167 1
168
        return true;
169 1
    }
170
171 1
    /**
172 1
     * 效验 Token
173 2
     *
174
     * @return void
175
     */
176 3
    protected function validateToken()
177
    {
178
        // 验证密钥是否与创建签名的密钥匹配
179
        if (false === $this->token->verify($this->getSigner(), $this->makeKey())) {
180 2
            throw new JWTException('此 Token 与 密钥不匹配', 500);
181
        }
182
183 5
        // 是否可用
184
        $exp = $this->token->getClaim('nbf');
185
        if (time() < $exp) {
186 5
            throw new JWTException('此 Token 暂未可用', 500);
187 1
        }
188
189
        if ($this->blacklist->has($this->token)) {
190
            throw new JWTException('此 Token 已注销', 500);
191 4
        }
192 4
    }
193 1
194
    /**
195 3
     * 获取 Token 对象.
196
     *
197
     * @return \Lcobucci\JWT\Token
198
     */
199
    public function getToken()
200
    {
201
        return $this->token;
202
    }
203
204
    /**
205 2
     * 刷新 Token.
206
     *
207 2
     * @param Token $token
208 2
     * @return Token
209
     */
210 2
    public function refresh(Token $token)
211 2
    {
212
        // 加入黑名单
213
        $this->blacklist->push($token);
214
215
        $claims = $token->getClaims();
216
217
        unset($claims['iat']);
218
        unset($claims['jti']);
219
        unset($claims['nbf']);
220 1
        unset($claims['exp']);
221
        unset($claims['iss']);
222 1
        unset($claims['aud']);
223
224
        return $this->token($claims);
225
    }
226
227
    /**
228
     * 删除 Token.
229
     *
230
     * @param Token $token
231
     * @return void
232
     */
233
    public function remove(Token $token)
234
    {
235
        $this->blacklist->push($token);
236
    }
237
238
    /**
239
     * 生成私钥.
240 1
     *
241
     * @return Key
242 1
     */
243
    private function makeKey()
244 1
    {
245 1
        $key = $this->getSignerKey();
246 1
        if (empty($key)) {
247 1
            throw new JWTException('私钥未配置.', 500);
248 1
        }
249 1
250
        return new Key($key);
251 1
    }
252
}
253