Completed
Pull Request — master (#8)
by Peter
04:00
created

AbstractTokenAuthenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 5
rs 9.4286
cc 1
eloc 3
nc 1
nop 2
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
     * @var TokenManager
26
     */
27
    protected $tokenManager;
28
29
    /**
30
     * @var UserCheckerInterface
31
     */
32
    protected $userChecker;
33
34
    /**
35
     * @param TokenManager         $tokenManager
36
     * @param UserCheckerInterface $userChecker
37
     */
38
    public function __construct(TokenManager $tokenManager, UserCheckerInterface $userChecker)
39
    {
40
        $this->tokenManager = $tokenManager;
41
        $this->userChecker = $userChecker;
42
    }
43
44
    /**
45
     * @inheritdoc
46
     */
47
    public function supportsToken(TokenInterface $token, $providerKey)
48
    {
49
        return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
50
    }
51
52
    /**
53
     * @inheritdoc
54
     */
55
    public function createToken(Request $request, $providerKey)
56
    {
57
        if (!$request->headers->has('X-Auth-Token')) {
58
            return null;
59
        }
60
61
        $authToken = (string) $request->headers->get('X-Auth-Token');
62
63
        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...
64
    }
65
66
    /**
67
     * @inheritdoc
68
     */
69
    public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
70
    {
71
        /* @var PreAuthenticatedToken $token */
72
        $authToken = $token->getToken();
73
        if (empty($authToken)) {
74
            $authToken = 'NONE_PROVIDED';
75
        }
76
77
        $tokenEntity = $this->tokenManager->findById($authToken);
78
        if (!$tokenEntity) {
79
            throw new BadCredentialsException('Bad token');
80
        }
81
82
        if (false === $this->tokenManager->isExpired($tokenEntity)) {
83
            throw new TokenExpiredException('Token expired');
84
        }
85
86
        $user = $this->retrieveUser($userProvider, $tokenEntity);
87
88
        if (!$user instanceof UserInterface) {
89
            throw new AuthenticationServiceException('retrieveUser() must return a UserInterface.');
90
        }
91
92
        try {
93
            $this->userChecker->checkPreAuth($user);
94
            $this->checkAuthentication($user, $tokenEntity, $token);
95
            $this->userChecker->checkPostAuth($user);
96
        } catch (BadCredentialsException $e) {
97
            throw new BadCredentialsException('Bad credentials', 0, $e);
98
        }
99
100
        $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...
101
        $authenticatedToken->setUser($user);
102
        $authenticatedToken->setAttributes($token->getAttributes());
103
104
        return $authenticatedToken;
105
    }
106
107
    /**
108
     * For correct http status codes see documentation at http://developer.openstack.org/api-ref-identity-v2.html.
109
     *
110
     * @inheritdoc
111
     */
112
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
113
    {
114
        $errorMessage = 'Authentication Failed';
115
        $responseCode = Response::HTTP_BAD_REQUEST;
116
117
        if ($exception instanceof TokenExpiredException) {
118
            $errorMessage = 'Token expired';
119
            $responseCode = Response::HTTP_UNAUTHORIZED;
120
        } elseif ($exception instanceof AccountStatusException) {
121
            $errorMessage = 'Account disabled';
122
            $responseCode = Response::HTTP_FORBIDDEN;
123
        }
124
125
        return new JsonResponse(['error' => $errorMessage], $responseCode);
126
    }
127
128
    /**
129
     * @param UserInterface         $user
130
     * @param Token                 $tokenEntity
131
     * @param PreAuthenticatedToken $token
132
     *
133
     * @throws BadCredentialsException
134
     */
135
    protected function checkAuthentication(UserInterface $user, Token $tokenEntity, PreAuthenticatedToken $token)
136
    {
137
        $currentUser = $token->getUser();
138
        if ($currentUser instanceof UserInterface) {
139
            if ($currentUser->getPassword() !== $user->getPassword()) {
140
                throw new BadCredentialsException('The credentials were changed from another session.');
141
            }
142
        } else {
143
            if ('' === ($presentedToken = $token->getToken())) {
144
                throw new BadCredentialsException('The presented token cannot be empty.');
145
            }
146
147
            list($class, $username, $expires, $hash) = $this->tokenManager->getEncoder()->decodeHash($tokenEntity->getHash());
148
149
            $username = base64_decode($username, true);
150
151
            $hash2 = $this->tokenManager->getEncoder()->generateHash($class, $username, $user->getPassword(), $expires);
152
            if (false === $this->tokenManager->getEncoder()->compareHashes($hash, $hash2)) {
153
                throw new BadCredentialsException('The presented token is invalid.');
154
            }
155
        }
156
    }
157
158
    /**
159
     * @param UserProviderInterface $userProvider
160
     * @param Token                 $token
161
     *
162
     * @throws AuthenticationException
163
     * @throws AuthenticationServiceException
164
     *
165
     * @return UserInterface
166
     */
167
    protected function retrieveUser(UserProviderInterface $userProvider, Token $token)
168
    {
169
        $parts = $this->tokenManager->getEncoder()->decodeHash($token->getHash());
170
171
        if (count($parts) !== 4) {
172
            throw new AuthenticationException('The hash is invalid.');
173
        }
174
175
        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...
176
177
        if (false === $username = base64_decode($username, true)) {
178
            throw new AuthenticationException('$username contains a character from outside the base64 alphabet.');
179
        }
180
181
        try {
182
            $user = $userProvider->loadUserByUsername($username);
183
        } catch (\Exception $e) {
184
            throw new AuthenticationServiceException($e->getMessage(), 0, $e);
185
        }
186
187
        if (!$user instanceof UserInterface) {
188
            throw new AuthenticationServiceException('The user provider must return a UserInterface object.');
189
        }
190
191
        return $user;
192
    }
193
}
194