Skip to content

Commit 68851da

Browse files
committed
Add passport credentials and authenticator
1 parent b669962 commit 68851da

File tree

7 files changed

+694
-13
lines changed

7 files changed

+694
-13
lines changed

DependencyInjection/Security/Factory/OAuthFactory.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ class OAuthFactory implements AuthenticatorFactoryInterface, SecurityFactoryInte
3232
*/
3333
public function createAuthenticator(ContainerBuilder $container, string $id, array $config, string $userProviderId)
3434
{
35-
$providerId = 'fos_oauth_server.security.authentication.provider.'.$id;
35+
$providerId = 'fos_oauth_server.security.authentication.authenticator.'.$id;
3636
$container
37-
->setDefinition($providerId, new ChildDefinition('fos_oauth_server.security.authentication.provider'))
38-
->replaceArgument(0, new Reference($userProviderId))
39-
->replaceArgument(1, new Reference('security.user_checker.'.$id))
40-
->replaceArgument(2, $id)
37+
->setDefinition($providerId, new ChildDefinition('fos_oauth_server.security.authentication.authenticator'))
38+
->replaceArgument(0, new Reference('fos_oauth_server.server'))
39+
->replaceArgument(1, new Reference('security.token_storage'))
40+
->replaceArgument(2, new Reference('security.user_checker.'.$id))
4141
;
4242

4343
return $providerId;

Resources/config/security.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,20 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<parameters>
8+
<parameter key="fos_oauth_server.security.authentication.authenticator.class">FOS\OAuthServerBundle\Security\Authentication\Authenticator\OAuthAuthenticator</parameter>
89
<parameter key="fos_oauth_server.security.authentication.provider.class">FOS\OAuthServerBundle\Security\Authentication\Provider\OAuthProvider</parameter>
910
<parameter key="fos_oauth_server.security.authentication.listener.class">FOS\OAuthServerBundle\Security\Firewall\OAuthListener</parameter>
1011
<parameter key="fos_oauth_server.security.entry_point.class">FOS\OAuthServerBundle\Security\EntryPoint\OAuthEntryPoint</parameter>
1112
</parameters>
1213

1314
<services>
15+
<service id="fos_oauth_server.security.authentication.authenticator" class="%fos_oauth_server.security.authentication.authenticator.class%" public="false">
16+
<argument type="service" id="fos_oauth_server.server" />
17+
<argument type="service" id="security.token_storage"/>
18+
<argument type="service" id="security.user_checker" />
19+
<argument /> <!-- user provider -->
20+
</service>
21+
1422
<service id="fos_oauth_server.security.authentication.provider" class="%fos_oauth_server.security.authentication.provider.class%" public="false">
1523
<argument /> <!-- user provider -->
1624
<argument type="service" id="fos_oauth_server.server" />
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the FOSOAuthServerBundle package.
7+
*
8+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace FOS\OAuthServerBundle\Security\Authentication\Authenticator;
15+
16+
use FOS\OAuthServerBundle\Security\Authentication\Passport\OAuthCredentials;
17+
use FOS\OAuthServerBundle\Security\Authentication\Token\OAuthToken;
18+
use OAuth2\OAuth2;
19+
use OAuth2\OAuth2AuthenticateException;
20+
use OAuth2\OAuth2ServerException;
21+
use Symfony\Component\HttpFoundation\Request;
22+
use Symfony\Component\HttpFoundation\Response;
23+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
24+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
25+
use Symfony\Component\Security\Core\Exception\AccountStatusException;
26+
use Symfony\Component\Security\Core\Exception\AuthenticationException;
27+
use Symfony\Component\Security\Core\User\UserCheckerInterface;
28+
use Symfony\Component\Security\Core\User\UserProviderInterface;
29+
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
30+
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
31+
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
32+
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
33+
use Symfony\Component\Security\Http\Authenticator\Passport\UserPassportInterface;
34+
35+
/**
36+
* OAuthAuthenticator class.
37+
*
38+
* @author Israel J. Carberry <[email protected]>
39+
*/
40+
class OAuthAuthenticator implements AuthenticatorInterface
41+
{
42+
/**
43+
* @var OAuth2
44+
*/
45+
protected $serverService;
46+
47+
/**
48+
* @var TokenStorageInterface
49+
*/
50+
private $tokenStorage;
51+
52+
/**
53+
* @var UserCheckerInterface
54+
*/
55+
protected $userChecker;
56+
57+
/**
58+
* @var UserProviderInterface
59+
*/
60+
protected $userProvider;
61+
62+
public function __construct(
63+
OAuth2 $serverService,
64+
TokenStorageInterface $tokenStorage,
65+
UserCheckerInterface $userChecker,
66+
UserProviderInterface $userProvider
67+
) {
68+
$this->serverService = $serverService;
69+
$this->tokenStorage = $tokenStorage;
70+
$this->userChecker = $userChecker;
71+
$this->userProvider = $userProvider;
72+
}
73+
74+
/**
75+
* {@inheritdoc}
76+
*/
77+
public function authenticate(Request $request): UserPassportInterface
78+
{
79+
try {
80+
$token = $this->tokenStorage->getToken();
81+
$tokenString = $token->getToken();
82+
83+
$accessToken = $this->serverService->verifyAccessToken($tokenString);
84+
85+
/** @var \Symfony\Component\Security\Core\User\UserInterface **/
86+
$user = $accessToken->getUser();
87+
88+
if (null === $user) {
89+
throw new AuthenticationException('OAuth2 authentication failed');
90+
}
91+
92+
// check the user
93+
try {
94+
$this->userChecker->checkPreAuth($user);
95+
} catch (AccountStatusException $e) {
96+
throw new OAuth2AuthenticateException(
97+
Response::HTTP_UNAUTHORIZED,
98+
OAuth2::TOKEN_TYPE_BEARER,
99+
$this->serverService->getVariable(OAuth2::CONFIG_WWW_REALM),
100+
'access_denied',
101+
$e->getMessage()
102+
);
103+
}
104+
105+
return new Passport(
106+
new UserBadge($user->getUsername()),
107+
new OAuthCredentials($tokenString, $accessToken->getScope())
108+
);
109+
} catch (OAuth2ServerException $e) {
110+
throw new AuthenticationException('OAuth2 authentication failed', 0, $e);
111+
}
112+
113+
// this should never be reached
114+
throw new AuthenticationException('OAuth2 authentication failed');
115+
}
116+
117+
/**
118+
* {@inheritdoc}
119+
*/
120+
public function createAuthenticatedToken(PassportInterface $passport, string $firewallName): TokenInterface
121+
{
122+
try {
123+
// expect the badges in the passport from authenticate method above
124+
if (!$passport->hasBadge(OAuthCredentials::class)
125+
|| !$passport->hasBadge(UserBadge::class)
126+
) {
127+
throw new OAuth2AuthenticateException(
128+
Response::HTTP_UNAUTHORIZED,
129+
OAuth2::TOKEN_TYPE_BEARER,
130+
$this->serverService->getVariable(OAuth2::CONFIG_WWW_REALM),
131+
'access_denied',
132+
'Unexpected credentials type.'
133+
);
134+
}
135+
136+
// get the passport badges
137+
$credentials = $passport->getBadge(OAuthCredentials::class);
138+
$user = $this->userProvider->loadUserByUsername(
139+
$passport->getBadge(UserBadge::class)->getUserIdentifier()
140+
);
141+
142+
// check the user
143+
try {
144+
$this->userChecker->checkPostAuth($user);
145+
} catch (AccountStatusException $e) {
146+
throw new OAuth2AuthenticateException(
147+
Response::HTTP_UNAUTHORIZED,
148+
OAuth2::TOKEN_TYPE_BEARER,
149+
$this->serverService->getVariable(OAuth2::CONFIG_WWW_REALM),
150+
'access_denied',
151+
$e->getMessage()
152+
);
153+
}
154+
} catch (OAuth2ServerException $e) {
155+
throw new AuthenticationException('OAuth2 authentication failed', 0, $e);
156+
}
157+
158+
$token = new OAuthToken($credentials->getRoles($user));
159+
$token->setAuthenticated(true);
160+
$token->setToken($credentials->getTokenString());
161+
$token->setUser($user);
162+
163+
$credentials->markResolved();
164+
165+
return $token;
166+
}
167+
168+
/**
169+
* {@inheritdoc}
170+
*/
171+
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
172+
{
173+
return null;
174+
}
175+
176+
/**
177+
* {@inheritdoc}
178+
*/
179+
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
180+
{
181+
$responseException = new OAuth2AuthenticateException(
182+
Response::HTTP_UNAUTHORIZED,
183+
OAuth2::TOKEN_TYPE_BEARER,
184+
$this->serverService->getVariable(OAuth2::CONFIG_WWW_REALM),
185+
'access_denied',
186+
$exception->getMessage()
187+
);
188+
189+
return $responseException->getHttpResponse();
190+
}
191+
192+
/**
193+
* {@inheritdoc}
194+
*/
195+
public function supports(Request $request): ?bool
196+
{
197+
return $this->tokenStorage->getToken() instanceof OAuthToken;
198+
}
199+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the FOSOAuthServerBundle package.
7+
*
8+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace FOS\OAuthServerBundle\Security\Authentication\Passport;
15+
16+
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\CredentialsInterface;
17+
use Symfony\Component\Security\Core\User\UserInterface;
18+
19+
/**
20+
* Implements credentials checking for an OAuth token.
21+
*
22+
* @author Israel J. Carberry <[email protected]>
23+
*
24+
* @final
25+
*/
26+
class OAuthCredentials implements CredentialsInterface
27+
{
28+
/**
29+
* @var bool
30+
*/
31+
private $resolved = false;
32+
33+
/**
34+
* @var string
35+
*/
36+
private $scope;
37+
38+
/**
39+
* @var string
40+
*/
41+
private $tokenString;
42+
43+
public function __construct(string $tokenString, string $scope)
44+
{
45+
$this->tokenString = $tokenString;
46+
$this->scope = $scope;
47+
}
48+
49+
public function getRoles(UserInterface $user): array
50+
{
51+
$roles = $user->getRoles();
52+
53+
if (empty($this->scope)) {
54+
return $roles;
55+
}
56+
57+
foreach (explode(' ', $this->scope) as $role) {
58+
$roles[] = 'ROLE_'.mb_strtoupper($role);
59+
}
60+
61+
return array_unique($roles, SORT_REGULAR);
62+
}
63+
64+
public function getTokenString(): ?string
65+
{
66+
return $this->tokenString;
67+
}
68+
69+
public function markResolved(): void
70+
{
71+
$this->resolved = true;
72+
$this->scope = null;
73+
$this->tokenString = null;
74+
}
75+
76+
public function isResolved(): bool
77+
{
78+
return $this->resolved;
79+
}
80+
}

Tests/DependencyInjection/Security/Factory/OAuthFactoryTest.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ public function testCreateAuthenticator(): void
124124
;
125125
$id = '12';
126126
$config = [];
127-
$userProvider = 'mock.user.provider.service';
128127

129128
$definition = $this->getMockBuilder(Definition::class)
130129
->disableOriginalConstructor()
@@ -135,8 +134,8 @@ public function testCreateAuthenticator(): void
135134
->expects($this->once())
136135
->method('setDefinition')
137136
->with(
138-
'fos_oauth_server.security.authentication.provider.'.$id,
139-
new ChildDefinition('fos_oauth_server.security.authentication.provider')
137+
'fos_oauth_server.security.authentication.authenticator.'.$id,
138+
new ChildDefinition('fos_oauth_server.security.authentication.authenticator')
140139
)
141140
->will($this->returnValue($definition))
142141
;
@@ -147,15 +146,15 @@ public function testCreateAuthenticator(): void
147146
->withConsecutive(
148147
[
149148
0,
150-
new Reference($userProvider),
149+
new Reference('fos_oauth_server.server'),
151150
],
152151
[
153152
1,
154-
new Reference('security.user_checker.'.$id),
153+
new Reference('security.token_storage'),
155154
],
156155
[
157156
2,
158-
$id,
157+
new Reference('security.user_checker.'.$id),
159158
]
160159
)
161160
->willReturnOnConsecutiveCalls(
@@ -166,8 +165,8 @@ public function testCreateAuthenticator(): void
166165
;
167166

168167
$this->assertSame(
169-
'fos_oauth_server.security.authentication.provider.'.$id,
170-
$this->instance->createAuthenticator($container, $id, $config, $userProvider)
168+
'fos_oauth_server.security.authentication.authenticator.'.$id,
169+
$this->instance->createAuthenticator($container, $id, $config, 'ignored.user.provider.service')
171170
);
172171
}
173172

0 commit comments

Comments
 (0)