Completed
Pull Request — master (#10)
by Jan
06:46 queued 04:52
created

AbstractTokenAuthenticator::authenticateToken()   C

Complexity

Conditions 7
Paths 16

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7.6773

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 41
ccs 19
cts 25
cp 0.76
rs 6.7272
c 1
b 0
f 0
cc 7
eloc 24
nc 16
nop 3
crap 7.6773
1
<?php
2
3
namespace TreeHouse\KeystoneBundle\Security\Authentication;
4
5
use Symfony\Component\HttpFoundation\JsonResponse;
6
use Symfony\Component\HttpFoundation\Request;
7
use Symfony\Component\HttpFoundation\Response;
8
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
9
use Symfony\Component\Security\Core\Exception\AccountStatusException;
10
use Symfony\Component\Security\Core\Exception\AuthenticationException;
11
use Symfony\Component\Security\Core\Exception\AuthenticationServiceException;
12
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
13
use Symfony\Component\Security\Core\User\UserCheckerInterface;
14
use Symfony\Component\Security\Core\User\UserInterface;
15
use Symfony\Component\Security\Core\User\UserProviderInterface;
16
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
17
use TreeHouse\KeystoneBundle\Exception\TokenExpiredException;
18
use TreeHouse\KeystoneBundle\Manager\TokenManager;
19
use TreeHouse\KeystoneBundle\Model\Token;
20
use TreeHouse\KeystoneBundle\Security\Authentication\Token\PreAuthenticatedToken;
21
22
abstract class AbstractTokenAuthenticator implements AuthenticationFailureHandlerInterface
23
{
24
    /**
25
     * Whether or not expired tokens should be automatically removed.
26
     *
27
     * @var bool
28
     */
29
    protected $autoRemoveExpiredToken = false;
30
31
    /**
32
     * @var TokenManager
33
     */
34
    protected $tokenManager;
35
36
    /**
37
     * @var UserCheckerInterface
38
     */
39
    protected $userChecker;
40
41
    /**
42
     * @param TokenManager         $tokenManager
43
     * @param UserCheckerInterface $userChecker
44
     */
45 18
    public function __construct(TokenManager $tokenManager, UserCheckerInterface $userChecker)
46
    {
47 18
        $this->tokenManager = $tokenManager;
48 18
        $this->userChecker = $userChecker;
49 18
    }
50
51
    /**
52
     * @inheritdoc
53
     */
54 10
    public function supportsToken(TokenInterface $token, $providerKey)
55
    {
56 10
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
57
    }
58
59
    /**
60
     * @inheritdoc
61
     */
62 8
    public function createToken(Request $request, $providerKey)
63
    {
64 8
        if (!$request->headers->has('X-Auth-Token')) {
65 2
            return null;
66
        }
67
68 6
        $authToken = (string) $request->headers->get('X-Auth-Token');
69
70 6
        return new PreAuthenticatedToken($authToken, $providerKey);
0 ignored issues
show
Documentation introduced by
$authToken is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
71
    }
72
73
    /**
74
     * @inheritdoc
75
     */
76 6
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
77
    {
78
        /* @var PreAuthenticatedToken $token */
79 6
        $authToken = $token->getToken();
80 6
        if (empty($authToken)) {
81
            $authToken = 'NONE_PROVIDED';
82
        }
83
84 6
        $tokenEntity = $this->tokenManager->findById($authToken);
85 6
        if (!$tokenEntity) {
86 2
            throw new BadCredentialsException('Bad token');
87
        }
88
89 4
        if (true === $this->tokenManager->isExpired($tokenEntity)) {
90 2
            if (true === $this->autoRemoveExpiredToken) {
91
                $this->tokenManager->removeToken($tokenEntity);
92
            }
93
94 2
            throw new TokenExpiredException('Token expired');
95
        }
96
97 2
        $user = $this->retrieveUser($userProvider, $tokenEntity);
98
99 2
        if (!$user instanceof UserInterface) {
100
            throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
101
        }
102
103
        try {
104 2
            $this->userChecker->checkPreAuth($user);
105 2
            $this->checkAuthentication($user, $tokenEntity, $token);
106 2
            $this->userChecker->checkPostAuth($user);
107 1
        } catch (BadCredentialsException $e) {
108
            throw new BadCredentialsException('Bad credentials', 0, $e);
109
        }
110
111 2
        $authenticatedToken = new PreAuthenticatedToken($token->getToken(), $providerKey, $user->getRoles());
0 ignored issues
show
Documentation introduced by
$token->getToken() is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
112 2
        $authenticatedToken->setUser($user);
113 2
        $authenticatedToken->setAttributes($token->getAttributes());
114
115 2
        return $authenticatedToken;
116
    }
117
118
    /**
119
     * For correct http status codes see documentation at http://developer.openstack.org/api-ref-identity-v2.html.
120
     *
121
     * @inheritdoc
122
     */
123 4
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
124
    {
125 4
        $errorMessage = 'Authentication Failed';
126 4
        $responseCode = Response::HTTP_BAD_REQUEST;
127
128 4
        if ($exception instanceof TokenExpiredException) {
129 2
            $errorMessage = 'Token expired';
130 2
            $responseCode = Response::HTTP_UNAUTHORIZED;
131 3
        } elseif ($exception instanceof AccountStatusException) {
132
            $errorMessage = 'Account disabled';
133
            $responseCode = Response::HTTP_FORBIDDEN;
134
        }
135
136 4
        return new JsonResponse(['error' => $errorMessage], $responseCode);
137
    }
138
139
    /**
140
     * @param UserInterface         $user
141
     * @param Token                 $tokenEntity
142
     * @param PreAuthenticatedToken $token
143
     *
144
     * @throws BadCredentialsException
145
     */
146 2
    protected function checkAuthentication(UserInterface $user, Token $tokenEntity, PreAuthenticatedToken $token)
147
    {
148 2
        $currentUser = $token->getUser();
149 2
        if ($currentUser instanceof UserInterface) {
150
            if ($currentUser->getPassword() !== $user->getPassword()) {
151
                throw new BadCredentialsException('The credentials were changed from another session.');
152
            }
153
        } else {
154 2
            if ('' === ($presentedToken = $token->getToken())) {
155
                throw new BadCredentialsException('The presented token cannot be empty.');
156
            }
157
158 2
            list($class, $username, $expires, $hash) = $this->tokenManager->getEncoder()->decodeHash($tokenEntity->getHash());
159
160 2
            $username = base64_decode($username, true);
161
162 2
            $hash2 = $this->tokenManager->getEncoder()->generateHash($class, $username, $user->getPassword(), $expires);
163 2
            if (false === $this->tokenManager->getEncoder()->compareHashes($hash, $hash2)) {
164
                throw new BadCredentialsException('The presented token is invalid.');
165
            }
166
        }
167 2
    }
168
169
    /**
170
     * @param UserProviderInterface $userProvider
171
     * @param Token                 $token
172
     *
173
     * @throws AuthenticationException
174
     * @throws AuthenticationServiceException
175
     *
176
     * @return UserInterface
177
     */
178 2
    protected function retrieveUser(UserProviderInterface $userProvider, Token $token)
179
    {
180 2
        $parts = $this->tokenManager->getEncoder()->decodeHash($token->getHash());
181
182 2
        if (count($parts) !== 4) {
183
            throw new AuthenticationException('The hash is invalid.');
184
        }
185
186 2
        list($class, $username, $expires, $hash) = $parts;
0 ignored issues
show
Unused Code introduced by
The assignment to $class is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $expires is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $hash is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
187
188 2
        if (false === $username = base64_decode($username, true)) {
189
            throw new AuthenticationException('$username contains a character from outside the base64 alphabet.');
190
        }
191
192
        try {
193 2
            $user = $userProvider->loadUserByUsername($username);
194 1
        } catch (\Exception $e) {
195
            throw new AuthenticationServiceException($e->getMessage(), 0, $e);
196
        }
197
198 2
        if (!$user instanceof UserInterface) {
199
            throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
200
        }
201
202 2
        return $user;
203
    }
204
}
205