ApiAuthenticator   A
last analyzed

Complexity

Total Complexity 15

Size/Duplication

Total Lines 93
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 15
lcom 1
cbo 8
dl 0
loc 93
ccs 44
cts 44
cp 1
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
B createToken() 0 28 6
B authenticateToken() 0 31 5
A supportsToken() 0 4 2
A onAuthenticationFailure() 0 4 1
1
<?php
2
3
namespace Overwatch\UserBundle\Security;
4
5
use Doctrine\ORM\EntityManager;
6
use Symfony\Component\HttpFoundation\JsonResponse;
7
use Symfony\Component\HttpFoundation\Request;
8
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
9
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
10
use Symfony\Component\Security\Core\Exception\AuthenticationException;
11
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
12
use Symfony\Component\Security\Core\User\UserProviderInterface;
13
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
14
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
15
16
/**
17
 * ApiAuthenticator
18
 *
19
 * @author Zac Sturgess <[email protected]>
20
 */
21
class ApiAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
22
{
23
    const USER_ID = 'x-api-user';
24
    const TIMESTAMP = 'x-api-timestamp';
25
    const TOKEN = 'x-api-token';
26
    
27
    private $em;
28
    
29 148
    public function __construct(EntityManager $em)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
30
    {
31 148
        $this->em = $em;
32 148
    }
33
    
34
    /**
35
     * @param string $providerKey
36
     */
37 5
    public function createToken(Request $request, $providerKey)
38
    {
39
        if (
40 5
            !$request->headers->has(self::USER_ID) ||
41 3
            !$request->headers->has(self::TIMESTAMP) ||
42 3
            !$request->headers->has(self::TOKEN)
43 5
        ) {
44 2
            throw new BadCredentialsException('API credentials invalid. The user ID, timestamp and token should given.');
45
        }
46
        
47 3
        if (!is_numeric($request->headers->get(self::USER_ID))) {
48 1
            throw new BadCredentialsException('API credentials invalid. The user ID should be an integer.');
49
        }
50
        
51 2
        if (!is_numeric($request->headers->get(self::TIMESTAMP))) {
52 1
            throw new BadCredentialsException('API credentials invalid. The timestamp should be an integer.');
53
        }
54
        
55 1
        return new PreAuthenticatedToken(
56 1
            'anon.',
57
            [
58 1
                self::USER_ID   => $request->headers->get(self::USER_ID),
59 1
                self::TIMESTAMP => $request->headers->get(self::TIMESTAMP),
60 1
                self::TOKEN     => $request->headers->get(self::TOKEN),
61 1
            ],
62
            $providerKey
63 1
        );
64
    }
65
    
66
    /**
67
     * @param string $providerKey
68
     */
69 6
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
70
    {
71 6
        $credentials = $token->getCredentials();
72
        
73 6
        if (abs(time() - (int) $credentials[self::TIMESTAMP]) > 60) {
74 2
            throw new AuthenticationException('API credentials invalid. The timestamp is more than 60 seconds old.');
75
        }
76
        
77 4
        $user = $this->em->find('OverwatchUserBundle:User', $credentials[self::USER_ID]);
78
        
79 4
        if ($user === null || $user->isLocked()) {
80 2
            throw new AuthenticationException('API credentials invalid. User not found.');
81
        }
82
        
83 2
        $apiToken = hash_hmac(
84 2
            'sha256',
85 2
            'timestamp=' . $credentials[self::TIMESTAMP],
86 2
            $user->getApiKey()
87 2
        );
88
        
89 2
        if ($apiToken !== $credentials[self::TOKEN]) {
90 1
            throw new AuthenticationException('API credentials invalid. Token verification failed.');
91
        }
92
        
93 1
        return new PreAuthenticatedToken(
94 1
            $user,
95 1
            $token->getCredentials(),
96 1
            $providerKey,
97 1
            $user->getRoles()
98 1
        );
99
    }
100
    
101
    /**
102
     * @param string $providerKey
103
     */
104 3
    public function supportsToken(TokenInterface $token, $providerKey)
105
    {
106 3
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
107
    }
108
    
109 2
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
110
    {
111 2
        return new JsonResponse($exception->getMessage(), JsonResponse::HTTP_UNAUTHORIZED);
112
    }
113
}
114