Skip to content

Commit c67aa70

Browse files
Merge branch 'master' into master-fix-events
2 parents 6371edd + 07920aa commit c67aa70

15 files changed

+203
-134
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Added
9+
- Added a new function to the provided ClientTrait, `supportsGrantType` to allow the auth server to issue the response `unauthorized_client` when applicable (PR #1420)
10+
11+
### Fixed
12+
- Clients only validated for Refresh, Device Code, and Password grants if the client is confidential (PR #1420)
13+
814
### Changed
915
- Key permission checks ignored on Windows regardless of userland choice as cannot be run successfully on this OS (PR #1447)
1016
- Fixed bug on setting interval visibility of device authorization grant (PR #1410)

examples/README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
# Example implementations
1+
# Example implementations (via [`Slim 3`](https://github.com/slimphp/Slim/tree/3.x))
22

33
## Installation
44

55
0. Run `composer install` in this directory to install dependencies
66
0. Create a private key `openssl genrsa -out private.key 2048`
7-
0. Create a public key `openssl rsa -in private.key -pubout > public.key`
8-
0. `cd` into the public directory
9-
0. Start a PHP server `php -S localhost:4444`
7+
0. Export the public key `openssl rsa -in private.key -pubout > public.key`
8+
0. Start local PHP server `php -S 127.0.0.1:4444 -t public/`
109

1110
## Testing the client credentials grant example
1211

examples/src/Repositories/AuthCodeRepository.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,31 @@ class AuthCodeRepository implements AuthCodeRepositoryInterface
2121
/**
2222
* {@inheritdoc}
2323
*/
24-
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity)
24+
public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void
2525
{
2626
// Some logic to persist the auth code to a database
2727
}
2828

2929
/**
3030
* {@inheritdoc}
3131
*/
32-
public function revokeAuthCode($codeId)
32+
public function revokeAuthCode($codeId): void
3333
{
3434
// Some logic to revoke the auth code in a database
3535
}
3636

3737
/**
3838
* {@inheritdoc}
3939
*/
40-
public function isAuthCodeRevoked($codeId)
40+
public function isAuthCodeRevoked($codeId): bool
4141
{
4242
return false; // The auth code has not been revoked
4343
}
4444

4545
/**
4646
* {@inheritdoc}
4747
*/
48-
public function getNewAuthCode()
48+
public function getNewAuthCode(): AuthCodeEntityInterface
4949
{
5050
return new AuthCodeEntity();
5151
}

src/Entities/ClientEntityInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,11 @@ public function getRedirectUri(): string|array;
3838
* Returns true if the client is confidential.
3939
*/
4040
public function isConfidential(): bool;
41+
42+
/*
43+
* Returns true if the client supports the given grant type.
44+
*
45+
* To be added in a future major release.
46+
*/
47+
// public function supportsGrantType(string $grantType): bool;
4148
}

src/Entities/Traits/ClientTrait.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,12 @@ public function isConfidential(): bool
5252
{
5353
return $this->isConfidential;
5454
}
55+
56+
/**
57+
* Returns true if the client supports the given grant type.
58+
*/
59+
public function supportsGrantType(string $grantType): bool
60+
{
61+
return true;
62+
}
5563
}

src/Exception/OAuthServerException.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,17 @@ public static function slowDown(string $hint = '', ?Throwable $previous = null):
252252
}
253253

254254
/**
255+
* Unauthorized client error.
256+
*/
257+
public static function unauthorizedClient(?string $hint = null): static
255258
{
256-
return $this->errorType;
259+
return new static(
260+
'The authenticated client is not authorized to use this authorization grant type.',
261+
14,
262+
'unauthorized_client',
263+
400,
264+
$hint
265+
);
257266
}
258267

259268
/**

src/Grant/AbstractGrant.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,18 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity
151151
{
152152
[$clientId, $clientSecret] = $this->getClientCredentials($request);
153153

154-
if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
155-
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
156-
157-
throw OAuthServerException::invalidClient($request);
158-
}
159154
$client = $this->getClientEntityOrFail($clientId, $request);
160155

161-
// If a redirect URI is provided ensure it matches what is pre-registered
162-
$redirectUri = $this->getRequestParameter('redirect_uri', $request);
156+
if ($client->isConfidential()) {
157+
if ($clientSecret === '') {
158+
throw OAuthServerException::invalidRequest('client_secret');
159+
}
163160

164-
if ($redirectUri !== null) {
165-
$this->validateRedirectUri($redirectUri, $client, $request);
161+
if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
162+
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
163+
164+
throw OAuthServerException::invalidClient($request);
165+
}
166166
}
167167

168168
return $client;
@@ -189,9 +189,22 @@ protected function getClientEntityOrFail(string $clientId, ServerRequestInterfac
189189
throw OAuthServerException::invalidClient($request);
190190
}
191191

192+
if ($this->supportsGrantType($client, $this->getIdentifier()) === false) {
193+
throw OAuthServerException::unauthorizedClient();
194+
}
195+
192196
return $client;
193197
}
194198

199+
/**
200+
* Returns true if the given client is authorized to use the given grant type.
201+
*/
202+
protected function supportsGrantType(ClientEntityInterface $client, string $grantType): bool
203+
{
204+
return method_exists($client, 'supportsGrantType') === false
205+
|| $client->supportsGrantType($grantType) === true;
206+
}
207+
195208
/**
196209
* Gets the client credentials from the request from the request body or
197210
* the Http Basic Authorization header
@@ -484,6 +497,10 @@ protected function issueAuthCode(
484497
*/
485498
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface
486499
{
500+
if ($this->supportsGrantType($accessToken->getClient(), 'refresh_token') === false) {
501+
return null;
502+
}
503+
487504
$refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
488505

489506
if ($refreshToken === null) {

src/Grant/AuthCodeGrant.php

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,7 @@ public function respondToAccessTokenRequest(
9797
ResponseTypeInterface $responseType,
9898
DateInterval $accessTokenTTL
9999
): ResponseTypeInterface {
100-
list($clientId) = $this->getClientCredentials($request);
101-
102-
$client = $this->getClientEntityOrFail($clientId, $request);
103-
104-
// Only validate the client if it is confidential
105-
if ($client->isConfidential()) {
106-
$this->validateClient($request);
107-
}
100+
$client = $this->validateClient($request);
108101

109102
$encryptedAuthCode = $this->getRequestParameter('code', $request);
110103

src/Grant/ClientCredentialsGrant.php

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,14 @@ public function respondToAccessTokenRequest(
3434
ResponseTypeInterface $responseType,
3535
DateInterval $accessTokenTTL
3636
): ResponseTypeInterface {
37-
list($clientId) = $this->getClientCredentials($request);
38-
39-
$client = $this->getClientEntityOrFail($clientId, $request);
37+
$client = $this->validateClient($request);
4038

4139
if (!$client->isConfidential()) {
4240
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
4341

4442
throw OAuthServerException::invalidClient($request);
4543
}
4644

47-
// Validate request
48-
$this->validateClient($request);
49-
5045
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
5146

5247
// Finalize the requested scopes

tests/AuthorizationServerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ public function testMultipleRequestsGetDifferentResponseTypeInstances(): void
183183
$privateKey = 'file://' . __DIR__ . '/Stubs/private.key';
184184
$encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key';
185185

186-
$responseTypePrototype = new class () extends BearerTokenResponse {
186+
$responseTypePrototype = new class extends BearerTokenResponse {
187187
protected CryptKeyInterface $privateKey;
188188
protected Key|string|null $encryptionKey = null;
189189

0 commit comments

Comments
 (0)