Skip to content

Commit 9ec2306

Browse files
committed
Lint
1 parent 36553ad commit 9ec2306

18 files changed

+288
-100
lines changed

config/module_oidc.php.dist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ $config = [
676676

677677
// Sample for 'dc+sd-jwt' format without notes about required and optional fields.
678678
'ResearchAndScholarshipCredentialDcSdJwt' => [
679-
ClaimsEnum::Format->value => CredentialFormatIdentifiersEnum::JwtVcJson->value,
679+
ClaimsEnum::Format->value => CredentialFormatIdentifiersEnum::DcSdJwt->value,
680680
ClaimsEnum::Scope->value => 'ResearchAndScholarshipCredentialDcSdJwt',
681681
ClaimsEnum::Display->value => [
682682
[
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
(function () {
2+
'use strict';
3+
4+
// Handle option changes based on Grant Type
5+
function togglePreAuthorizedCodeOptions() {
6+
if (grantTypeSelect.value === "urn:ietf:params:oauth:grant-type:pre-authorized_code") {
7+
useTxCodeCheckbox.disabled = false;
8+
usersEmailAttributeNameInput.disabled = false;
9+
} else {
10+
useTxCodeCheckbox.disabled = true;
11+
useTxCodeCheckbox.checked = false;
12+
usersEmailAttributeNameInput.disabled = true;
13+
}
14+
}
15+
16+
const grantTypeSelect = document.getElementById("grantType");
17+
18+
// Get references to options
19+
const useTxCodeCheckbox = document.getElementById("useTxCode");
20+
const usersEmailAttributeNameInput = document.getElementById("usersEmailAttributeName");
21+
22+
grantTypeSelect.addEventListener("change", togglePreAuthorizedCodeOptions);
23+
24+
togglePreAuthorizedCodeOptions();
25+
})();

src/Controllers/Admin/VerifiableCredentailsTestController.php

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
namespace SimpleSAML\Module\oidc\Controllers\Admin;
66

77
use SimpleSAML\Auth\Simple;
8+
use SimpleSAML\Locale\Translate;
89
use SimpleSAML\Module\oidc\Admin\Authorization;
910
use SimpleSAML\Module\oidc\Bridges\SspBridge;
1011
use SimpleSAML\Module\oidc\Codebooks\RoutesEnum;
1112
use SimpleSAML\Module\oidc\Factories\AuthSimpleFactory;
1213
use SimpleSAML\Module\oidc\Factories\CredentialOfferUriFactory;
13-
use SimpleSAML\Module\oidc\Factories\EmailFactory;
1414
use SimpleSAML\Module\oidc\Factories\TemplateFactory;
1515
use SimpleSAML\Module\oidc\ModuleConfig;
16-
use SimpleSAML\Module\oidc\Services\LoggerService;
1716
use SimpleSAML\Module\oidc\Services\SessionService;
1817
use SimpleSAML\Module\oidc\Utils\Routes;
18+
use SimpleSAML\OpenID\Codebooks\GrantTypesEnum;
1919
use Symfony\Component\HttpFoundation\Request;
2020
use Symfony\Component\HttpFoundation\Response;
2121

@@ -25,8 +25,6 @@ public function __construct(
2525
protected readonly ModuleConfig $moduleConfig,
2626
protected readonly TemplateFactory $templateFactory,
2727
protected readonly Authorization $authorization,
28-
protected readonly LoggerService $loggerService,
29-
protected readonly EmailFactory $emailFactory,
3028
protected readonly AuthSimpleFactory $authSimpleFactory,
3129
protected readonly SessionService $sessionService,
3230
protected readonly SspBridge $sspBridge,
@@ -40,6 +38,7 @@ public function __construct(
4038
* @throws \SimpleSAML\Error\ConfigurationError
4139
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
4240
* @throws \SimpleSAML\OpenID\Exceptions\CredentialOfferException
41+
* @psalm-suppress MixedAssignment, InternalMethod
4342
*/
4443
public function verifiableCredentialIssuance(Request $request): Response
4544
{
@@ -85,11 +84,13 @@ public function verifiableCredentialIssuance(Request $request): Response
8584
$authSource->login(['ReturnTo' => $this->routes->urlAdminTestVerifiableCredentialIssuance()]);
8685
}
8786

87+
/** @psalm-suppress MixedAssignment */
8888
$selectedCredentialConfigurationId = $this->sessionService->getCurrentSession()->getData(
8989
'vci',
9090
'credential_configuration_id',
9191
);
9292

93+
/** @psalm-suppress MixedAssignment, InternalMethod */
9394
if (is_string($newCredentialConfigurationId = $request->get('credentialConfigurationId'))) {
9495
$this->sessionService->getCurrentSession()->setData(
9596
'vci',
@@ -114,7 +115,11 @@ public function verifiableCredentialIssuance(Request $request): Response
114115

115116
$credentialOfferQrUri = null;
116117
$credentialOfferUri = null;
118+
/** @psalm-suppress MixedAssignment, InternalMethod */
119+
$grantType = $request->get('grantType');
120+
/** @psalm-suppress InternalMethod */
117121
$useTxCode = (bool) $request->get('useTxCode');
122+
/** @psalm-suppress MixedAssignment, InternalMethod */
118123
$usersEmailAttributeName = $request->get('usersEmailAttributeName');
119124
$usersEmailAttributeName = is_string($usersEmailAttributeName) && (trim($usersEmailAttributeName) !== '') ?
120125
trim($usersEmailAttributeName) :
@@ -129,12 +134,18 @@ public function verifiableCredentialIssuance(Request $request): Response
129134
$authSource->getAuthSource()->getAuthId(),
130135
);
131136

132-
$credentialOfferUri = $this->credentialOfferUriFactory->buildPreAuthorized(
133-
[$selectedCredentialConfigurationId],
134-
$userAttributes,
135-
$useTxCode,
136-
$usersEmailAttributeName,
137-
);
137+
if ($grantType === GrantTypesEnum::PreAuthorizedCode->value) {
138+
$credentialOfferUri = $this->credentialOfferUriFactory->buildPreAuthorized(
139+
[$selectedCredentialConfigurationId],
140+
$userAttributes,
141+
$useTxCode,
142+
$usersEmailAttributeName,
143+
);
144+
} else {
145+
$credentialOfferUri = $this->credentialOfferUriFactory->buildForAuthorization(
146+
[$selectedCredentialConfigurationId],
147+
);
148+
}
138149

139150
// TODO mivanci Local QR code generator
140151
// https://quickchart.io/documentation/qr-codes/
@@ -145,6 +156,11 @@ public function verifiableCredentialIssuance(Request $request): Response
145156

146157
$defaultUsersEmailAttributeName = $this->moduleConfig->getDefaultUsersEmailAttributeName();
147158

159+
$grantTypesSupported = [
160+
GrantTypesEnum::AuthorizationCode->value => Translate::noop('Authorization Code'),
161+
GrantTypesEnum::PreAuthorizedCode->value => Translate::noop('Pre-authorized Code'),
162+
];
163+
148164
return $this->templateFactory->build(
149165
'oidc:tests/verifiable-credential-issuance.twig',
150166
compact(
@@ -158,6 +174,7 @@ public function verifiableCredentialIssuance(Request $request): Response
158174
'selectedCredentialConfigurationId',
159175
'defaultUsersEmailAttributeName',
160176
'usersEmailAttributeName',
177+
'grantTypesSupported',
161178
),
162179
RoutesEnum::AdminTestVerifiableCredentialIssuance->value,
163180
);

src/Controllers/Api/VciCredentialOfferController.php

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,14 @@
44

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

7-
use SimpleSAML\Module\oidc\Bridges\SspBridge;
87
use SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum;
98
use SimpleSAML\Module\oidc\Exceptions\AuthorizationException;
109
use SimpleSAML\Module\oidc\Factories\CredentialOfferUriFactory;
11-
use SimpleSAML\Module\oidc\Factories\Entities\AuthCodeEntityFactory;
12-
use SimpleSAML\Module\oidc\Factories\Entities\ClientEntityFactory;
13-
use SimpleSAML\Module\oidc\Factories\Entities\UserEntityFactory;
1410
use SimpleSAML\Module\oidc\ModuleConfig;
15-
use SimpleSAML\Module\oidc\Repositories\AuthCodeRepository;
16-
use SimpleSAML\Module\oidc\Repositories\ClientRepository;
17-
use SimpleSAML\Module\oidc\Repositories\UserRepository;
1811
use SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException;
1912
use SimpleSAML\Module\oidc\Services\Api\Authorization;
2013
use SimpleSAML\Module\oidc\Services\LoggerService;
2114
use SimpleSAML\Module\oidc\Utils\Routes;
22-
use SimpleSAML\OpenID\VerifiableCredentials;
2315
use Symfony\Component\HttpFoundation\Request;
2416
use Symfony\Component\HttpFoundation\Response;
2517

@@ -31,15 +23,7 @@ class VciCredentialOfferController
3123
public function __construct(
3224
protected readonly ModuleConfig $moduleConfig,
3325
protected readonly Authorization $authorization,
34-
protected readonly VerifiableCredentials $verifiableCredentials,
35-
protected readonly ClientEntityFactory $clientEntityFactory,
36-
protected readonly ClientRepository $clientRepository,
37-
protected readonly SspBridge $sspBridge,
3826
protected readonly LoggerService $loggerService,
39-
protected readonly UserRepository $userRepository,
40-
protected readonly UserEntityFactory $userEntityFactory,
41-
protected readonly AuthCodeRepository $authCodeRepository,
42-
protected readonly AuthCodeEntityFactory $authCodeEntityFactory,
4327
protected readonly Routes $routes,
4428
protected readonly CredentialOfferUriFactory $credentialOfferUriFactory,
4529
) {
@@ -52,6 +36,7 @@ public function __construct(
5236
*/
5337
public function credentialOffer(Request $request): Response
5438
{
39+
$this->loggerService->debug('VCI credential offer request data: ', $request->getPayload()->all());
5540
try {
5641
$this->authorization->requireTokenForAnyOfScope(
5742
$request,
@@ -66,7 +51,9 @@ public function credentialOffer(Request $request): Response
6651
}
6752

6853
$input = $request->getPayload()->all();
54+
/** @psalm-suppress MixedAssignment */
6955
$userAttributes = $input['user_attributes'] ?? [];
56+
$userAttributes = is_array($userAttributes) ? $userAttributes : [];
7057

7158
$selectedCredentialConfigurationId = $input['credential_configuration_id'] ?? null;
7259

@@ -79,8 +66,10 @@ public function credentialOffer(Request $request): Response
7966
}
8067

8168
$useTxCode = boolval($input['use_tx_code'] ?? false);
69+
/** @psalm-suppress MixedAssignment */
8270
$usersEmailAttributeName = $input['users_email_attribute_name'] ?? null;
8371
$usersEmailAttributeName = is_string($usersEmailAttributeName) ? $usersEmailAttributeName : null;
72+
/** @psalm-suppress MixedAssignment */
8473
$authenticationSourceId = $input['authentication_source_id'] ?? null;
8574
$authenticationSourceId = is_string($authenticationSourceId) ? $authenticationSourceId : null;
8675

src/Controllers/VerifiableCredentials/CredentialIssuerCredentialController.php

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Base64Url\Base64Url;
88
use League\OAuth2\Server\ResourceServer;
99
use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge;
10+
use SimpleSAML\Module\oidc\Entities\AccessTokenEntity;
1011
use SimpleSAML\Module\oidc\ModuleConfig;
1112
use SimpleSAML\Module\oidc\Repositories\AccessTokenRepository;
1213
use SimpleSAML\Module\oidc\Repositories\UserRepository;
@@ -23,6 +24,7 @@
2324
use SimpleSAML\OpenID\Codebooks\JwtTypesEnum;
2425
use SimpleSAML\OpenID\Did;
2526
use SimpleSAML\OpenID\Exceptions\OpenId4VciProofException;
27+
use SimpleSAML\OpenID\Exceptions\OpenIdException;
2628
use SimpleSAML\OpenID\Jwk;
2729
use SimpleSAML\OpenID\VerifiableCredentials;
2830
use SimpleSAML\OpenID\VerifiableCredentials\OpenId4VciProof;
@@ -62,6 +64,7 @@ public function __construct(
6264
* @throws \SimpleSAML\Module\oidc\Server\Exceptions\OidcServerException
6365
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
6466
* @throws \ReflectionException
67+
* @throws OpenIdException
6568
*/
6669
public function credential(Request $request): Response
6770
{
@@ -77,7 +80,18 @@ public function credential(Request $request): Response
7780
);
7881

7982
// TODO mivanci validate access token
80-
$accessToken = $this->accessTokenRepository->findById($authorization->getAttribute('oauth_access_token_id'));
83+
$accessToken = $this->accessTokenRepository->findById(
84+
(string)$authorization->getAttribute('oauth_access_token_id'),
85+
);
86+
87+
if (! $accessToken instanceof AccessTokenEntity) {
88+
return $this->routes->newJsonErrorResponse(
89+
'invalid_token',
90+
'Access token not found.',
91+
401,
92+
);
93+
}
94+
8195
if ($accessToken->isRevoked()) {
8296
return $this->routes->newJsonErrorResponse(
8397
'invalid_token',
@@ -90,7 +104,7 @@ public function credential(Request $request): Response
90104

91105
$credentialFormatId = $requestData[ClaimsEnum::Format->value] ?? null;
92106

93-
if (is_null($credentialFormatId)) {
107+
if (!is_string($credentialFormatId)) {
94108
throw OidcServerException::serverError('Credential format missing in request.');
95109
}
96110

@@ -111,6 +125,7 @@ public function credential(Request $request): Response
111125

112126
$credentialConfigurationId = $requestData[ClaimsEnum::CredentialConfigurationId->value] ?? null;
113127

128+
/** @psalm-suppress MixedAssignment */
114129
if (is_null($credentialConfigurationId)) {
115130
// TODO mivanci Update this to newest draft.
116131
// Check per draft 14 (Sphereon wallet case).
@@ -133,7 +148,7 @@ public function credential(Request $request): Response
133148
}
134149
}
135150

136-
if (is_null($credentialConfigurationId)) {
151+
if (!is_string($credentialConfigurationId)) {
137152
return $this->routes->newJsonErrorResponse(
138153
'invalid_credential_request',
139154
'Can not resolve credential configuration ID.',
@@ -148,6 +163,9 @@ public function credential(Request $request): Response
148163
}
149164

150165
$userId = $accessToken->getUserIdentifier();
166+
if (!is_string($userId)) {
167+
throw OidcServerException::invalidRequest('User identifier not available in Access Token.');
168+
}
151169
$userEntity = $this->userRepository->getUserEntityByIdentifier($userId);
152170
if ($userEntity === null) {
153171
throw OidcServerException::invalidRequest('User not found.');
@@ -159,12 +177,13 @@ public function credential(Request $request): Response
159177
$proof = null;
160178
// Validate proof, if provided.
161179
// TODO mivanci consider making proof mandatory (in issuer metadata).
180+
/** @psalm-suppress MixedAssignment */
162181
if (
163182
isset($requestData['proof']['proof_type']) &&
164183
isset($requestData['proof']['jwt']) &&
165-
$requestData['proof']['proof_type'] === 'jwt'
184+
$requestData['proof']['proof_type'] === 'jwt' &&
185+
is_string($proofJwt = $requestData['proof']['jwt'])
166186
) {
167-
$proofJwt = $requestData['proof']['jwt'];
168187
$this->loggerService->debug('Verifying proof JWT: ' . $proofJwt);
169188

170189
try {
@@ -239,8 +258,30 @@ public function credential(Request $request): Response
239258
$credentialConfigurationId,
240259
);
241260
foreach ($attributeToCredentialClaimPathMap as $mapEntry) {
261+
if (!is_array($mapEntry)) {
262+
$this->loggerService->warning(
263+
sprintf(
264+
'Attribute to credential claim path map entry is not an array. Value was: %s',
265+
var_export($mapEntry, true),
266+
),
267+
);
268+
continue;
269+
}
270+
242271
$userAttributeName = key($mapEntry);
272+
/** @psalm-suppress MixedAssignment */
243273
$credentialClaimPath = current($mapEntry);
274+
if (!is_array($credentialClaimPath)) {
275+
$this->loggerService->warning(
276+
sprintf(
277+
'Credential claim path for user attribute name %s is not an array. Value was: %s',
278+
$userAttributeName,
279+
var_export($credentialClaimPath, true),
280+
),
281+
);
282+
continue;
283+
}
284+
$credentialClaimPath = array_filter($credentialClaimPath, 'is_string');
244285
if (!in_array($credentialClaimPath, $validClaimPaths)) {
245286
$this->loggerService->warning(
246287
'Attribute "%s" does not use one of valid credential claim paths.',
@@ -258,6 +299,7 @@ public function credential(Request $request): Response
258299
}
259300

260301
// Normalize to string for single array values.
302+
/** @psalm-suppress MixedAssignment */
261303
$attributeValue = is_array($userAttributes[$userAttributeName]) &&
262304
count($userAttributes[$userAttributeName]) === 1 ?
263305
reset($userAttributes[$userAttributeName]) :
@@ -285,10 +327,11 @@ public function credential(Request $request): Response
285327
continue;
286328
}
287329

330+
/** @psalm-suppress ArgumentTypeCoercion */
288331
$disclosure = $this->verifiableCredentials->disclosureFactory()->build(
289332
value: $attributeValue,
290333
name: $claimName,
291-
path: is_array($credentialClaimPath) ? $credentialClaimPath : [],
334+
path: $credentialClaimPath,
292335
saltBlacklist: $disclosureBag->salts(),
293336
);
294337

@@ -389,6 +432,10 @@ public function credential(Request $request): Response
389432
);
390433
}
391434

435+
if ($verifiableCredential === null) {
436+
throw new OpenIdException('Invalid credential format ID.');
437+
}
438+
392439
$this->loggerService->debug('response', [
393440
'credentials' => [
394441
['credential' => $verifiableCredential->getToken()],
@@ -407,15 +454,22 @@ public function credential(Request $request): Response
407454

408455
/**
409456
* Helper method to set a claim value at a path. Supports creating nested arrays dynamically.
457+
* @psalm-suppress UnusedVariable, MixedAssignment
458+
* @param array-key[] $path
410459
*/
411460
protected function setCredentialClaimValue(array &$claims, array $path, mixed $value): void
412461
{
413462
$temp = &$claims;
414463

415464
foreach ($path as $key) {
465+
if (!is_array($temp)) {
466+
$temp = [];
467+
}
468+
416469
if (!isset($temp[$key])) {
417470
$temp[$key] = [];
418471
}
472+
419473
$temp = &$temp[$key];
420474
}
421475

0 commit comments

Comments
 (0)