Completed
Push — master ( ff92f6...3ef1d3 )
by John
09:08
created

JwtAuthenticationProvider::getKeyById()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 1
1
<?php declare(strict_types = 1);
2
/*
3
 * This file is part of the KleijnWeb\JwtBundle package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace KleijnWeb\JwtBundle\Authentication;
10
11
use KleijnWeb\JwtBundle\Jwt\JwtKey;
12
use KleijnWeb\JwtBundle\Jwt\JwtToken;
13
use KleijnWeb\JwtBundle\User\JwtUserProvider;
14
use KleijnWeb\JwtBundle\User\UnsafeGroupsUserInterface;
15
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
16
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17
use Symfony\Component\Security\Core\Exception\AuthenticationException;
18
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
19
use Symfony\Component\Security\Core\User\UserProviderInterface;
20
21
/**
22
 * @author John Kleijn <[email protected]>
23
 */
24
class JwtAuthenticationProvider implements AuthenticationProviderInterface
25
{
26
    /**
27
     * @var UserProviderInterface
28
     */
29
    private $userProvider;
30
31
    /**
32
     * @var JwtKey[]
33
     */
34
    private $keys = [];
35
36
    /**
37
     * @param UserProviderInterface $userProvider
38
     * @param JwtKey[]              $keys
39
     */
40
    public function __construct(UserProviderInterface $userProvider, array $keys)
41
    {
42
        foreach ($keys as $key) {
43
            $this->keys[$key->getId()] = $key;
44
        }
45
        $this->userProvider = $userProvider;
46
    }
47
48
    /**
49
     * @param string|null $id
50
     *
51
     * @return JwtKey
52
     */
53
    public function getKeyById(string $id = null)
54
    {
55
        if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
56
            if (!isset($this->keys[$id])) {
57
                throw new AuthenticationException("Unknown 'kid' $id");
58
            }
59
60
            return $this->keys[$id];
61
        }
62
        if (count($this->keys) > 1) {
63
            throw new AuthenticationException("Missing 'kid'");
64
        }
65
66
        return current($this->keys);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression current($this->keys); of type KleijnWeb\JwtBundle\Jwt\JwtKey|false adds false to the return on line 66 which is incompatible with the return type documented by KleijnWeb\JwtBundle\Auth...ionProvider::getKeyById of type KleijnWeb\JwtBundle\Jwt\JwtKey. It seems like you forgot to handle an error condition.
Loading history...
67
    }
68
69
    /**
70
     * @param TokenInterface $token
71
     * @return JwtAuthenticatedToken
72
     */
73
    public function authenticate(TokenInterface $token)
74
    {
75
        if (!$this->supports($token)) {
76
            throw new \LogicException(
77
                sprintf("Token of type '%s' not supported by '%s'", get_class($token), get_class($this))
78
            );
79
        }
80
        try {
81
            $jwtToken = new JwtToken($token->getCredentials());
82
            $key      = $this->getKeyById($jwtToken->getKeyId());
83
            $key->validateToken($jwtToken);
84
        } catch (\Exception $e) {
85
            throw new BadCredentialsException('Invalid JWT token', 0, $e);
86
        }
87
88
        $username = $jwtToken->getSubject();
89
90
        if ($this->userProvider instanceof JwtUserProvider) {
91
            // Not ideal, sequential coupling
92
            $this->userProvider->setClaimsUsingToken($jwtToken);
93
        }
94
95
        $user = $this->userProvider->loadUserByUsername($username);
0 ignored issues
show
Bug introduced by
It seems like $username defined by $jwtToken->getSubject() on line 88 can also be of type array; however, Symfony\Component\Securi...e::loadUserByUsername() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
96
97
        if (!$this->userProvider instanceof JwtUserProvider && $user instanceof UnsafeGroupsUserInterface) {
98
            $this->setUserRolesFromAudienceClaims($user, $jwtToken->getClaims());
99
        }
100
101
        return new JwtAuthenticatedToken($user);
102
    }
103
104
    public function supports(TokenInterface $token)
105
    {
106
        return $token instanceof JwtAuthenticationToken;
107
    }
108
109
    /**
110
     * @param UnsafeGroupsUserInterface $user
111
     * @param array                     $claims
112
     */
113
    private function setUserRolesFromAudienceClaims(UnsafeGroupsUserInterface $user, array $claims)
114
    {
115
        foreach (JwtUserProvider::getRolesFromAudienceClaims($claims) as $role) {
0 ignored issues
show
Deprecated Code introduced by
The method KleijnWeb\JwtBundle\User...lesFromAudienceClaims() has been deprecated.

This method has been deprecated.

Loading history...
116
            $user->addRole($role);
117
        }
118
    }
119
}
120
121