Skip to content

Commit e4a81a3

Browse files
authored
Move to dedicated AccessTokenEntity Factory (#256)
* Add AccessTokenEntityFactory --------- Co-authored-by: Marko Ivančić <[email protected]>
1 parent 7d653ab commit e4a81a3

28 files changed

+422
-255
lines changed

routing/services/services.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ services:
7070
SimpleSAML\Module\oidc\Factories\IdTokenResponseFactory:
7171
arguments:
7272
$privateKey: '@oidc.key.private'
73+
SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory:
74+
arguments:
75+
$privateKey: '@oidc.key.private'
7376

7477
SimpleSAML\Module\oidc\Server\Validators\BearerTokenValidator:
7578
arguments:

src/Entities/AccessTokenEntity.php

Lines changed: 23 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,19 @@
1717
namespace SimpleSAML\Module\oidc\Entities;
1818

1919
use DateTimeImmutable;
20+
use Lcobucci\JWT\Configuration;
2021
use Lcobucci\JWT\Token;
22+
use League\OAuth2\Server\CryptKey;
2123
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
2224
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
2325
use League\OAuth2\Server\Entities\Traits\EntityTrait;
2426
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;
2527
use PDO;
2628
use SimpleSAML\Module\oidc\Entities\Interfaces\AccessTokenEntityInterface;
27-
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
2829
use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface;
2930
use SimpleSAML\Module\oidc\Entities\Traits\AssociateWithAuthCodeTrait;
3031
use SimpleSAML\Module\oidc\Entities\Traits\RevokeTokenTrait;
31-
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
3232
use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService;
33-
use SimpleSAML\Module\oidc\Utils\TimestampGenerator;
3433
use Stringable;
3534

3635
/**
@@ -57,86 +56,35 @@ class AccessTokenEntity implements AccessTokenEntityInterface, EntityStringRepre
5756
protected array $requestedClaims;
5857

5958
/**
60-
* Constructor.
61-
*/
62-
private function __construct()
63-
{
64-
}
65-
66-
/**
67-
* Create new Access Token from data.
68-
*
6959
* @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
7060
*/
71-
public static function fromData(
61+
public function __construct(
62+
string $id,
7263
OAuth2ClientEntityInterface $clientEntity,
7364
array $scopes,
65+
DateTimeImmutable $expiryDateTime,
66+
CryptKey $privateKey,
67+
protected JsonWebTokenBuilderService $jsonWebTokenBuilderService,
7468
int|string $userIdentifier = null,
7569
string $authCodeId = null,
7670
array $requestedClaims = null,
77-
): self {
78-
$accessToken = new self();
79-
80-
$accessToken->setClient($clientEntity);
81-
$accessToken->setUserIdentifier($userIdentifier);
82-
$accessToken->setAuthCodeId($authCodeId);
71+
bool $isRevoked = false,
72+
Configuration $jwtConfiguration = null,
73+
) {
74+
$this->setIdentifier($id);
75+
$this->setClient($clientEntity);
8376
foreach ($scopes as $scope) {
84-
$accessToken->addScope($scope);
77+
$this->addScope($scope);
8578
}
86-
$accessToken->setRequestedClaims($requestedClaims ?? []);
87-
88-
return $accessToken;
89-
}
90-
91-
/**
92-
* @throws \Exception
93-
* @throws \JsonException
94-
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
95-
*/
96-
public static function fromState(array $state): self
97-
{
98-
$accessToken = new self();
99-
100-
if (
101-
!is_string($state['scopes']) ||
102-
!is_string($state['id']) ||
103-
!is_string($state['expires_at']) ||
104-
!is_a($state['client'], ClientEntityInterface::class)
105-
) {
106-
throw OidcServerException::serverError('Invalid Access Token Entity state');
107-
}
108-
109-
$stateScopes = json_decode($state['scopes'], true, 512, JSON_THROW_ON_ERROR);
110-
if (!is_array($stateScopes)) {
111-
throw OidcServerException::serverError('Invalid Access Token Entity state: scopes');
79+
$this->setExpiryDateTime($expiryDateTime);
80+
$this->setPrivateKey($privateKey);
81+
$this->setUserIdentifier($userIdentifier);
82+
$this->setAuthCodeId($authCodeId);
83+
$this->setRequestedClaims($requestedClaims ?? []);
84+
if ($isRevoked) {
85+
$this->revoke();
11286
}
113-
114-
/** @psalm-var string $scope */
115-
$scopes = array_map(fn(string $scope) => ScopeEntity::fromData($scope), $stateScopes);
116-
117-
$accessToken->identifier = $state['id'];
118-
$accessToken->scopes = $scopes;
119-
// TODO mivanci move to new 'utcImmutable' method in TimestampGenerator.
120-
$accessToken->expiryDateTime = DateTimeImmutable::createFromMutable(
121-
TimestampGenerator::utc($state['expires_at']),
122-
);
123-
$accessToken->userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id'];
124-
$accessToken->client = $state['client'];
125-
$accessToken->isRevoked = (bool) $state['is_revoked'];
126-
$accessToken->authCodeId = empty($state['auth_code_id']) ? null : (string)$state['auth_code_id'];
127-
128-
$stateRequestedClaims = json_decode(
129-
empty($state['requested_claims']) ? '[]' : (string)$state['requested_claims'],
130-
true,
131-
512,
132-
JSON_THROW_ON_ERROR,
133-
);
134-
if (!is_array($stateRequestedClaims)) {
135-
throw OidcServerException::serverError('Invalid Access Token Entity state: requested claims');
136-
}
137-
$accessToken->requestedClaims = $stateRequestedClaims;
138-
139-
return $accessToken;
87+
$jwtConfiguration !== null ? $this->jwtConfiguration = $jwtConfiguration : $this->initJwtConfiguration();
14088
}
14189

14290
/**
@@ -199,9 +147,8 @@ public function toString(): ?string
199147
*/
200148
protected function convertToJWT(): Token
201149
{
202-
$jwtBuilderService = new JsonWebTokenBuilderService();
203150
/** @psalm-suppress ArgumentTypeCoercion */
204-
$jwtBuilder = $jwtBuilderService->getProtocolJwtBuilder()
151+
$jwtBuilder = $this->jsonWebTokenBuilderService->getProtocolJwtBuilder()
205152
->permittedFor($this->getClient()->getIdentifier())
206153
->identifiedBy((string)$this->getIdentifier())
207154
->issuedAt(new DateTimeImmutable())
@@ -210,6 +157,6 @@ protected function convertToJWT(): Token
210157
->relatedTo((string) $this->getUserIdentifier())
211158
->withClaim('scopes', $this->getScopes());
212159

213-
return $jwtBuilderService->getSignedProtocolJwt($jwtBuilder);
160+
return $this->jsonWebTokenBuilderService->getSignedProtocolJwt($jwtBuilder);
214161
}
215162
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidc\Factories\Entities;
6+
7+
use DateTimeImmutable;
8+
use League\OAuth2\Server\CryptKey;
9+
use League\OAuth2\Server\Entities\ClientEntityInterface as OAuth2ClientEntityInterface;
10+
use SimpleSAML\Module\oidc\Entities\AccessTokenEntity;
11+
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
12+
use SimpleSAML\Module\oidc\Entities\ScopeEntity;
13+
use SimpleSAML\Module\oidc\Helpers;
14+
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
15+
use SimpleSAML\Module\oidc\Services\JsonWebTokenBuilderService;
16+
17+
class AccessTokenEntityFactory
18+
{
19+
public function __construct(
20+
protected readonly Helpers $helpers,
21+
protected readonly CryptKey $privateKey,
22+
protected readonly JsonWebTokenBuilderService $jsonWebTokenBuilderService,
23+
) {
24+
}
25+
26+
/**
27+
* @param \League\OAuth2\Server\Entities\ScopeEntityInterface[] $scopes
28+
*/
29+
public function fromData(
30+
string $id,
31+
OAuth2ClientEntityInterface $clientEntity,
32+
array $scopes,
33+
DateTimeImmutable $expiryDateTime,
34+
int|string $userIdentifier = null,
35+
string $authCodeId = null,
36+
array $requestedClaims = null,
37+
bool $isRevoked = false,
38+
): AccessTokenEntity {
39+
return new AccessTokenEntity(
40+
$id,
41+
$clientEntity,
42+
$scopes,
43+
$expiryDateTime,
44+
$this->privateKey,
45+
$this->jsonWebTokenBuilderService,
46+
$userIdentifier,
47+
$authCodeId,
48+
$requestedClaims,
49+
$isRevoked,
50+
);
51+
}
52+
53+
/**
54+
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
55+
* @throws \JsonException
56+
*/
57+
public function fromState(array $state): AccessTokenEntity
58+
{
59+
if (
60+
!is_string($state['scopes']) ||
61+
!is_string($state['id']) ||
62+
!is_string($state['expires_at']) ||
63+
!is_a($state['client'], ClientEntityInterface::class)
64+
) {
65+
throw OidcServerException::serverError('Invalid Access Token Entity state');
66+
}
67+
68+
$stateScopes = json_decode($state['scopes'], true, 512, JSON_THROW_ON_ERROR);
69+
if (!is_array($stateScopes)) {
70+
throw OidcServerException::serverError('Invalid Access Token Entity state: scopes');
71+
}
72+
73+
/** @psalm-var string $scope */
74+
$scopes = array_map(fn(string $scope) => ScopeEntity::fromData($scope), $stateScopes);
75+
76+
$id = $state['id'];
77+
$expiryDateTime = $this->helpers->dateTime()->getUtc($state['expires_at']);
78+
$userIdentifier = empty($state['user_id']) ? null : (string)$state['user_id'];
79+
$client = $state['client'];
80+
$isRevoked = (bool) $state['is_revoked'];
81+
$authCodeId = empty($state['auth_code_id']) ? null : (string)$state['auth_code_id'];
82+
83+
$stateRequestedClaims = json_decode(
84+
empty($state['requested_claims']) ? '[]' : (string)$state['requested_claims'],
85+
true,
86+
512,
87+
JSON_THROW_ON_ERROR,
88+
);
89+
if (!is_array($stateRequestedClaims)) {
90+
throw OidcServerException::serverError('Invalid Access Token Entity state: requested claims');
91+
}
92+
93+
return $this->fromData(
94+
$id,
95+
$client,
96+
$scopes,
97+
$expiryDateTime,
98+
$userIdentifier,
99+
$authCodeId,
100+
$stateRequestedClaims,
101+
$isRevoked,
102+
);
103+
}
104+
}

src/Factories/Grant/AuthCodeGrantFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
namespace SimpleSAML\Module\oidc\Factories\Grant;
1818

19+
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
1920
use SimpleSAML\Module\oidc\ModuleConfig;
2021
use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository;
2122
use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository;
@@ -33,6 +34,7 @@ public function __construct(
3334
private readonly RefreshTokenRepository $refreshTokenRepository,
3435
private readonly RequestRulesManager $requestRulesManager,
3536
private readonly RequestParamsResolver $requestParamsResolver,
37+
private readonly AccessTokenEntityFactory $accessTokenEntityFactory,
3638
) {
3739
}
3840

@@ -48,6 +50,7 @@ public function build(): AuthCodeGrant
4850
$this->moduleConfig->getAuthCodeDuration(),
4951
$this->requestRulesManager,
5052
$this->requestParamsResolver,
53+
$this->accessTokenEntityFactory,
5154
);
5255
$authCodeGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration());
5356

src/Factories/Grant/ImplicitGrantFactory.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
namespace SimpleSAML\Module\oidc\Factories\Grant;
1717

18+
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
1819
use SimpleSAML\Module\oidc\ModuleConfig;
1920
use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository;
2021
use SimpleSAML\Module\oidc\Server\Grants\ImplicitGrant;
@@ -30,6 +31,7 @@ public function __construct(
3031
private readonly RequestRulesManager $requestRulesManager,
3132
private readonly AccessTokenRepository $accessTokenRepository,
3233
private readonly RequestParamsResolver $requestParamsResolver,
34+
private readonly AccessTokenEntityFactory $accessTokenEntityFactory,
3335
) {
3436
}
3537

@@ -42,6 +44,7 @@ public function build(): ImplicitGrant
4244
$this->requestRulesManager,
4345
$this->requestParamsResolver,
4446
'#',
47+
$this->accessTokenEntityFactory,
4548
);
4649
}
4750
}

src/Factories/Grant/RefreshTokenGrantFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
namespace SimpleSAML\Module\oidc\Factories\Grant;
1717

18+
use SimpleSAML\Module\oidc\Factories\Entities\AccessTokenEntityFactory;
1819
use SimpleSAML\Module\oidc\ModuleConfig;
1920
use SimpleSAML\Module\oidc\Repositories\RefreshTokenRepository;
2021
use SimpleSAML\Module\oidc\Server\Grants\RefreshTokenGrant;
@@ -24,12 +25,13 @@ class RefreshTokenGrantFactory
2425
public function __construct(
2526
private readonly ModuleConfig $moduleConfig,
2627
private readonly RefreshTokenRepository $refreshTokenRepository,
28+
private readonly AccessTokenEntityFactory $accessTokenEntityFactory,
2729
) {
2830
}
2931

3032
public function build(): RefreshTokenGrant
3133
{
32-
$refreshTokenGrant = new RefreshTokenGrant($this->refreshTokenRepository);
34+
$refreshTokenGrant = new RefreshTokenGrant($this->refreshTokenRepository, $this->accessTokenEntityFactory);
3335
$refreshTokenGrant->setRefreshTokenTTL($this->moduleConfig->getRefreshTokenDuration());
3436

3537
return $refreshTokenGrant;

0 commit comments

Comments
 (0)