66
77use DateTimeImmutable ;
88use RuntimeException ;
9+ use SimpleSAML \Error \Exception ;
910use SimpleSAML \Module \oidc \Bridges \SspBridge ;
1011use SimpleSAML \Module \oidc \Codebooks \ParametersEnum ;
1112use SimpleSAML \Module \oidc \Entities \ScopeEntity ;
1213use SimpleSAML \Module \oidc \Entities \UserEntity ;
13- use SimpleSAML \Module \oidc \Exceptions \OidcException ;
1414use SimpleSAML \Module \oidc \Factories \Entities \AuthCodeEntityFactory ;
1515use SimpleSAML \Module \oidc \Factories \Entities \ClientEntityFactory ;
1616use SimpleSAML \Module \oidc \Factories \Entities \UserEntityFactory ;
2121use SimpleSAML \Module \oidc \Services \LoggerService ;
2222use SimpleSAML \OpenID \Codebooks \ClaimsEnum ;
2323use SimpleSAML \OpenID \Codebooks \GrantTypesEnum ;
24- use SimpleSAML \OpenID \Exceptions \OpenIdException ;
2524use SimpleSAML \OpenID \VerifiableCredentials ;
25+ use SimpleSAML \OpenID \VerifiableCredentials \TxCode ;
2626
2727class CredentialOfferUriFactory
2828{
@@ -37,6 +37,7 @@ public function __construct(
3737 protected readonly LoggerService $ loggerService ,
3838 protected readonly UserRepository $ userRepository ,
3939 protected readonly UserEntityFactory $ userEntityFactory ,
40+ protected readonly EmailFactory $ emailFactory ,
4041 ) {
4142 }
4243
@@ -47,6 +48,8 @@ public function __construct(
4748 public function buildPreAuthorized (
4849 array $ credentialConfigurationIds ,
4950 array $ userAttributes ,
51+ bool $ useTxCode = false ,
52+ string $ userEmailAttributeName = null ,
5053 ): string {
5154 if (empty ($ credentialConfigurationIds )) {
5255 throw new RuntimeException ('No credential configuration IDs provided. ' );
@@ -62,21 +65,6 @@ public function buildPreAuthorized(
6265 throw new RuntimeException ('Unsupported credential configuration IDs provided. ' );
6366 }
6467
65- /* TODO mivanci TX Code handling
66- $email = $this->emailFactory->build(
67- subject: 'VC Issuance Transaction code',
68- 69- );
70-
71- $email->setData(['Transaction Code' => '1234']);
72- try {
73- $email->send();
74- $this->sessionMessagesService->addMessage('Email with tx code sent to: [email protected] '); 75- } catch (Exception $e) {
76- $this->sessionMessagesService->addMessage('Error emailing tx code.');
77- }
78- */
79-
8068 // TODO mivanci Wallet (client) credential_offer_endpoint metadata
8169 // https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#client-metadata
8270
@@ -103,13 +91,19 @@ public function buildPreAuthorized(
10391 } catch (\Throwable $ e ) {
10492 $ this ->loggerService ->warning (
10593 'Could not extract user identifier from user attributes: ' . $ e ->getMessage (),
94+ $ userAttributes ,
10695 );
10796 }
10897
10998 if ($ userId === null ) {
99+ $ this ->loggerService ->warning ('Falling back to user attributes hash for user identifier. ' );
110100 $ sortedAttributes = $ userAttributes ;
111101 $ this ->verifiableCredentials ->helpers ()->arr ()->hybridSort ($ sortedAttributes );
112- $ userId = 'vci_preauthz_ ' . hash ('sha256 ' , serialize ($ sortedAttributes ));
102+ $ userId = 'vci_credential_offer_preauthz_ ' . hash ('sha256 ' , serialize ($ sortedAttributes ));
103+ $ this ->loggerService ->info (
104+ 'Generated user identifier based on user attributes: ' . $ userId ,
105+ $ userAttributes ,
106+ );
113107 }
114108
115109 $ oldUserEntity = $ this ->userRepository ->getUserEntityByIdentifier ($ userId );
@@ -136,14 +130,28 @@ public function buildPreAuthorized(
136130 throw new RuntimeException ('Failed to generate Authorization Code ID. ' );
137131 }
138132
139- // TODO mivanci Add indication of preAuthZ code to the auth code table.
133+ $ txCode = null ;
134+ $ userEmail = null ;
135+ $ userEmailAttributeName ??= $ this ->moduleConfig ->getDefaultUsersEmailAttributeName ();
136+ if ($ useTxCode ) {
137+ $ userEmail = $ this ->getUserEmail ($ userEmailAttributeName , $ userAttributes );
138+ $ txCodeDescription = 'Please provide the one-time code that was sent to e-mail ' . $ userEmail ;
139+ $ txCode = $ this ->buildTxCode ($ txCodeDescription );
140+ $ this ->loggerService ->debug (
141+ 'Generated TxCode for sending by email: ' . $ txCode ->getCodeAsString (),
142+ $ txCode ->jsonSerialize (),
143+ );
144+ }
145+
140146 $ authCode = $ this ->authCodeEntityFactory ->fromData (
141147 id: $ authCodeId ,
142148 client: $ client ,
143149 scopes: $ scopes ,
144150 expiryDateTime: (new DateTimeImmutable ())->add ($ this ->moduleConfig ->getAuthCodeDuration ()),
145151 userIdentifier: $ userId ,
146152 redirectUri: 'openid-credential-offer:// ' ,
153+ isPreAuthorized: true ,
154+ txCode: $ txCode instanceof VerifiableCredentials \TxCode ? $ txCode ->getCodeAsString () : null ,
147155 );
148156 $ this ->authCodeRepository ->persistNewAuthCode ($ authCode );
149157
@@ -156,17 +164,22 @@ public function buildPreAuthorized(
156164 ClaimsEnum::Grants->value => [
157165 GrantTypesEnum::PreAuthorizedCode->value => [
158166 ClaimsEnum::PreAuthorizedCode->value => $ authCode ->getIdentifier (),
159- // TODO mivanci support for TxCode
160- // ClaimsEnum::TxCode->value => [
161- // ClaimsEnum::InputMode->value => 'numeric',
162- // ClaimsEnum::Length->value => 6,
163- // ClaimsEnum::Description->value => 'Sent to user mail',
164- // ],
167+ ...(array_filter (
168+ [
169+ ClaimsEnum::TxCode->value => $ txCode instanceof VerifiableCredentials \TxCode ?
170+ $ txCode ->jsonSerialize () :
171+ null ,
172+ ],
173+ )),
165174 ],
166175 ],
167176 ],
168177 );
169178
179+ if ($ txCode instanceof VerifiableCredentials \TxCode && $ userEmail !== null ) {
180+ $ this ->sendTxCodeByEmail ($ txCode , $ userEmail );
181+ }
182+
170183 $ credentialOfferValue = $ credentialOffer ->jsonSerialize ();
171184 $ parameterName = ParametersEnum::CredentialOfferUri->value ;
172185 if (is_array ($ credentialOfferValue )) {
@@ -176,4 +189,60 @@ public function buildPreAuthorized(
176189
177190 return "openid-credential-offer://? $ parameterName= $ credentialOfferValue " ;
178191 }
192+
193+ /**
194+ * @param mixed[] $userAttributes
195+ * @throws RuntimeException
196+ */
197+ public function getUserEmail (string $ userEmailAttributeName , array $ userAttributes ): string
198+ {
199+ try {
200+ $ userEmail = $ this ->sspBridge ->utils ()->attributes ()->getExpectedAttribute (
201+ $ userAttributes ,
202+ $ userEmailAttributeName ,
203+ true ,
204+ );
205+ } catch (Exception $ e ) {
206+ throw new RuntimeException ('Could not extract user email from user attributes: ' . $ e ->getMessage ());
207+ }
208+
209+ if (!is_string ($ userEmail )) {
210+ throw new RuntimeException ('User email attribute value is not a string. ' );
211+ }
212+
213+ return $ userEmail ;
214+ }
215+
216+ public function buildTxCode (
217+ string $ description ,
218+ int |string $ txCode = null ,
219+ ): TxCode {
220+ $ txCode ??= rand (1000 , 9999 );
221+
222+ return $ this ->verifiableCredentials ->txCodeFactory ()->build (
223+ $ txCode ,
224+ $ description ,
225+ );
226+ }
227+
228+ /**
229+ * @throws OidcException
230+ */
231+ public function sendTxCodeByEmail (TxCode $ txCode , string $ email , string $ subject = null ): void
232+ {
233+ $ subject ??= 'Your one-time code ' ;
234+
235+ $ email = $ this ->emailFactory ->build (
236+ subject: $ subject ,
237+ to: $ email ,
238+ );
239+
240+ $ email ->setText ('Use the following code to complete the transaction. ' );
241+
242+ $ email ->setData ([
243+ 'Transaction Code ' => $ txCode ->getCodeAsString (),
244+ ]);
245+
246+ $ email ->send ();
247+ }
179248}
0 commit comments