|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* auth interface for authing against an airlock key of some sorts |
|
4
|
|
|
*/ |
|
5
|
|
|
|
|
6
|
|
|
namespace Graviton\SecurityBundle\Authentication; |
|
7
|
|
|
|
|
8
|
|
|
use Graviton\SecurityBundle\Authentication\Strategies\StrategyInterface; |
|
9
|
|
|
use Graviton\SecurityBundle\Authentication\Provider\AuthenticationProvider; |
|
10
|
|
|
use Graviton\SecurityBundle\Entities\AnonymousUser; |
|
11
|
|
|
use Graviton\SecurityBundle\Entities\SecurityUser; |
|
12
|
|
|
use Psr\Log\LoggerInterface as Logger; |
|
13
|
|
|
use Symfony\Component\HttpFoundation\Request; |
|
14
|
|
|
use Symfony\Component\HttpFoundation\Response; |
|
15
|
|
|
use Symfony\Component\Security\Core\Authentication\SimplePreAuthenticatorInterface as SimplePreAuthInterface; |
|
16
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; |
|
17
|
|
|
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; |
|
18
|
|
|
use Symfony\Component\Security\Core\Exception\AuthenticationException; |
|
19
|
|
|
use Symfony\Component\Security\Core\User\UserProviderInterface; |
|
20
|
|
|
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; |
|
21
|
|
|
|
|
22
|
|
|
/** |
|
23
|
|
|
* @author List of contributors <https://github.com/libgraviton/graviton/graphs/contributors> |
|
24
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GNU Public License |
|
25
|
|
|
* @link http://swisscom.ch |
|
26
|
|
|
*/ |
|
27
|
|
|
final class SecurityAuthenticator implements |
|
28
|
|
|
SimplePreAuthInterface, |
|
29
|
|
|
AuthenticationFailureHandlerInterface |
|
30
|
|
|
{ |
|
31
|
|
|
|
|
32
|
|
|
/** |
|
33
|
|
|
* Authentication can be required to use any service |
|
34
|
|
|
* @var bool, |
|
35
|
|
|
*/ |
|
36
|
|
|
protected $securityRequired; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* Authentication can use a test user if no user found |
|
40
|
|
|
* @var bool, |
|
41
|
|
|
*/ |
|
42
|
|
|
protected $securityTestUsername; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* Authentication can allow not identified users to get information |
|
46
|
|
|
* @var bool, |
|
47
|
|
|
*/ |
|
48
|
|
|
protected $allowAnonymous; |
|
49
|
|
|
|
|
50
|
|
|
/** |
|
51
|
|
|
* @var AuthenticationProvider |
|
52
|
|
|
*/ |
|
53
|
|
|
protected $userProvider; |
|
54
|
|
|
|
|
55
|
|
|
/** |
|
56
|
|
|
* @var StrategyInterface |
|
57
|
|
|
*/ |
|
58
|
|
|
protected $extractionStrategy; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* @var Logger |
|
62
|
|
|
*/ |
|
63
|
|
|
protected $logger; |
|
64
|
|
|
|
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* @param boolean $securityRequired user provider to use |
|
68
|
|
|
* @param string $securityTestUsername user for testing |
|
69
|
|
|
* @param boolean $allowAnonymous user provider to use |
|
70
|
|
|
* @param AuthenticationProvider $userProvider user provider to use |
|
71
|
|
|
* @param StrategyInterface $extractionStrategy auth strategy to use |
|
72
|
|
|
* @param Logger $logger logger to user for logging errors |
|
73
|
|
|
*/ |
|
74
|
15 |
|
public function __construct( |
|
75
|
|
|
$securityRequired, |
|
76
|
|
|
$securityTestUsername, |
|
77
|
|
|
$allowAnonymous, |
|
78
|
|
|
AuthenticationProvider $userProvider, |
|
79
|
|
|
StrategyInterface $extractionStrategy, |
|
80
|
|
|
Logger $logger |
|
81
|
|
|
) { |
|
82
|
|
|
|
|
83
|
15 |
|
$this->securityRequired = $securityRequired; |
|
84
|
15 |
|
$this->securityTestUsername = $securityTestUsername; |
|
85
|
15 |
|
$this->allowAnonymous = $allowAnonymous; |
|
86
|
15 |
|
$this->userProvider = $userProvider; |
|
87
|
15 |
|
$this->extractionStrategy = $extractionStrategy; |
|
88
|
|
|
|
|
89
|
15 |
|
$this->logger = $logger; |
|
90
|
15 |
|
} |
|
91
|
|
|
|
|
92
|
|
|
/** |
|
93
|
|
|
* @param Request $request request to authenticate |
|
94
|
|
|
* @param string $providerKey provider key to auth with |
|
95
|
|
|
* |
|
96
|
|
|
* @return \Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken |
|
97
|
|
|
*/ |
|
98
|
7 |
|
public function createToken(Request $request, $providerKey) |
|
99
|
|
|
{ |
|
100
|
|
|
// look for an apikey query parameter |
|
101
|
7 |
|
$apiKey = $this->extractionStrategy->apply($request); |
|
102
|
|
|
|
|
103
|
7 |
|
return new PreAuthenticatedToken( |
|
104
|
7 |
|
'anon.', |
|
105
|
7 |
|
$apiKey, |
|
106
|
|
|
$providerKey |
|
107
|
7 |
|
); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* Tries to authenticate the provided token |
|
112
|
|
|
* |
|
113
|
|
|
* @param TokenInterface $token token to authenticate |
|
114
|
|
|
* @param UserProviderInterface $userProvider provider to auth against |
|
115
|
|
|
* @param string $providerKey key to auth with |
|
116
|
|
|
* |
|
117
|
|
|
* @return \Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken |
|
118
|
|
|
*/ |
|
119
|
4 |
|
public function authenticateToken( |
|
120
|
|
|
TokenInterface $token, |
|
121
|
|
|
UserProviderInterface $userProvider, |
|
122
|
|
|
$providerKey) |
|
123
|
|
|
{ |
|
124
|
4 |
|
$username = $token->getCredentials(); |
|
125
|
|
|
|
|
126
|
|
|
// If no username in Strategy, check if required. |
|
127
|
4 |
View Code Duplication |
if ( $this->securityRequired && !$username ) { |
|
|
|
|
|
|
128
|
|
|
$this->logger->warning('Authentication key is required.'); |
|
129
|
|
|
throw new AuthenticationException( |
|
130
|
|
|
sprintf('Authentication key is required.') |
|
131
|
|
|
); |
|
132
|
|
|
} |
|
133
|
|
|
|
|
134
|
|
|
/** @var SecurityUser $securityUser */ |
|
135
|
4 |
|
$securityUser = $this->userProvider->loadUserByUsername($username); |
|
136
|
|
|
|
|
137
|
4 |
|
if ( !$securityUser && $this->securityTestUsername ) { |
|
138
|
|
|
$this->logger->info('Authentication, loading test user: '.$this->securityTestUsername); |
|
139
|
|
|
$securityUser = $this->userProvider->loadUserByUsername($this->securityTestUsername); |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
4 |
|
if (!$securityUser && $this->allowAnonymous) { |
|
143
|
2 |
|
$this->logger->info('Authentication, loading anonymous user.'); |
|
144
|
2 |
|
$securityUser = new SecurityUser(new AnonymousUser(), ['ROLE_GRAVITON_ANONYMOUS'] ); |
|
|
|
|
|
|
145
|
2 |
|
} |
|
146
|
|
|
|
|
147
|
4 |
View Code Duplication |
if (!$securityUser) { |
|
|
|
|
|
|
148
|
1 |
|
$this->logger->warning(sprintf('Authentication key "%s" could not be resolved.', $username)); |
|
149
|
1 |
|
throw new AuthenticationException( |
|
150
|
1 |
|
sprintf('Authentication key "%s" could not be resolved.', $username) |
|
151
|
1 |
|
); |
|
152
|
|
|
} |
|
153
|
|
|
|
|
154
|
3 |
|
return new PreAuthenticatedToken( |
|
155
|
3 |
|
$securityUser, $username, |
|
156
|
3 |
|
$providerKey, |
|
157
|
3 |
|
$securityUser->getRoles() |
|
158
|
3 |
|
); |
|
159
|
|
|
|
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
/** |
|
163
|
|
|
* @param TokenInterface $token token to check |
|
164
|
|
|
* @param string $providerKey provider to check against |
|
165
|
|
|
* |
|
166
|
|
|
* @return bool |
|
167
|
|
|
*/ |
|
168
|
3 |
|
public function supportsToken(TokenInterface $token, $providerKey) |
|
169
|
|
|
{ |
|
170
|
3 |
|
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey; |
|
171
|
|
|
} |
|
172
|
|
|
|
|
173
|
|
|
/** |
|
174
|
|
|
* This is called when an interactive authentication attempt fails. This is |
|
175
|
|
|
* called by authentication listeners inheriting from |
|
176
|
|
|
* AbstractAuthenticationListener. |
|
177
|
|
|
* |
|
178
|
|
|
* @param Request $request original request |
|
179
|
|
|
* @param AuthenticationException $exception exception from auth attempt |
|
180
|
|
|
* |
|
181
|
|
|
* @return Response|null |
|
182
|
|
|
*/ |
|
183
|
1 |
|
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) |
|
184
|
|
|
{ |
|
185
|
1 |
|
return new Response( |
|
186
|
1 |
|
$exception->getMessageKey(), |
|
187
|
|
|
Response::HTTP_NETWORK_AUTHENTICATION_REQUIRED |
|
188
|
1 |
|
); |
|
189
|
|
|
} |
|
190
|
|
|
} |
|
191
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.