ApiAuthenticator::createToken()   B
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 28
ccs 17
cts 17
cp 1
rs 8.439
c 0
b 0
f 0
cc 6
eloc 16
nc 4
nop 2
crap 6
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