1818
1919package org .keycloak .testsuite .federation ;
2020
21+ import java .io .IOException ;
22+ import java .util .ArrayList ;
2123import java .util .HashSet ;
2224import java .util .List ;
2325import java .util .Map ;
2426import java .util .Set ;
25- import java .util .stream .Collectors ;
2627import java .util .stream .Stream ;
2728
2829import org .jboss .logging .Logger ;
3334import org .keycloak .credential .CredentialInputValidator ;
3435import org .keycloak .credential .CredentialModel ;
3536import org .keycloak .credential .hash .PasswordHashProvider ;
36- import org .keycloak .credential .hash .Pbkdf2Sha512PasswordHashProviderFactory ;
3737import org .keycloak .models .GroupModel ;
3838import org .keycloak .models .KeycloakSession ;
3939import org .keycloak .models .OTPPolicy ;
4343import org .keycloak .models .UserModel ;
4444import org .keycloak .models .cache .UserCache ;
4545import org .keycloak .models .credential .PasswordUserCredentialModel ;
46+ import org .keycloak .models .credential .RecoveryAuthnCodesCredentialModel ;
47+ import org .keycloak .models .utils .KeycloakModelUtils ;
4648import org .keycloak .models .utils .TimeBasedOTP ;
4749import org .keycloak .storage .StorageId ;
4850import org .keycloak .storage .UserStorageProvider ;
5153import org .keycloak .storage .user .UserLookupProvider ;
5254import org .keycloak .storage .user .UserQueryProvider ;
5355import org .keycloak .storage .user .UserRegistrationProvider ;
56+ import org .keycloak .util .JsonSerialization ;
5457
5558/**
5659 * UserStorage implementation created in Keycloak 4.8.3. It is used for backwards compatibility testing. Future Keycloak versions
5760 * should work fine without a need to change the code of this provider.
58- *
61+ * <p>
5962 * TODO: Have some good mechanims to make sure that source code of this provider is really compatible with Keycloak 4.8.3
6063 *
6164 * @author <a href="mailto:[email protected] ">Marek Posolda</a> @@ -89,7 +92,7 @@ public UserModel getUserById(RealmModel realm, String id) {
8992 }
9093
9194 private UserModel createUser (RealmModel realm , String username ) {
92- return new AbstractUserAdapterFederatedStorage (session , realm , model ) {
95+ return new AbstractUserAdapterFederatedStorage (session , realm , model ) {
9396 @ Override
9497 public String getUsername () {
9598 return username ;
@@ -107,7 +110,8 @@ public void setUsername(String username1) {
107110 @ Override
108111 public boolean supportsCredentialType (String credentialType ) {
109112 if (CredentialModel .PASSWORD .equals (credentialType )
110- || isOTPType (credentialType )) {
113+ || isOTPType (credentialType )
114+ || credentialType .equals (RecoveryAuthnCodesCredentialModel .TYPE )) {
111115 return true ;
112116 } else {
113117 log .infof ("Unsupported credential type: %s" , credentialType );
@@ -172,6 +176,7 @@ public boolean updateCredential(RealmModel realm, UserModel user, CredentialInpu
172176 OTPPolicy otpPolicy = session .getContext ().getRealm ().getOTPPolicy ();
173177
174178 CredentialModel newOTP = new CredentialModel ();
179+ newOTP .setId (KeycloakModelUtils .generateId ());
175180 newOTP .setType (input .getType ());
176181 long createdDate = Time .currentTimeMillis ();
177182 newOTP .setCreatedDate (createdDate );
@@ -184,6 +189,15 @@ public boolean updateCredential(RealmModel realm, UserModel user, CredentialInpu
184189
185190 users .get (translateUserName (user .getUsername ())).otp = newOTP ;
186191
192+ return true ;
193+ } else if (input .getType ().equals (RecoveryAuthnCodesCredentialModel .TYPE )) {
194+ CredentialModel recoveryCodesModel = new CredentialModel ();
195+ recoveryCodesModel .setId (KeycloakModelUtils .generateId ());
196+ recoveryCodesModel .setType (input .getType ());
197+ recoveryCodesModel .setCredentialData (input .getChallengeResponse ());
198+ long createdDate = Time .currentTimeMillis ();
199+ recoveryCodesModel .setCreatedDate (createdDate );
200+ users .get (translateUserName (user .getUsername ())).recoveryCodes = recoveryCodesModel ;
187201 return true ;
188202 } else {
189203 log .infof ("Attempt to update unsupported credential of type: %s" , input .getType ());
@@ -213,6 +227,30 @@ private MyUser getMyUser(UserModel user) {
213227 return users .get (translateUserName (user .getUsername ()));
214228 }
215229
230+ @ Override
231+ public Stream <CredentialModel > getCredentials (RealmModel realm , UserModel user ) {
232+ var myUser = getMyUser (user );
233+ RecoveryAuthnCodesCredentialModel model ;
234+ List <CredentialModel > credentialModels = new ArrayList <>();
235+ if (myUser .recoveryCodes != null ) {
236+ try {
237+ model = RecoveryAuthnCodesCredentialModel .createFromValues (
238+ JsonSerialization .readValue (myUser .recoveryCodes .getCredentialData (), List .class ),
239+ myUser .recoveryCodes .getCreatedDate (),
240+ myUser .recoveryCodes .getUserLabel ()
241+ );
242+ credentialModels .add (model );
243+ } catch (IOException e ) {
244+ log .error ("Could not deserialize credential of type: recovery-codes" );
245+ }
246+ }
247+ if (myUser .otp != null ) {
248+ credentialModels .add (myUser .getOtp ());
249+ }
250+
251+ return credentialModels .stream ();
252+ }
253+
216254 @ Override
217255 public Stream <String > getDisableableCredentialTypesStream (RealmModel realm , UserModel user ) {
218256 Set <String > types = new HashSet <>();
@@ -234,6 +272,8 @@ public boolean isConfiguredFor(RealmModel realm, UserModel user, String credenti
234272
235273 if (isOTPType (credentialType ) && myUser .otp != null ) {
236274 return true ;
275+ } else if (credentialType .equals (RecoveryAuthnCodesCredentialModel .TYPE ) && myUser .recoveryCodes != null ) {
276+ return true ;
237277 } else {
238278 log .infof ("Not supported credentialType '%s' for user '%s'" , credentialType , user .getUsername ());
239279 return false ;
@@ -283,7 +323,22 @@ public boolean isValid(RealmModel realm, UserModel user, CredentialInput input)
283323 TimeBasedOTP validator = new TimeBasedOTP (storedOTPCredential .getAlgorithm (), storedOTPCredential .getDigits (),
284324 storedOTPCredential .getPeriod (), realm .getOTPPolicy ().getLookAheadWindow ());
285325 return validator .validateTOTP (otpCredential .getValue (), storedOTPCredential .getValue ().getBytes ());
286- } else {
326+ } else if (input .getType ().equals (RecoveryAuthnCodesCredentialModel .TYPE )) {
327+ CredentialModel storedRecoveryKeys = myUser .recoveryCodes ;
328+ if (storedRecoveryKeys == null ) {
329+ log .warnf ("Not found credential for the user %s" , user .getUsername ());
330+ return false ;
331+ }
332+ List generatedKeys ;
333+ try {
334+ generatedKeys = JsonSerialization .readValue (storedRecoveryKeys .getCredentialData (), List .class );
335+ } catch (IOException e ) {
336+ log .warnf ("Cannot deserialize recovery keys credential for the user %s" , user .getUsername ());
337+ return false ;
338+ }
339+
340+ return generatedKeys .stream ().anyMatch (key -> key .equals (input .getChallengeResponse ()));
341+ } else {
287342 log .infof ("Not supported to validate credential of type '%s' for user '%s'" , input .getType (), user .getUsername ());
288343 return false ;
289344 }
@@ -369,6 +424,7 @@ static class MyUser {
369424 private String username ;
370425 private CredentialModel hashedPassword ;
371426 private CredentialModel otp ;
427+ private CredentialModel recoveryCodes ;
372428
373429 private MyUser (String username ) {
374430 this .username = username ;
@@ -377,6 +433,10 @@ private MyUser(String username) {
377433 public CredentialModel getOtp () {
378434 return otp ;
379435 }
436+
437+ public CredentialModel getRecoveryCodes () {
438+ return recoveryCodes ;
439+ }
380440 }
381441
382442
0 commit comments