Completed
Push — master ( a6f5e7...505dfd )
by John
02:13
created

Authenticator::authenticateToken()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 10
nc 5
nop 3
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
namespace KleijnWeb\JwtBundle\Authenticator;
9
10
use KleijnWeb\JwtBundle\User\JwtUserProvider;
11
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
12
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
13
use Symfony\Component\Security\Core\Exception\AuthenticationException;
14
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
15
use Symfony\Component\HttpFoundation\Request;
16
use KleijnWeb\JwtBundle\User\UnsafeGroupsUserInterface;
17
use Symfony\Component\Security\Core\User\UserProviderInterface;
18
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
19
20
/**
21
 * @author John Kleijn <[email protected]>
22
 */
23
class Authenticator implements SimplePreAuthenticatorInterface
24
{
25
    /**
26
     * @var JwtKey[]
27
     */
28
    private $keys = [];
29
30
    /**
31
     * @param JwtKey[] $keys
32
     */
33
    public function __construct(array $keys)
34
    {
35
        foreach ($keys as $key) {
36
            $this->keys[$key->getId()] = $key;
37
        }
38
    }
39
40
    /**
41
     * @param string|null $id
42
     *
43
     * @return JwtKey
44
     */
45
    public function getKeyById(string $id = null)
46
    {
47
        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...
48
            if (!isset($this->keys[$id])) {
49
                throw new AuthenticationException("Unknown 'kid' $id");
50
            }
51
52
            return $this->keys[$id];
53
        }
54
        if (count($this->keys) > 1) {
55
            throw new AuthenticationException("Missing 'kid'");
56
        }
57
58
        return current($this->keys);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The expression current($this->keys); of type KleijnWeb\JwtBundle\Authenticator\JwtKey|false adds false to the return on line 58 which is incompatible with the return type documented by KleijnWeb\JwtBundle\Auth...thenticator::getKeyById of type KleijnWeb\JwtBundle\Authenticator\JwtKey. It seems like you forgot to handle an error condition.
Loading history...
59
    }
60
61
    /**
62
     * @param Request $request
63
     * @param string  $providerKey
64
     *
65
     * @return PreAuthenticatedToken
66
     */
67
    public function createToken(Request $request, $providerKey)
68
    {
69
        $tokenString = $request->headers->get('Authorization');
70
71
        if (0 === strpos((string)$tokenString, 'Bearer ')) {
72
            $tokenString = substr($tokenString, 7);
73
        }
74
75
        if (!$tokenString) {
76
            throw new BadCredentialsException('No API key found');
77
        }
78
79
        try {
80
            $token = new JwtToken($tokenString);
81
            $key   = $this->getKeyById($token->getKeyId());
82
            $key->validateToken($token);
83
        } catch (\Exception $e) {
84
            throw new AuthenticationException('Invalid key', 0, $e);
85
        }
86
87
        return new PreAuthenticatedToken('anon.', $token, $providerKey);
88
    }
89
90
    /**
91
     * @param TokenInterface        $token
92
     * @param UserProviderInterface $userProvider
93
     * @param string                $providerKey
94
     *
95
     * @return PreAuthenticatedToken
96
     */
97
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
98
    {
99
        /** @var $jwtToken JwtToken */
100
        if (!($jwtToken = $token->getCredentials()) instanceof JwtToken) {
101
            throw new \UnexpectedValueException("Expected credentials to be a JwtToken object");
102
        }
103
104
        $username = $jwtToken->getSubject();
105
106
        if ($userProvider instanceof JwtUserProvider) {
107
            // Not ideal, sequential coupling
108
            $userProvider->setClaimsUsingToken($jwtToken);
109
        }
110
111
        $user = $userProvider->loadUserByUsername($username);
0 ignored issues
show
Bug introduced by
It seems like $username defined by $jwtToken->getSubject() on line 104 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...
112
113
        if (!$userProvider instanceof JwtUserProvider && $user instanceof UnsafeGroupsUserInterface) {
114
            $user = $this->setUserRolesFromAudienceClaims($user, $jwtToken->getClaims());
115
        }
116
117
        return new PreAuthenticatedToken($user, $token, $providerKey, $user->getRoles());
118
    }
119
120
    /**
121
     * @param TokenInterface $token
122
     * @param string         $providerKey
123
     *
124
     * @return bool
125
     */
126
    public function supportsToken(TokenInterface $token, $providerKey)
127
    {
128
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
129
    }
130
131
    /**
132
     * @param UnsafeGroupsUserInterface $user
133
     * @param array                     $claims
134
     *
135
     * @return UnsafeGroupsUserInterface
136
     */
137
    private function setUserRolesFromAudienceClaims(UnsafeGroupsUserInterface $user, array $claims)
138
    {
139
        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...
140
            $user->addRole($role);
141
        }
142
143
        return $user;
144
    }
145
}
146