Skip to content

Commit 6594667

Browse files
committed
WIP
1 parent dc8766f commit 6594667

File tree

7 files changed

+153
-43
lines changed

7 files changed

+153
-43
lines changed

routing/routes/routes.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,9 @@
146146
* API
147147
****************************************************************************************************************/
148148

149-
$routes->add(RoutesEnum::ApiVciCredentialOffer->name, RoutesEnum::ApiVciCredentialOffer->value)
150-
->controller([VciCredentialOfferController::class, 'credentialOffer'])
149+
$routes->add(
150+
RoutesEnum::ApiVciPreAuthorizedCredentialOffer->name,
151+
RoutesEnum::ApiVciPreAuthorizedCredentialOffer->value,
152+
)->controller([VciCredentialOfferController::class, 'preAuthorizedCredentialOffer'])
151153
->methods([HttpMethodsEnum::POST->value]);
152154
};

src/Codebooks/RoutesEnum.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,5 +74,5 @@ enum RoutesEnum: string
7474
* API
7575
****************************************************************************************************************/
7676

77-
case ApiVciCredentialOffer = 'api/vci/credential-offer';
77+
case ApiVciPreAuthorizedCredentialOffer = 'api/vci/pre-authorized-credential-offer';
7878
}

src/Controllers/Admin/VerifiableCredentailsTestController.php

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,17 @@ public function verifiableCredentialIssuance(Request $request): Response
175175
// TODO mivanci Wallet (client) credential_offer_endpoint metadata
176176
// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#client-metadata
177177

178-
$clientSecret = '1234567890';
179-
180178

181179
$client = $this->clientEntityFactory->getGenericForVciPreAuthZFlow();
180+
if ($this->clientRepository->findById($client->getIdentifier()) === null) {
181+
$this->clientRepository->add($client);
182+
} else {
183+
$this->clientRepository->update($client);
184+
}
182185

183-
// TODO mivanci Randomly generate auth code.
184-
$authCodeId = '1234567890';
186+
$authCodeId = $this->sspBridge->utils()->random()->generateID();
185187

186-
// TODO mivanci Add indication of preauthz code to the auth code table.
188+
// TODO mivanci Add indication of preAuthZ code to the auth code table.
187189

188190
if (($authCode = $this->authCodeRepository->findById($authCodeId)) === null) {
189191
$authCode = $this->authCodeEntityFactory->fromData(
@@ -193,10 +195,9 @@ public function verifiableCredentialIssuance(Request $request): Response
193195
new ScopeEntity('openid'),
194196
new ScopeEntity($selectedCredentialConfigurationId),
195197
],
196-
expiryDateTime: new \DateTimeImmutable('+1 month'),
198+
expiryDateTime: new \DateTimeImmutable('+10 minutes'),
197199
userIdentifier: $userId,
198-
redirectUri: 'https://example.com/oidc/callback',
199-
nonce: '1234567890',
200+
redirectUri: 'openid-credential-offer://',
200201
);
201202

202203
$this->authCodeRepository->persistNewAuthCode($authCode);
@@ -236,10 +237,6 @@ public function verifiableCredentialIssuance(Request $request): Response
236237
$credentialOfferQrUri = 'https://quickchart.io/qr?size=200&margin=1&text=' . urlencode($credentialOfferUri);
237238
}
238239

239-
240-
241-
242-
243240
$authSourceActionRoute = $this->routes->urlAdminTestVerifiableCredentialIssuance();
244241

245242
return $this->templateFactory->build(

src/Controllers/Api/VciCredentialOfferController.php

Lines changed: 127 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,26 @@
44

55
namespace SimpleSAML\Module\oidc\Controllers\Api;
66

7+
use SimpleSAML\Module\oidc\Bridges\SspBridge;
78
use SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum;
8-
use SimpleSAML\Module\oidc\Exceptions\AuthorizationException;
9+
use SimpleSAML\Module\oidc\Codebooks\ParametersEnum;
10+
use SimpleSAML\Module\oidc\Entities\ScopeEntity;
11+
use SimpleSAML\Module\oidc\Entities\UserEntity;
12+
use SimpleSAML\Module\oidc\Exceptions\OidcException;
13+
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
914
use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory;
15+
use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory;
1016
use SimpleSAML\Module\oidc\ModuleConfig;
17+
use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository;
18+
use SimpleSAML\Module\oidc\Repositories\ClientRepository;
19+
use SimpleSAML\Module\oidc\Repositories\UserRepository;
1120
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
1221
use SimpleSAML\Module\oidc\Services\Api\Authorization;
22+
use SimpleSAML\Module\oidc\Services\LoggerService;
23+
use SimpleSAML\Module\oidc\Utils\Routes;
24+
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
25+
use SimpleSAML\OpenID\Codebooks\GrantTypesEnum;
1326
use SimpleSAML\OpenID\VerifiableCredentials;
14-
use Symfony\Component\HttpFoundation\JsonResponse;
1527
use Symfony\Component\HttpFoundation\Request;
1628
use Symfony\Component\HttpFoundation\Response;
1729

@@ -25,30 +37,136 @@ public function __construct(
2537
protected readonly Authorization $authorization,
2638
protected readonly VerifiableCredentials $verifiableCredentials,
2739
protected readonly ClientEntityFactory $clientEntityFactory,
40+
protected readonly ClientRepository $clientRepository,
41+
protected readonly SspBridge $sspBridge,
42+
protected readonly LoggerService $loggerService,
43+
protected readonly UserRepository $userRepository,
44+
protected readonly UserEntityFactory $userEntityFactory,
45+
protected readonly AuthCodeRepository $authCodeRepository,
46+
protected readonly AuthCodeEntityFactory $authCodeEntityFactory,
47+
protected readonly Routes $routes,
2848
) {
2949
if (!$this->moduleConfig->getApiEnabled()) {
3050
throw OidcServerException::forbidden('API capabilities not enabled.');
3151
}
3252
}
3353

3454
/**
35-
* @throws AuthorizationException
3655
*/
37-
public function credentialOffer(Request $request): Response
56+
public function preAuthorizedCredentialOffer(Request $request): Response
3857
{
3958
$this->authorization->requireTokenForAnyOfScope(
4059
$request,
4160
[ApiScopesEnum::VciCredentialOffer, ApiScopesEnum::VciAll, ApiScopesEnum::All],
4261
);
4362

44-
$input = $request->getPayload()->all();
45-
4663
// Currently, we need a dedicated client for which the PreAuthZed code will be bound to.
47-
// TODO mivanci: Remove requirement for dedicated client for authorization codes.
64+
// TODO mivanci: Remove requirement for dedicated client for (pre-)authorization codes.
4865
$client = $this->clientEntityFactory->getGenericForVciPreAuthZFlow();
66+
if ($this->clientRepository->findById($client->getIdentifier()) === null) {
67+
$this->clientRepository->add($client);
68+
} else {
69+
$this->clientRepository->update($client);
70+
}
71+
72+
$input = $request->getPayload()->all();
73+
$userAttributes = $input['user_attributes'] ?? [];
74+
75+
$selectedCredentialConfigurationId = $input['credential_configuration_id'] ?? null;
76+
if ($selectedCredentialConfigurationId === null) {
77+
throw new OidcException('No credential configuration ID provided.');
78+
}
79+
$credentialConfigurationIdsSupported = $this->moduleConfig->getCredentialConfigurationIdsSupported();
80+
81+
if (empty($credentialConfigurationIdsSupported)) {
82+
throw new OidcException('No credential configuration IDs configured.');
83+
}
84+
if (!in_array($selectedCredentialConfigurationId, $credentialConfigurationIdsSupported, true)) {
85+
throw new OidcException(
86+
'Credential configuration ID not supported: ' . $selectedCredentialConfigurationId,
87+
);
88+
}
89+
90+
$userId = null;
91+
try {
92+
$userId = $this->sspBridge->utils()->attributes()->getExpectedAttribute(
93+
$userAttributes,
94+
$this->moduleConfig->getUserIdentifierAttribute(),
95+
);
96+
} catch (\Throwable $e) {
97+
$this->loggerService->warning(
98+
'Could not extract user identifier from user attributes: ' . $e->getMessage(),
99+
);
100+
}
101+
102+
if ($userId === null) {
103+
$sortedAttributes = $userAttributes;
104+
$this->verifiableCredentials->helpers()->arr()->hybridSort($sortedAttributes);
105+
$userId = 'vci_preauthz_' . hash('sha256', serialize($sortedAttributes));
106+
}
49107

50-
dd($this->verifiableCredentials->helpers()->arr()->hybridSort($input['user_attributes']));
108+
$oldUserEntity = $this->userRepository->getUserEntityByIdentifier($userId);
51109

52-
return new JsonResponse(['ok']);
110+
$userEntity = $this->userEntityFactory->fromData($userId, $userAttributes);
111+
112+
if ($oldUserEntity instanceof UserEntity) {
113+
$this->userRepository->update($userEntity);
114+
} else {
115+
$this->userRepository->add($userEntity);
116+
}
117+
118+
119+
$authCodeId = $this->sspBridge->utils()->random()->generateID();
120+
121+
if (($authCode = $this->authCodeRepository->findById($authCodeId)) === null) {
122+
$authCode = $this->authCodeEntityFactory->fromData(
123+
id: $authCodeId,
124+
client: $client,
125+
scopes: [
126+
new ScopeEntity('openid'),
127+
new ScopeEntity($selectedCredentialConfigurationId),
128+
],
129+
expiryDateTime: new \DateTimeImmutable('+10 minutes'),
130+
userIdentifier: $userId,
131+
redirectUri: 'openid-credential-offer://',
132+
);
133+
134+
$this->authCodeRepository->persistNewAuthCode($authCode);
135+
}
136+
137+
$credentialOffer = $this->verifiableCredentials->credentialOfferFactory()->from(
138+
parameters: [
139+
ClaimsEnum::CredentialIssuer->value => $this->moduleConfig->getIssuer(),
140+
ClaimsEnum::CredentialConfigurationIds->value => [
141+
$selectedCredentialConfigurationId,
142+
],
143+
ClaimsEnum::Grants->value => [
144+
GrantTypesEnum::PreAuthorizedCode->value => [
145+
ClaimsEnum::PreAuthorizedCode->value => $authCode->getIdentifier(),
146+
// TODO mivanci support for TxCode
147+
// ClaimsEnum::TxCode->value => [
148+
// ClaimsEnum::InputMode->value => 'numeric',
149+
// ClaimsEnum::Length->value => 6,
150+
// ClaimsEnum::Description->value => 'Sent to user mail',
151+
// ],
152+
],
153+
],
154+
],
155+
);
156+
157+
$credentialOfferValue = $credentialOffer->jsonSerialize();
158+
$parameterName = ParametersEnum::CredentialOfferUri->value;
159+
if (is_array($credentialOfferValue)) {
160+
$parameterName = ParametersEnum::CredentialOffer->value;
161+
$credentialOfferValue = json_encode($credentialOfferValue);
162+
}
163+
164+
$credentialOfferUri = "openid-credential-offer://?$parameterName=$credentialOfferValue";
165+
166+
return $this->routes->newJsonResponse(
167+
data: [
168+
'credential_offer_uri' => $credentialOfferUri,
169+
],
170+
);
53171
}
54172
}

src/Factories/Entities/ClientEntityFactory.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
use SimpleSAML\Module\oidc\Entities\Interfaces\ClientEntityInterface;
1313
use SimpleSAML\Module\oidc\Helpers;
1414
use SimpleSAML\Module\oidc\ModuleConfig;
15-
use SimpleSAML\Module\oidc\Repositories\ClientRepository;
1615
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
1716
use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor;
1817
use SimpleSAML\Module\oidc\Utils\RequestParamsResolver;
@@ -32,7 +31,6 @@ public function __construct(
3231
private readonly ClaimTranslatorExtractor $claimTranslatorExtractor,
3332
private readonly RequestParamsResolver $requestParamsResolver,
3433
private readonly ModuleConfig $moduleConfig,
35-
private readonly ClientRepository $clientRepository,
3634
) {
3735
}
3836

@@ -388,37 +386,24 @@ public function fromState(array $state): ClientEntityInterface
388386
public function getGenericForVciPreAuthZFlow(): ClientEntityInterface
389387
{
390388
$clientId = 'vci_' .
391-
hash('sha256', 'vci_' . $this->moduleConfig->sspConfig()->getString('secretsalt'));
389+
hash('sha256', 'vci_' . $this->moduleConfig->sspConfig()->getString('secretsalt'));
392390

393391
$clientSecret = $this->helpers->random()->getIdentifier();
394392

395393
$credentialConfigurationIdsSupported = $this->moduleConfig->getCredentialConfigurationIdsSupported();
396394

397-
$oldClient = $this->clientRepository->findById($clientId);
398395
$createdAt = $this->helpers->dateTime()->getUtc();
399396

400-
if ($oldClient instanceof ClientEntityInterface) {
401-
$createdAt = $oldClient->getCreatedAt();
402-
}
403-
404-
$client = $this->fromData(
397+
return $this->fromData(
405398
id: $clientId,
406399
secret: $clientSecret,
407400
name: 'VCI Pre-authorized Code Generic Client',
408401
description: 'Generic client for VCI Pre-authorized Code',
409402
redirectUri: ['openid-credential-offer://'],
410403
scopes: ['openid', ...$credentialConfigurationIdsSupported],
411404
isEnabled: true,
412-
updatedAt: $this->helpers->dateTime()->getUtc(),
405+
updatedAt: $createdAt,
413406
createdAt: $createdAt,
414407
);
415-
416-
if ($oldClient === null) {
417-
$this->clientRepository->add($client);
418-
} else {
419-
$this->clientRepository->update($client);
420-
}
421-
422-
return $client;
423408
}
424409
}

src/Services/Api/Authorization.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use SimpleSAML\Locale\Translate;
88
use SimpleSAML\Module\oidc\Bridges\SspBridge;
9-
use SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum;
109
use SimpleSAML\Module\oidc\Exceptions\AuthorizationException;
1110
use SimpleSAML\Module\oidc\ModuleConfig;
1211
use Symfony\Component\HttpFoundation\Request;

src/Utils/Routes.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,13 @@ public function urlJwtVcIssuerConfiguration(array $parameters = []): string
235235
{
236236
return $this->getModuleUrl(RoutesEnum::JwtVcIssuerConfiguration->value, $parameters);
237237
}
238+
239+
/*****************************************************************************************************************
240+
* API
241+
****************************************************************************************************************/
242+
243+
public function urlApiVciPreAuthorizedCredentialOffer(array $parameters = []): string
244+
{
245+
return $this->getModuleUrl(RoutesEnum::ApiVciPreAuthorizedCredentialOffer->value, $parameters);
246+
}
238247
}

0 commit comments

Comments
 (0)