77use Base64Url \Base64Url ;
88use League \OAuth2 \Server \ResourceServer ;
99use SimpleSAML \Module \oidc \Bridges \PsrHttpBridge ;
10+ use SimpleSAML \Module \oidc \Entities \AccessTokenEntity ;
1011use SimpleSAML \Module \oidc \ModuleConfig ;
1112use SimpleSAML \Module \oidc \Repositories \AccessTokenRepository ;
1213use SimpleSAML \Module \oidc \Repositories \UserRepository ;
2324use SimpleSAML \OpenID \Codebooks \JwtTypesEnum ;
2425use SimpleSAML \OpenID \Did ;
2526use SimpleSAML \OpenID \Exceptions \OpenId4VciProofException ;
27+ use SimpleSAML \OpenID \Exceptions \OpenIdException ;
2628use SimpleSAML \OpenID \Jwk ;
2729use SimpleSAML \OpenID \VerifiableCredentials ;
2830use 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