Skip to content

Commit 6612948

Browse files
committed
Http: Review credential classes
- Split `AccessToken` into `GenericCredential`, `GenericToken` and `OAuth2AccessToken`, replacing public properties with getters
1 parent f30ff46 commit 6612948

File tree

8 files changed

+138
-157
lines changed

8 files changed

+138
-157
lines changed

src/Toolkit/Contract/Http/CredentialInterface.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@
88
interface CredentialInterface
99
{
1010
/**
11-
* Get the authentication scheme of the credential, e.g. "Bearer"
11+
* Get the authentication scheme of the credential, e.g. "Basic", "Digest"
12+
* or "Bearer"
1213
*/
1314
public function getAuthenticationScheme(): string;
1415

1516
/**
16-
* Get the credential
17+
* Get the credential, e.g. a Base64-encoded user ID/password pair, a
18+
* comma-delimited list of authorization parameters or an OAuth 2.0 access
19+
* token
1720
*/
1821
public function getCredential(): string;
1922
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace Salient\Http;
4+
5+
use Salient\Contract\Core\Immutable;
6+
use Salient\Contract\Http\CredentialInterface;
7+
8+
/**
9+
* @api
10+
*/
11+
class GenericCredential implements CredentialInterface, Immutable
12+
{
13+
private string $AuthenticationScheme;
14+
private string $Credential;
15+
16+
/**
17+
* @api
18+
*/
19+
public function __construct(
20+
string $credential,
21+
string $authenticationScheme
22+
) {
23+
$this->AuthenticationScheme = $authenticationScheme;
24+
$this->Credential = $credential;
25+
}
26+
27+
/**
28+
* @inheritDoc
29+
*/
30+
public function getAuthenticationScheme(): string
31+
{
32+
return $this->AuthenticationScheme;
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
public function getCredential(): string
39+
{
40+
return $this->Credential;
41+
}
42+
}

src/Toolkit/Http/GenericToken.php

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,52 @@
11
<?php declare(strict_types=1);
22

3-
namespace Salient\Http\OAuth2;
3+
namespace Salient\Http;
44

5-
use Salient\Contract\Core\Entity\Readable;
6-
use Salient\Contract\Core\Immutable;
7-
use Salient\Contract\Http\CredentialInterface;
8-
use Salient\Core\Concern\ReadableProtectedPropertiesTrait;
95
use Salient\Utility\Date;
106
use DateTimeImmutable;
117
use DateTimeInterface;
128
use InvalidArgumentException;
139

1410
/**
15-
* A token issued by an authorization provider for access to protected resources
16-
*
17-
* @property-read string $Token
18-
* @property-read string $Type
19-
* @property-read DateTimeImmutable|null $Expires
20-
* @property-read string[] $Scopes
21-
* @property-read array<string,mixed> $Claims
11+
* @api
2212
*/
23-
final class AccessToken implements CredentialInterface, Immutable, Readable
13+
class GenericToken extends GenericCredential
2414
{
25-
use ReadableProtectedPropertiesTrait;
26-
27-
protected string $Token;
28-
protected string $Type;
29-
protected ?DateTimeImmutable $Expires;
30-
/** @var string[] */
31-
protected array $Scopes;
32-
/** @var array<string,mixed> */
33-
protected array $Claims;
15+
private ?DateTimeImmutable $Expires;
3416

3517
/**
36-
* @param DateTimeInterface|int|null $expires `null` if the access token's
37-
* lifetime is unknown, otherwise a {@see DateTimeInterface} or Unix
18+
* @api
19+
*
20+
* @param DateTimeInterface|int|null $expires `null` if the token's lifetime
21+
* is unknown or unlimited, otherwise a {@see DateTimeInterface} or Unix
3822
* timestamp representing its expiration time.
39-
* @param string[]|null $scopes
40-
* @param array<string,mixed>|null $claims
4123
*/
4224
public function __construct(
4325
string $token,
44-
string $type,
45-
$expires,
46-
?array $scopes = null,
47-
?array $claims = null
26+
string $authenticationScheme,
27+
$expires = null
4828
) {
4929
if (is_int($expires) && $expires < 0) {
50-
throw new InvalidArgumentException(sprintf(
51-
'Invalid $expires: %d',
52-
$expires
53-
));
30+
throw new InvalidArgumentException(
31+
sprintf('Invalid timestamp: %d', $expires),
32+
);
5433
}
5534

56-
$this->Token = $token;
57-
$this->Type = $type;
5835
$this->Expires = $expires instanceof DateTimeInterface
5936
? Date::immutable($expires)
60-
: ($expires === null
61-
? null
62-
: new DateTimeImmutable("@$expires"));
63-
$this->Scopes = $scopes ?: [];
64-
$this->Claims = $claims ?: [];
65-
}
37+
: ($expires !== null
38+
? new DateTimeImmutable('@' . $expires)
39+
: null);
6640

67-
/**
68-
* @inheritDoc
69-
*/
70-
public function getAuthenticationScheme(): string
71-
{
72-
return $this->Type;
41+
parent::__construct($token, $authenticationScheme);
7342
}
7443

7544
/**
76-
* @inheritDoc
45+
* Get the expiration time of the token, or null if its lifetime is unknown
46+
* or unlimited
7747
*/
78-
public function getCredential(): string
48+
public function getExpires(): ?DateTimeImmutable
7949
{
80-
return $this->Token;
50+
return $this->Expires;
8151
}
8252
}

src/Toolkit/Http/OAuth2/OAuth2AccessToken.php

Lines changed: 25 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,81 +2,53 @@
22

33
namespace Salient\Http\OAuth2;
44

5-
use Salient\Contract\Core\Entity\Readable;
6-
use Salient\Contract\Core\Immutable;
7-
use Salient\Contract\Http\CredentialInterface;
8-
use Salient\Core\Concern\ReadableProtectedPropertiesTrait;
9-
use Salient\Utility\Date;
10-
use DateTimeImmutable;
11-
use DateTimeInterface;
12-
use InvalidArgumentException;
5+
use Salient\Http\GenericToken;
136

147
/**
15-
* A token issued by an authorization provider for access to protected resources
16-
*
17-
* @property-read string $Token
18-
* @property-read string $Type
19-
* @property-read DateTimeImmutable|null $Expires
20-
* @property-read string[] $Scopes
21-
* @property-read array<string,mixed> $Claims
8+
* @api
229
*/
23-
final class AccessToken implements CredentialInterface, Immutable, Readable
10+
class OAuth2AccessToken extends GenericToken
2411
{
25-
use ReadableProtectedPropertiesTrait;
26-
27-
protected string $Token;
28-
protected string $Type;
29-
protected ?DateTimeImmutable $Expires;
3012
/** @var string[] */
31-
protected array $Scopes;
13+
private array $Scopes;
3214
/** @var array<string,mixed> */
33-
protected array $Claims;
15+
private array $Claims;
3416

3517
/**
36-
* @param DateTimeInterface|int|null $expires `null` if the access token's
37-
* lifetime is unknown, otherwise a {@see DateTimeInterface} or Unix
38-
* timestamp representing its expiration time.
39-
* @param string[]|null $scopes
40-
* @param array<string,mixed>|null $claims
18+
* @api
19+
*
20+
* @param string[] $scopes
21+
* @param array<string,mixed> $claims
4122
*/
4223
public function __construct(
4324
string $token,
44-
string $type,
45-
$expires,
46-
?array $scopes = null,
47-
?array $claims = null
25+
$expires = null,
26+
array $scopes = [],
27+
array $claims = []
4828
) {
49-
if (is_int($expires) && $expires < 0) {
50-
throw new InvalidArgumentException(sprintf(
51-
'Invalid $expires: %d',
52-
$expires
53-
));
54-
}
29+
$this->Scopes = $scopes;
30+
$this->Claims = $claims;
5531

56-
$this->Token = $token;
57-
$this->Type = $type;
58-
$this->Expires = $expires instanceof DateTimeInterface
59-
? Date::immutable($expires)
60-
: ($expires === null
61-
? null
62-
: new DateTimeImmutable("@$expires"));
63-
$this->Scopes = $scopes ?: [];
64-
$this->Claims = $claims ?: [];
32+
parent::__construct($token, 'Bearer', $expires);
6533
}
6634

6735
/**
68-
* @inheritDoc
36+
* Get the token's scopes
37+
*
38+
* @return string[]
6939
*/
70-
public function getAuthenticationScheme(): string
40+
public function getScopes(): array
7141
{
72-
return $this->Type;
42+
return $this->Scopes;
7343
}
7444

7545
/**
76-
* @inheritDoc
46+
* Get the token's claims
47+
*
48+
* @return array<string,mixed>
7749
*/
78-
public function getCredential(): string
50+
public function getClaims(): array
7951
{
80-
return $this->Token;
52+
return $this->Claims;
8153
}
8254
}

src/Toolkit/Http/OAuth2/OAuth2Client.php

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ abstract protected function getJsonWebKeySetUrl(): ?string;
126126
* @param array<string,mixed>|null $idToken
127127
* @param OAuth2GrantType::* $grantType
128128
*/
129-
abstract protected function receiveToken(AccessToken $token, ?array $idToken, string $grantType): void;
129+
abstract protected function receiveToken(OAuth2AccessToken $token, ?array $idToken, string $grantType): void;
130130

131131
public function __construct()
132132
{
@@ -162,9 +162,9 @@ final protected function getRedirectUri(): ?string
162162
*
163163
* @param string[]|null $scopes
164164
*/
165-
final public function getAccessToken(?array $scopes = null): AccessToken
165+
final public function getAccessToken(?array $scopes = null): OAuth2AccessToken
166166
{
167-
$token = Cache::getInstance()->getInstanceOf($this->TokenKey, AccessToken::class);
167+
$token = Cache::getInstance()->getInstanceOf($this->TokenKey, OAuth2AccessToken::class);
168168
if ($token) {
169169
if ($this->accessTokenHasScopes($token, $scopes)) {
170170
return $token;
@@ -197,9 +197,9 @@ final public function getAccessToken(?array $scopes = null): AccessToken
197197
*
198198
* @param string[]|null $scopes
199199
*/
200-
private function accessTokenHasScopes(AccessToken $token, ?array $scopes): bool
200+
private function accessTokenHasScopes(OAuth2AccessToken $token, ?array $scopes): bool
201201
{
202-
if ($scopes && array_diff($scopes, $token->Scopes)) {
202+
if ($scopes && array_diff($scopes, $token->getScopes())) {
203203
return false;
204204
}
205205
return true;
@@ -209,7 +209,7 @@ private function accessTokenHasScopes(AccessToken $token, ?array $scopes): bool
209209
* If an unexpired refresh token is available, use it to get a new access
210210
* token from the provider if possible
211211
*/
212-
final protected function refreshAccessToken(): ?AccessToken
212+
final protected function refreshAccessToken(): ?OAuth2AccessToken
213213
{
214214
$refreshToken = Cache::getString("{$this->TokenKey}:refresh");
215215
return $refreshToken === null
@@ -225,7 +225,7 @@ final protected function refreshAccessToken(): ?AccessToken
225225
*
226226
* @param array<string,mixed> $options
227227
*/
228-
final protected function authorize(array $options = []): AccessToken
228+
final protected function authorize(array $options = []): OAuth2AccessToken
229229
{
230230
if (isset($options['scope'])) {
231231
$scopes = $this->filterScope($options['scope']);
@@ -237,9 +237,9 @@ final protected function authorize(array $options = []): AccessToken
237237
$cache->has($this->TokenKey)
238238
|| $cache->has("{$this->TokenKey}:refresh")
239239
) {
240-
$lastToken = $cache->getInstanceOf($this->TokenKey, AccessToken::class);
240+
$lastToken = $cache->getInstanceOf($this->TokenKey, OAuth2AccessToken::class);
241241
if ($lastToken) {
242-
$scopes = Arr::extend($lastToken->Scopes, ...$scopes);
242+
$scopes = Arr::extend($lastToken->getScopes(), ...$scopes);
243243
}
244244
}
245245
$cache->close();
@@ -265,7 +265,7 @@ final protected function authorize(array $options = []): AccessToken
265265
/**
266266
* @param array<string,mixed> $options
267267
*/
268-
private function authorizeWithClientCredentials(array $options = []): AccessToken
268+
private function authorizeWithClientCredentials(array $options = []): OAuth2AccessToken
269269
{
270270
// league/oauth2-client doesn't add scopes to client_credentials
271271
// requests
@@ -290,7 +290,7 @@ private function authorizeWithClientCredentials(array $options = []): AccessToke
290290
/**
291291
* @param array<string,mixed> $options
292292
*/
293-
private function authorizeWithAuthorizationCode(array $options = []): AccessToken
293+
private function authorizeWithAuthorizationCode(array $options = []): OAuth2AccessToken
294294
{
295295
if (!$this->Listener) {
296296
throw new LogicException('Cannot use the Authorization Code flow without a Listener');
@@ -380,7 +380,7 @@ private function requestAccessToken(
380380
string $grantType,
381381
array $options = [],
382382
$scope = null
383-
): AccessToken {
383+
): OAuth2AccessToken {
384384
Console::debug('Requesting access token with ' . $grantType);
385385

386386
$_token = $this->Provider->getAccessToken($grantType, $options);
@@ -419,21 +419,20 @@ private function requestAccessToken(
419419
?? $scope);
420420

421421
if (!$scopes && $grantType === OAuth2GrantType::REFRESH_TOKEN) {
422-
$lastToken = Cache::getInstance()->getInstanceOf($this->TokenKey, AccessToken::class);
422+
$lastToken = Cache::getInstance()->getInstanceOf($this->TokenKey, OAuth2AccessToken::class);
423423
if ($lastToken) {
424-
$scopes = $lastToken->Scopes;
424+
$scopes = $lastToken->getScopes();
425425
}
426426
}
427427

428-
$token = new AccessToken(
428+
$token = new OAuth2AccessToken(
429429
$accessToken,
430-
$tokenType,
431430
$expires,
432431
$scopes ?: $this->getDefaultScopes(),
433432
$claims
434433
);
435434

436-
Cache::set($this->TokenKey, $token, $token->Expires);
435+
Cache::set($this->TokenKey, $token, $token->getExpires());
437436

438437
if ($idToken !== null) {
439438
$idToken = $this->getValidJsonWebToken($idToken, true);

0 commit comments

Comments
 (0)