diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml old mode 100644 new mode 100755 index 9325a40964..27869eb1a5 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml @@ -1,6 +1,11 @@ - + + + + + + @@ -24,5 +29,44 @@ + + + + + + + + + + + + + + + + + + + + + ACTION = 0 + + + + ACTION = 1 + + + + ACTION = 2 + + + + ACTION = 3 + + + + + + - \ No newline at end of file + diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index 79b730a727..5bcb77ee99 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -28,6 +28,7 @@ org.keycloak.models.sessions.jpa.entities.ClientSessionEntity org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity + org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java new file mode 100755 index 0000000000..0ca9322076 --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java @@ -0,0 +1,127 @@ +package org.keycloak.models; + +import java.util.HashMap; +import java.util.Map; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public class AuthenticatorModel { + + public enum Requirement { + REQUIRED, + OPTIONAL, + ALTERNATIVE + } + + private String id; + private String alias; + private String providerId; + private boolean masterAuthenticator; + private boolean formBased; + private String inputPage; + private String actionUrl; + private String setupUrl; + private Requirement requirement; + private boolean userSetupAllowed; + private int priority; + private Map config = new HashMap(); + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public boolean isFormBased() { + return formBased; + } + + public void setFormBased(boolean formBased) { + this.formBased = formBased; + } + + public String getInputPage() { + return inputPage; + } + + public void setInputPage(String inputPage) { + this.inputPage = inputPage; + } + + public String getActionUrl() { + return actionUrl; + } + + public void setActionUrl(String actionUrl) { + this.actionUrl = actionUrl; + } + + public String getSetupUrl() { + return setupUrl; + } + + public void setSetupUrl(String setupUrl) { + this.setupUrl = setupUrl; + } + + public Requirement getRequirement() { + return requirement; + } + + public void setRequirement(Requirement requirement) { + this.requirement = requirement; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } + + public boolean isUserSetupAllowed() { + return userSetupAllowed; + } + + public void setUserSetupAllowed(boolean userSetupAllowed) { + this.userSetupAllowed = userSetupAllowed; + } + + public boolean isMasterAuthenticator() { + return masterAuthenticator; + } + + public void setMasterAuthenticator(boolean masterAuthenticator) { + this.masterAuthenticator = masterAuthenticator; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java index 7c197a34a1..2c66df3f4e 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java @@ -1,5 +1,6 @@ package org.keycloak.models; +import java.util.Map; import java.util.Set; /** @@ -31,6 +32,14 @@ public interface ClientSessionModel { public Set getProtocolMappers(); public void setProtocolMappers(Set protocolMappers); + public Map getAuthenticators(); + public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status); + public void setAuthenticatorStatus(Map status); + public UserModel getAuthenticatedUser(); + public void setAuthenticatedUser(UserModel user); + + + /** * Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc. * diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java index 6761920add..2088abce2d 100755 --- a/model/api/src/main/java/org/keycloak/models/UserModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserModel.java @@ -35,8 +35,12 @@ public interface UserModel { Map getAttributes(); - Set getRequiredActions(); - + Set getRequiredActions(); + + void addRequiredAction(String action); + + void removeRequiredAction(String action); + void addRequiredAction(RequiredAction action); void removeRequiredAction(RequiredAction action); @@ -65,6 +69,14 @@ public interface UserModel { void updateCredentialDirectly(UserCredentialValueModel cred); + /** + * Is the use configured to use this credential type + * + * @param type + * @return + */ + boolean configuredForCredentialType(String type); + Set getRealmRoleMappings(); Set getClientRoleMappings(ClientModel app); boolean hasRole(RoleModel role); diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java index 1db82b6ff8..769fbcaef3 100755 --- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java @@ -1,6 +1,7 @@ package org.keycloak.models; import java.util.List; +import java.util.Map; /** * @author Stian Thorgersen @@ -36,6 +37,13 @@ public interface UserSessionModel { List getClientSessions(); + public static enum AuthenticatorStatus { + SUCCESS, + SETUP_REQUIRED, + ATTEMPTED, + SKIPPED + } + public String getNote(String name); public void setNote(String name, String value); public void removeNote(String name); diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java index d2a20988f8..064697b00a 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java @@ -24,7 +24,7 @@ public class UserEntity extends AbstractIdentifiableEntity { private List roleIds; private Map attributes; - private List requiredActions; + private List requiredActions; private List credentials = new ArrayList(); private List federatedIdentities; private String federationLink; @@ -109,11 +109,11 @@ public class UserEntity extends AbstractIdentifiableEntity { this.attributes = attributes; } - public List getRequiredActions() { + public List getRequiredActions() { return requiredActions; } - public void setRequiredActions(List requiredActions) { + public void setRequiredActions(List requiredActions) { this.requiredActions = requiredActions; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 6148f8ebed..84248fdada 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -56,8 +56,8 @@ public class ModelToRepresentation { rep.setFederationLink(user.getFederationLink()); List reqActions = new ArrayList(); - for (UserModel.RequiredAction ra : user.getRequiredActions()){ - reqActions.add(ra.name()); + for (String ra : user.getRequiredActions()){ + reqActions.add(ra); } rep.setRequiredActions(reqActions); diff --git a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java index 8ac8f5b1b6..f0256473f8 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java +++ b/model/api/src/main/java/org/keycloak/models/utils/UserModelDelegate.java @@ -73,10 +73,25 @@ public class UserModelDelegate implements UserModel { } @Override - public Set getRequiredActions() { + public Set getRequiredActions() { return delegate.getRequiredActions(); } + @Override + public void addRequiredAction(String action) { + delegate.addRequiredAction(action); + } + + @Override + public void removeRequiredAction(String action) { + delegate.removeRequiredAction(action); + } + + @Override + public boolean configuredForCredentialType(String type) { + return delegate.configuredForCredentialType(type); + } + @Override public void addRequiredAction(RequiredAction action) { delegate.addRequiredAction(action); @@ -211,4 +226,5 @@ public class UserModelDelegate implements UserModel { public boolean revokeConsentForClient(String clientId) { return delegate.revokeConsentForClient(clientId); } + } diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java index 6e622cca11..eb4277f621 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java @@ -183,28 +183,53 @@ public class UserAdapter implements UserModel, Comparable { } @Override - public Set getRequiredActions() { - List requiredActions = user.getRequiredActions(); - if (requiredActions == null) requiredActions = new ArrayList(); + public Set getRequiredActions() { + List requiredActions = user.getRequiredActions(); + if (requiredActions == null) requiredActions = new ArrayList(); return new HashSet(requiredActions); } @Override public void addRequiredAction(RequiredAction action) { - List requiredActions = user.getRequiredActions(); - if (requiredActions == null) requiredActions = new ArrayList(); - if (!requiredActions.contains(action)) requiredActions.add(action); + String actionName = action.name(); + addRequiredAction(actionName); + } + + @Override + public void addRequiredAction(String actionName) { + List requiredActions = user.getRequiredActions(); + if (requiredActions == null) requiredActions = new ArrayList<>(); + if (!requiredActions.contains(actionName)) { + requiredActions.add(actionName); + } user.setRequiredActions(requiredActions); } @Override public void removeRequiredAction(RequiredAction action) { - List requiredActions = user.getRequiredActions(); + String actionName = action.name(); + removeRequiredAction(actionName); + } + + @Override + public void removeRequiredAction(String actionName) { + List requiredActions = user.getRequiredActions(); if (requiredActions == null) return; - requiredActions.remove(action); + requiredActions.remove(actionName); user.setRequiredActions(requiredActions); } + @Override + public boolean configuredForCredentialType(String type) { + List creds = getCredentialsDirectly(); + for (UserCredentialValueModel cred : creds) { + if (cred.getType().equals(type)) return true; + } + return false; + } + + + @Override public boolean isTotp() { return user.isTotp(); diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java index 10336fd592..dc159ceb67 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java @@ -102,7 +102,7 @@ public class UserAdapter implements UserModel { } @Override - public Set getRequiredActions() { + public Set getRequiredActions() { if (updated != null) return updated.getRequiredActions(); return cached.getRequiredActions(); } @@ -119,6 +119,27 @@ public class UserAdapter implements UserModel { updated.removeRequiredAction(action); } + @Override + public void addRequiredAction(String action) { + getDelegateForUpdate(); + updated.addRequiredAction(action); + } + + @Override + public void removeRequiredAction(String action) { + getDelegateForUpdate(); + updated.removeRequiredAction(action); + } + + @Override + public boolean configuredForCredentialType(String type) { + List creds = getCredentialsDirectly(); + for (UserCredentialValueModel cred : creds) { + if (cred.getType().equals(type)) return true; + } + return false; + } + @Override public String getFirstName() { if (updated != null) return updated.getFirstName(); diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java index ca9a24c0ff..eb9d418cb7 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java @@ -28,8 +28,8 @@ public class CachedUser { private boolean enabled; private boolean totp; private String federationLink; - private Map attributes = new HashMap(); - private Set requiredActions = new HashSet(); + private Map attributes = new HashMap<>(); + private Set requiredActions = new HashSet<>(); private Set roleMappings = new HashSet(); @@ -96,7 +96,7 @@ public class CachedUser { return attributes; } - public Set getRequiredActions() { + public Set getRequiredActions() { return requiredActions; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 5f92104b26..7c9086bf3b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -139,8 +139,8 @@ public class UserAdapter implements UserModel { } @Override - public Set getRequiredActions() { - Set result = new HashSet(); + public Set getRequiredActions() { + Set result = new HashSet<>(); for (UserRequiredActionEntity attr : user.getRequiredActions()) { result.add(attr.getAction()); } @@ -149,13 +149,19 @@ public class UserAdapter implements UserModel { @Override public void addRequiredAction(RequiredAction action) { + String actionName = action.name(); + addRequiredAction(actionName); + } + + @Override + public void addRequiredAction(String actionName) { for (UserRequiredActionEntity attr : user.getRequiredActions()) { - if (attr.getAction().equals(action)) { + if (attr.getAction().equals(actionName)) { return; } } UserRequiredActionEntity attr = new UserRequiredActionEntity(); - attr.setAction(action); + attr.setAction(actionName); attr.setUser(user); em.persist(attr); user.getRequiredActions().add(attr); @@ -163,16 +169,31 @@ public class UserAdapter implements UserModel { @Override public void removeRequiredAction(RequiredAction action) { + String actionName = action.name(); + removeRequiredAction(actionName); + } + + @Override + public void removeRequiredAction(String actionName) { Iterator it = user.getRequiredActions().iterator(); while (it.hasNext()) { UserRequiredActionEntity attr = it.next(); - if (attr.getAction().equals(action)) { + if (attr.getAction().equals(actionName)) { it.remove(); em.remove(attr); } } } + @Override + public boolean configuredForCredentialType(String type) { + List creds = getCredentialsDirectly(); + for (UserCredentialValueModel cred : creds) { + if (cred.getType().equals(type)) return true; + } + return false; + } + @Override public String getFirstName() { return user.getFirstName(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java index 1818b75644..a583815d3d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java @@ -33,14 +33,14 @@ public class UserRequiredActionEntity { protected UserEntity user; @Id - @Column(name="ACTION") - protected UserModel.RequiredAction action; + @Column(name="REQUIRED_ACTION") + protected String action; - public UserModel.RequiredAction getAction() { + public String getAction() { return action; } - public void setAction(UserModel.RequiredAction action) { + public void setAction(String action) { this.action = action; } @@ -56,12 +56,12 @@ public class UserRequiredActionEntity { protected UserEntity user; - protected UserModel.RequiredAction action; + protected String action; public Key() { } - public Key(UserEntity user, UserModel.RequiredAction action) { + public Key(UserEntity user, String action) { this.user = user; this.action = action; } @@ -70,7 +70,7 @@ public class UserRequiredActionEntity { return user; } - public UserModel.RequiredAction getAction() { + public String getAction() { return action; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 1a4a9b5196..f7895f2992 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -159,8 +159,8 @@ public class UserAdapter extends AbstractMongoAdapter implement @Override - public Set getRequiredActions() { - Set result = new HashSet(); + public Set getRequiredActions() { + Set result = new HashSet(); if (user.getRequiredActions() != null) { result.addAll(user.getRequiredActions()); } @@ -169,12 +169,24 @@ public class UserAdapter extends AbstractMongoAdapter implement @Override public void addRequiredAction(RequiredAction action) { - getMongoStore().pushItemToList(user, "requiredActions", action, true, invocationContext); + String actionName = action.name(); + addRequiredAction(actionName); + } + + @Override + public void addRequiredAction(String actionName) { + getMongoStore().pushItemToList(user, "requiredActions", actionName, true, invocationContext); } @Override public void removeRequiredAction(RequiredAction action) { - getMongoStore().pullItemFromList(user, "requiredActions", action, invocationContext); + String actionName = action.name(); + removeRequiredAction(actionName); + } + + @Override + public void removeRequiredAction(String actionName) { + getMongoStore().pullItemFromList(user, "requiredActions", actionName, invocationContext); } @Override @@ -320,6 +332,16 @@ public class UserAdapter extends AbstractMongoAdapter implement return result; } + @Override + public boolean configuredForCredentialType(String type) { + List creds = getCredentialsDirectly(); + for (UserCredentialValueModel cred : creds) { + if (cred.getType().equals(type)) return true; + } + return false; + } + + @Override public void updateCredentialDirectly(UserCredentialValueModel credModel) { CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType()); diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java old mode 100644 new mode 100755 index cc2c8ccb75..ddf42d58f4 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java @@ -5,11 +5,13 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import java.util.HashMap; +import java.util.Map; import java.util.Set; /** @@ -165,5 +167,30 @@ public class ClientSessionAdapter implements ClientSessionModel { void update() { provider.getTx().replace(cache, entity.getId(), entity); } + @Override + public Map getAuthenticators() { + return entity.getAuthenticatorStatus(); + } + + @Override + public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + entity.getAuthenticatorStatus().put(authenticator, status); + + } + + @Override + public void setAuthenticatorStatus(Map status) { + entity.setAuthenticatorStatus(status); + } + + @Override + public UserModel getAuthenticatedUser() { + return session.users().getUserById(entity.getAuthUserId(), realm); } + + @Override + public void setAuthenticatedUser(UserModel user) { + entity.setAuthUserId(user.getId()); + + } } diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java index 38d2ea56ac..cc8ce200ba 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java @@ -1,7 +1,9 @@ package org.keycloak.models.sessions.infinispan.entities; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.UserSessionModel; +import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -27,6 +29,8 @@ public class ClientSessionEntity extends SessionEntity { private Set roles; private Set protocolMappers; private Map notes; + private Map authenticatorStatus = new HashMap<>(); + private String authUserId; public String getClient() { return client; @@ -107,4 +111,20 @@ public class ClientSessionEntity extends SessionEntity { public void setNotes(Map notes) { this.notes = notes; } + + public Map getAuthenticatorStatus() { + return authenticatorStatus; + } + + public void setAuthenticatorStatus(Map authenticatorStatus) { + this.authenticatorStatus = authenticatorStatus; + } + + public String getAuthUserId() { + return authUserId; + } + + public void setAuthUserId(String authUserId) { + this.authUserId = authUserId; + } } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java index 74e795f830..68e3b53ee5 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java @@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity; @@ -14,6 +15,7 @@ import org.keycloak.models.sessions.jpa.entities.UserSessionEntity; import javax.persistence.EntityManager; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; /** @@ -210,4 +212,29 @@ public class ClientSessionAdapter implements ClientSessionModel { } } } + + @Override + public Map getAuthenticators() { + return null; + } + + @Override + public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + + } + + @Override + public void setAuthenticatorStatus(Map status) { + + } + + @Override + public UserModel getAuthenticatedUser() { + return null; + } + + @Override + public void setAuthenticatedUser(UserModel user) { + + } } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java new file mode 100755 index 0000000000..49afbab2f7 --- /dev/null +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionAuthStatusEntity.java @@ -0,0 +1,111 @@ +package org.keycloak.models.sessions.jpa.entities; + +import org.keycloak.models.UserSessionModel; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name = "removeClientSessionAuthStatusByUser", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and s.userId = :userId))"), + @NamedQuery(name = "removeClientSessionAuthStatusByClient", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.clientId = :clientId and c.realmId = :realmId)"), + @NamedQuery(name = "removeClientSessionAuthStatusByRealm", query="delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.realmId = :realmId)"), + @NamedQuery(name = "removeClientSessionAuthStatusByExpired", query = "delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IN (select s from UserSessionEntity s where s.realmId = :realmId and (s.started < :maxTime or s.lastSessionRefresh < :idleTime)))"), + @NamedQuery(name = "removeDetachedClientSessionAuthStatusByExpired", query = "delete from ClientSessionAuthStatusEntity r where r.clientSession IN (select c from ClientSessionEntity c where c.session IS NULL and c.realmId = :realmId and c.timestamp < :maxTime )") +}) +@Table(name="CLIENT_SESSION_AUTH_STATUS") +@Entity +@IdClass(ClientSessionAuthStatusEntity.Key.class) +public class ClientSessionAuthStatusEntity { + + @Id + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "CLIENT_SESSION") + protected ClientSessionEntity clientSession; + + @Id + @Column(name = "AUTHENTICATOR") + protected String authenticator; + @Column(name = "STATUS") + protected UserSessionModel.AuthenticatorStatus status; + + public String getAuthenticator() { + return authenticator; + } + + public void setAuthenticator(String authenticator) { + this.authenticator = authenticator; + } + + public UserSessionModel.AuthenticatorStatus getStatus() { + return status; + } + + public void setStatus(UserSessionModel.AuthenticatorStatus status) { + this.status = status; + } + + public ClientSessionEntity getClientSession() { + return clientSession; + } + + public void setClientSession(ClientSessionEntity clientSession) { + this.clientSession = clientSession; + } + + public static class Key implements Serializable { + + protected ClientSessionEntity clientSession; + + protected String authenticator; + + public Key() { + } + + public Key(ClientSessionEntity clientSession, String authenticator) { + this.clientSession = clientSession; + this.authenticator = authenticator; + } + + public ClientSessionEntity getClientSession() { + return clientSession; + } + + public String getAuthenticator() { + return authenticator; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (authenticator != null ? !authenticator.equals(key.authenticator) : key.authenticator != null) return false; + if (clientSession != null ? !clientSession.getId().equals(key.clientSession != null ? key.clientSession.getId() : null) : key.clientSession != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = clientSession != null ? clientSession.getId().hashCode() : 0; + result = 31 * result + (authenticator != null ? authenticator.hashCode() : 0); + return result; + } + } + +} diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java index 6f0c535539..7cc9062b99 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java @@ -57,6 +57,9 @@ public class ClientSessionEntity { @Column(name="ACTION") protected ClientSessionModel.Action action; + @Column(name="AUTH_USER_ID") + protected String userId; + @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession") protected Collection roles = new ArrayList(); @@ -66,6 +69,9 @@ public class ClientSessionEntity { @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession") protected Collection notes = new ArrayList(); + @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession") + protected Collection authanticatorStatus = new ArrayList<>(); + public String getId() { return id; } @@ -153,4 +159,20 @@ public class ClientSessionEntity { public void setAuthMethod(String authMethod) { this.authMethod = authMethod; } + + public Collection getAuthanticatorStatus() { + return authanticatorStatus; + } + + public void setAuthanticatorStatus(Collection authanticatorStatus) { + this.authanticatorStatus = authanticatorStatus; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java index daa6e3f572..cff4ca373d 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java @@ -4,10 +4,12 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.sessions.mem.entities.ClientSessionEntity; import org.keycloak.models.sessions.mem.entities.UserSessionEntity; +import java.util.Map; import java.util.Set; /** @@ -141,4 +143,30 @@ public class ClientSessionAdapter implements ClientSessionModel { public void setAuthMethod(String method) { entity.setAuthMethod(method); } + + @Override + public Map getAuthenticators() { + return entity.getAuthenticatorStatus(); + } + + @Override + public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + entity.getAuthenticatorStatus().put(authenticator, status); + + } + + @Override + public void setAuthenticatorStatus(Map status) { + entity.setAuthenticatorStatus(status); + } + + @Override + public UserModel getAuthenticatedUser() { + return session.users().getUserById(entity.getAuthUserId(), realm); } + + @Override + public void setAuthenticatedUser(UserModel user) { + entity.setAuthUserId(user.getId()); + + } } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java index ca304966ec..9823570c4c 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java @@ -1,6 +1,7 @@ package org.keycloak.models.sessions.mem.entities; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.UserSessionModel; import java.util.HashMap; import java.util.Map; @@ -14,6 +15,8 @@ public class ClientSessionEntity { private String id; private String clientId; private String realmId; + private Map authenticatorStatus = new HashMap<>(); + private String authUserId; private UserSessionEntity session; @@ -24,7 +27,7 @@ public class ClientSessionEntity { private ClientSessionModel.Action action; private Set roles; private Set protocolMappers; - private Map notes = new HashMap(); + private Map notes = new HashMap<>(); public String getId() { return id; @@ -109,4 +112,20 @@ public class ClientSessionEntity { public void setAuthMethod(String authMethod) { this.authMethod = authMethod; } + + public String getAuthUserId() { + return authUserId; + } + + public void setAuthUserId(String authUserId) { + this.authUserId = authUserId; + } + + public Map getAuthenticatorStatus() { + return authenticatorStatus; + } + + public void setAuthenticatorStatus(Map authenticatorStatus) { + this.authenticatorStatus = authenticatorStatus; + } } diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java index 649ffcf400..e5fd346d55 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java @@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity; import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity; @@ -12,6 +13,7 @@ import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -154,6 +156,37 @@ public class ClientSessionAdapter extends AbstractMongoAdapter getAuthenticators() { + return entity.getAuthenticatorStatus(); + } + + @Override + public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) { + entity.getAuthenticatorStatus().put(authenticator, status); + updateMongoEntity(); + + } + + @Override + public void setAuthenticatorStatus(Map status) { + entity.setAuthenticatorStatus(status); + updateMongoEntity(); + + } + + @Override + public UserModel getAuthenticatedUser() { + return session.users().getUserById(entity.getAuthUserId(), realm); + } + + @Override + public void setAuthenticatedUser(UserModel user) { + entity.setAuthUserId(user.getId()); + updateMongoEntity(); + + } + @Override public String getAuthMethod() { return entity.getAuthMethod(); diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java index b5abede7d9..ed8099dd90 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/entities/MongoClientSessionEntity.java @@ -4,6 +4,7 @@ import org.keycloak.connections.mongo.api.MongoCollection; import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.UserSessionModel; import org.keycloak.models.entities.AbstractIdentifiableEntity; import java.util.HashMap; @@ -29,6 +30,8 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme private List roles; private List protocolMappers; private Map notes = new HashMap(); + private Map authenticatorStatus = new HashMap<>(); + private String authUserId; public String getId() { return id; @@ -118,6 +121,22 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme this.sessionId = sessionId; } + public Map getAuthenticatorStatus() { + return authenticatorStatus; + } + + public void setAuthenticatorStatus(Map authenticatorStatus) { + this.authenticatorStatus = authenticatorStatus; + } + + public String getAuthUserId() { + return authUserId; + } + + public void setAuthUserId(String authUserId) { + this.authUserId = authUserId; + } + @Override public void afterRemove(MongoStoreInvocationContext context) { } diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java new file mode 100755 index 0000000000..6f630049b6 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -0,0 +1,358 @@ +package org.keycloak.authentication; + +import org.jboss.resteasy.spi.HttpRequest; +import org.keycloak.ClientConnection; +import org.keycloak.events.EventBuilder; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.BruteForceProtector; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ + +// +// setup +// cookie: master, alternative +// CERT_AUTH: alternative +// UserPassword: alternative +// OTP: optional +// CAPTHA: required +// +// scenario: username password +// * cookie, attempted +// * cert, attempated +// * usernamepassord, doesn't see form, sets challenge to form +// +// +// +// +// +// +// + + + +public class AuthenticationProcessor { + protected RealmModel realm; + protected UserSessionModel userSession; + protected ClientSessionModel clientSession; + protected ClientConnection connection; + protected UriInfo uriInfo; + protected KeycloakSession session; + protected List authenticators; + protected BruteForceProtector protector; + protected EventBuilder eventBuilder; + protected HttpRequest request; + + + public static enum Status { + SUCCESS, + CHALLENGE, + FAILURE_CHALLENGE, + FAILED, + ATTEMPTED + + } + public static enum Error { + INVALID_USER, + INVALID_CREDENTIALS, + CREDENTIAL_SETUP_REQUIRED, + USER_DISABLED, + USER_CONFLICT, + USER_TEMPORARILY_DISABLED, + INTERNAL_ERROR, + UNKNOWN_USER + } + + public RealmModel getRealm() { + return realm; + } + + public ClientSessionModel getClientSession() { + return clientSession; + } + + public ClientConnection getConnection() { + return connection; + } + + public UriInfo getUriInfo() { + return uriInfo; + } + + public KeycloakSession getSession() { + return session; + } + + + private class Result implements AuthenticatorContext { + AuthenticatorModel model; + Authenticator authenticator; + Status status; + Response challenge; + Error error; + + private Result(AuthenticatorModel model, Authenticator authenticator) { + this.model = model; + this.authenticator = authenticator; + } + + @Override + public AuthenticatorModel getModel() { + return model; + } + + @Override + public void setModel(AuthenticatorModel model) { + this.model = model; + } + + @Override + public Authenticator getAuthenticator() { + return authenticator; + } + + @Override + public void setAuthenticator(Authenticator authenticator) { + this.authenticator = authenticator; + } + + @Override + public Status getStatus() { + return status; + } + + @Override + public void success() { + this.status = Status.SUCCESS; + } + + @Override + public void failure(Error error) { + status = Status.FAILED; + this.error = error; + + } + + @Override + public void challenge(Response challenge) { + this.status = Status.CHALLENGE; + this.challenge = challenge; + + } + @Override + public void failureChallenge(Error error, Response challenge) { + this.error = error; + this.status = Status.FAILURE_CHALLENGE; + this.challenge = challenge; + + } + + @Override + public void attempted() { + this.status = Status.ATTEMPTED; + + } + + @Override + public UserModel getUser() { + return getClientSession().getAuthenticatedUser(); + } + + @Override + public void setUser(UserModel user) { + UserModel previousUser = getUser(); + if (previousUser != null && !user.getId().equals(previousUser.getId())) throw new AuthException(Error.USER_CONFLICT); + validateUser(user); + getClientSession().setAuthenticatedUser(user); + } + + @Override + public RealmModel getRealm() { + return AuthenticationProcessor.this.getRealm(); + } + + @Override + public ClientSessionModel getClientSession() { + return AuthenticationProcessor.this.getClientSession(); + } + + @Override + public ClientConnection getConnection() { + return AuthenticationProcessor.this.getConnection(); + } + + @Override + public UriInfo getUriInfo() { + return AuthenticationProcessor.this.getUriInfo(); + } + + @Override + public KeycloakSession getSession() { + return AuthenticationProcessor.this.getSession(); + } + + @Override + public HttpRequest getHttpRequest() { + return AuthenticationProcessor.this.request; + } + + @Override + public void attachUserSession(UserSessionModel userSession) { + AuthenticationProcessor.this.userSession = userSession; + } + + @Override + public BruteForceProtector getProtector() { + return AuthenticationProcessor.this.protector; + } + } + + public static class AuthException extends RuntimeException { + private Error error; + + public AuthException(Error error) { + this.error = error; + } + + public AuthException(String message, Error error) { + super(message); + this.error = error; + } + + public AuthException(String message, Throwable cause, Error error) { + super(message, cause); + this.error = error; + } + + public AuthException(Throwable cause, Error error) { + super(cause); + this.error = error; + } + + public AuthException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Error error) { + super(message, cause, enableSuppression, writableStackTrace); + this.error = error; + } + } + + public void logUserFailure() { + + } + + protected boolean isProcessed(UserSessionModel.AuthenticatorStatus status) { + return status == UserSessionModel.AuthenticatorStatus.SUCCESS || status == UserSessionModel.AuthenticatorStatus.SKIPPED + || status == UserSessionModel.AuthenticatorStatus.ATTEMPTED + || status == UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED; + } + + public Response authenticate() { + UserModel authUser = clientSession.getAuthenticatedUser(); + validateUser(authUser); + Response challenge = null; + Map previousAttempts = clientSession.getAuthenticators(); + for (AuthenticatorModel model : authenticators) { + UserSessionModel.AuthenticatorStatus oldStatus = previousAttempts.get(model.getAlias()); + if (isProcessed(oldStatus)) continue; + + AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getProviderId()); + Authenticator authenticator = factory.create(model); + if (authenticator.requiresUser() && authUser == null){ + if ( authenticator.requiresUser()) { + if (challenge != null) return challenge; + throw new AuthException(Error.UNKNOWN_USER); + } + } + if (authUser != null && model.getRequirement() == AuthenticatorModel.Requirement.ALTERNATIVE) { + clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SKIPPED); + continue; + } + authUser = clientSession.getAuthenticatedUser(); + + if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) { + if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) { + if (model.isUserSetupAllowed()) { + clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED); + authUser.addRequiredAction(authenticator.getRequiredAction()); + + } else { + throw new AuthException(Error.CREDENTIAL_SETUP_REQUIRED); + } + } + continue; + } + Result context = new Result(model, authenticator); + authenticator.authenticate(context); + Status result = context.getStatus(); + if (result == Status.SUCCESS){ + clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SUCCESS); + if (model.isMasterAuthenticator()) return authenticationComplete(); + continue; + } else if (result == Status.FAILED) { + throw new AuthException(context.error); + } else if (result == Status.CHALLENGE) { + if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) return context.challenge; + if (challenge != null) challenge = context.challenge; + continue; + } else if (result == Status.FAILURE_CHALLENGE) { + logUserFailure(); + return context.challenge; + } else if (result == Status.ATTEMPTED) { + if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS); + clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.ATTEMPTED); + continue; + } + } + + if (authUser == null) { + if (challenge != null) return challenge; + throw new AuthException(Error.UNKNOWN_USER); + } + + + return authenticationComplete(); + } + + + + public void validateUser(UserModel authenticatedUser) { + if (authenticatedUser != null) { + if (!clientSession.getAuthenticatedUser().isEnabled()) throw new AuthException(Error.USER_DISABLED); + } + if (realm.isBruteForceProtected()) { + if (protector.isTemporarilyDisabled(session, realm, authenticatedUser.getUsername())) { + throw new AuthException(Error.USER_TEMPORARILY_DISABLED); + } + } + } + + protected Response authenticationComplete() { + if (userSession == null) { // if no authenticator attached a usersession + userSession = session.sessions().createUserSession(realm, clientSession.getAuthenticatedUser(), clientSession.getAuthenticatedUser().getUsername(), connection.getRemoteAddr(), "form", false, null, null); + userSession.setState(UserSessionModel.State.LOGGING_IN); + } + TokenManager.attachClientSession(userSession, clientSession); + return processRequiredActions(); + + } + + public Response processRequiredActions() { + return AuthenticationManager.nextActionAfterAuthentication(session, userSession, clientSession, connection, request, uriInfo, eventBuilder); + + } + + +} diff --git a/services/src/main/java/org/keycloak/authentication/Authenticator.java b/services/src/main/java/org/keycloak/authentication/Authenticator.java new file mode 100755 index 0000000000..3d5c64eeba --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/Authenticator.java @@ -0,0 +1,17 @@ +package org.keycloak.authentication; + +import org.keycloak.models.UserModel; +import org.keycloak.provider.Provider; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public interface Authenticator extends Provider { + boolean requiresUser(); + void authenticate(AuthenticatorContext context); + boolean configuredFor(UserModel user); + String getRequiredAction(); + + +} diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java new file mode 100755 index 0000000000..162c9b649f --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java @@ -0,0 +1,54 @@ +package org.keycloak.authentication; + +import org.jboss.resteasy.spi.HttpRequest; +import org.keycloak.ClientConnection; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.services.managers.BruteForceProtector; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface AuthenticatorContext { + AuthenticatorModel getModel(); + + void setModel(AuthenticatorModel model); + + Authenticator getAuthenticator(); + + void setAuthenticator(Authenticator authenticator); + + AuthenticationProcessor.Status getStatus(); + + UserModel getUser(); + + void setUser(UserModel user); + + RealmModel getRealm(); + + ClientSessionModel getClientSession(); + void attachUserSession(UserSessionModel userSession); + + ClientConnection getConnection(); + + UriInfo getUriInfo(); + + KeycloakSession getSession(); + + HttpRequest getHttpRequest(); + BruteForceProtector getProtector(); + + void success(); + void failure(AuthenticationProcessor.Error error); + void challenge(Response challenge); + void failureChallenge(AuthenticationProcessor.Error error, Response challenge); + void attempted(); +} diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java new file mode 100755 index 0000000000..28290fc22e --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java @@ -0,0 +1,13 @@ +package org.keycloak.authentication; + +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.provider.ProviderFactory; + +/** +* @author Bill Burke +* @version $Revision: 1 $ +*/ +public interface AuthenticatorFactory extends ProviderFactory { + Authenticator create(AuthenticatorModel model); + +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java new file mode 100755 index 0000000000..b508024828 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticator.java @@ -0,0 +1,47 @@ +package org.keycloak.authentication.authenticators; + +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.AuthenticationManager; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CookieAuthenticator implements Authenticator { + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public void authenticate(AuthenticatorContext context) { + AuthenticationManager.AuthResult authResult = AuthenticationManager.authenticateIdentityCookie(context.getSession(), + context.getRealm(), context.getUriInfo(), context.getConnection(), context.getHttpRequest().getHttpHeaders(), true); + if (authResult == null) { + context.attempted(); + } else { + context.setUser(authResult.getUser()); + context.attachUserSession(authResult.getSession()); + context.success(); + } + + } + + @Override + public boolean configuredFor(UserModel user) { + return true; + } + + @Override + public String getRequiredAction() { + return null; + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java new file mode 100755 index 0000000000..ab50b3dc51 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java @@ -0,0 +1,45 @@ +package org.keycloak.authentication.authenticators; + +import org.keycloak.Config; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorFactory; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CookieAuthenticatorFactory implements AuthenticatorFactory { + static CookieAuthenticator SINGLETON = new CookieAuthenticator(); + @Override + public Authenticator create(AuthenticatorModel model) { + return SINGLETON; + } + + @Override + public Authenticator create(KeycloakSession session) { + throw new IllegalStateException("illegal call"); + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return "auth-cookie"; + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java new file mode 100755 index 0000000000..7cf5b71705 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java @@ -0,0 +1,71 @@ +package org.keycloak.authentication.authenticators; + +import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator { + + public LoginFormOTPAuthenticator(AuthenticatorModel model) { + super(model); + } + + @Override + public void authenticate(AuthenticatorContext context) { + if (!isActionUrl(context)) { + context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR); + return; + } + validateOTP(context); + } + + public void validateOTP(AuthenticatorContext context) { + MultivaluedMap inputData = context.getHttpRequest().getFormParameters(); + List credentials = new LinkedList<>(); + String password = inputData.getFirst(CredentialRepresentation.TOTP); + if (password == null) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); + return; + } + credentials.add(UserCredentialModel.totp(password)); + boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + if (!valid) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); + return; + } + } + + @Override + public boolean requiresUser() { + return true; + } + + @Override + public boolean configuredFor(UserModel user) { + return user.configuredForCredentialType(UserCredentialModel.TOTP); + } + + @Override + public String getRequiredAction() { + return UserModel.RequiredAction.CONFIGURE_TOTP.name(); + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java new file mode 100755 index 0000000000..1aae3b2445 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java @@ -0,0 +1,71 @@ +package org.keycloak.authentication.authenticators; + +import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticator { + + public LoginFormPasswordAuthenticator(AuthenticatorModel model) { + super(model); + } + + @Override + public void authenticate(AuthenticatorContext context) { + if (!isActionUrl(context)) { + context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR); + return; + } + validatePassword(context); + } + + public void validatePassword(AuthenticatorContext context) { + MultivaluedMap inputData = context.getHttpRequest().getFormParameters(); + List credentials = new LinkedList<>(); + String password = inputData.getFirst(CredentialRepresentation.PASSWORD); + if (password == null) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); + return; + } + credentials.add(UserCredentialModel.password(password)); + boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + if (!valid) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); + return; + } + } + + @Override + public boolean requiresUser() { + return true; + } + + @Override + public boolean configuredFor(UserModel user) { + return false; + } + + @Override + public String getRequiredAction() { + return UserModel.RequiredAction.UPDATE_PASSWORD.name(); + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java new file mode 100755 index 0000000000..1d1f73f896 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java @@ -0,0 +1,121 @@ +package org.keycloak.authentication.authenticators; + +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.ClientSessionCode; +import org.keycloak.services.resources.LoginActionsService; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.net.URI; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class LoginFormUsernameAuthenticator implements Authenticator { + protected AuthenticatorModel model; + + public LoginFormUsernameAuthenticator(AuthenticatorModel model) { + this.model = model; + } + + @Override + public void authenticate(AuthenticatorContext context) { + if (!isActionUrl(context)) { + MultivaluedMap formData = new MultivaluedMapImpl<>(); + String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); + if (loginHint == null) { + loginHint = AuthenticationManager.getRememberMeUsername(context.getRealm(), context.getHttpRequest().getHttpHeaders()); + if (loginHint != null) { + formData.add("rememberMe", "on"); + } + } + if (loginHint != null) formData.add(AuthenticationManager.FORM_USERNAME, loginHint); + Response challengeResponse = challenge(context, formData); + context.challenge(challengeResponse); + return; + } + validateUser(context); + } + + protected boolean isActionUrl(AuthenticatorContext context) { + URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName()); + return expected.getPath().equals(context.getUriInfo().getPath()); + + } + + @Override + public boolean requiresUser() { + return false; + } + + protected Response challenge(AuthenticatorContext context, MultivaluedMap formData) { + LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) + .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()); + + if (formData.size() > 0) forms.setFormData(formData); + + return forms.createLogin(); + } + + public void validateUser(AuthenticatorContext context) { + MultivaluedMap inputData = context.getHttpRequest().getFormParameters(); + String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME); + if (username == null) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse); + return; + } + UserModel user = KeycloakModelUtils.findUserByNameOrEmail(context.getSession(), context.getRealm(), username); + if (invalidUser(context, user)) return; + context.setUser(user); + } + + public boolean invalidUser(AuthenticatorContext context, UserModel user) { + if (user == null) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse); + return true; + } + if (!user.isEnabled()) { + context.failure(AuthenticationProcessor.Error.USER_DISABLED); + return true; + } + if (context.getRealm().isBruteForceProtected()) { + if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) { + context.failure(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED); + return true; + } + } + return false; + } + + public Response challenge(AuthenticatorContext context) { + MultivaluedMap formData = new MultivaluedMapImpl<>(); + return challenge(context, formData); + } + + @Override + public boolean configuredFor(UserModel user) { + return true; + } + + @Override + public String getRequiredAction() { + return null; + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java new file mode 100755 index 0000000000..7659c92a66 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java @@ -0,0 +1,97 @@ +package org.keycloak.authentication.authenticators; + +import org.jboss.resteasy.specimpl.MultivaluedMapImpl; +import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.login.LoginFormsProvider; +import org.keycloak.models.AuthenticatorModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.AuthenticationManager; +import org.keycloak.services.managers.ClientSessionCode; +import org.keycloak.services.resources.LoginActionsService; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import java.net.URI; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class OTPFormAuthenticator implements Authenticator { + protected AuthenticatorModel model; + + public OTPFormAuthenticator(AuthenticatorModel model) { + this.model = model; + } + + @Override + public void authenticate(AuthenticatorContext context) { + URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName()); + if (!expected.getPath().equals(context.getUriInfo().getPath())) { + Response challengeResponse = challenge(context); + context.challenge(challengeResponse); + return; + } + validateOTP(context); + } + + public void validateOTP(AuthenticatorContext context) { + MultivaluedMap inputData = context.getHttpRequest().getFormParameters(); + List credentials = new LinkedList<>(); + String password = inputData.getFirst(CredentialRepresentation.TOTP); + if (password == null) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); + return; + } + credentials.add(UserCredentialModel.totp(password)); + boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + if (!valid) { + Response challengeResponse = challenge(context); + context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse); + return; + } + } + + + @Override + public boolean requiresUser() { + return true; + } + + protected Response challenge(AuthenticatorContext context, MultivaluedMap formData) { + LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) + .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode()); + + if (formData.size() > 0) forms.setFormData(formData); + + return forms.createLoginTotp(); + } + + public Response challenge(AuthenticatorContext context) { + MultivaluedMap formData = new MultivaluedMapImpl<>(); + return challenge(context, formData); + } + + @Override + public boolean configuredFor(UserModel user) { + return user.configuredForCredentialType(UserCredentialModel.TOTP); + } + + @Override + public String getRequiredAction() { + return UserModel.RequiredAction.CONFIGURE_TOTP.name(); + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index be7291c8e4..60a49d5c8a 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -406,7 +406,7 @@ public class AuthenticationManager { } } } - + if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN); // refresh the cookies! createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection); if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection); @@ -434,12 +434,12 @@ public class AuthenticationManager { event.detail(Details.CODE_ID, clientSession.getId()); - Set requiredActions = user.getRequiredActions(); + Set requiredActions = user.getRequiredActions(); if (!requiredActions.isEmpty()) { - Iterator i = user.getRequiredActions().iterator(); - UserModel.RequiredAction action = i.next(); + Iterator i = user.getRequiredActions().iterator(); + String action = i.next(); - if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL) && Validation.isEmpty(user.getEmail())) { + if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name()) && Validation.isEmpty(user.getEmail())) { if (i.hasNext()) action = i.next(); else @@ -447,16 +447,16 @@ public class AuthenticationManager { } if (action != null) { - accessCode.setRequiredAction(action); + accessCode.setRequiredAction(RequiredAction.valueOf(action)); LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) .setUser(user); - if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) { + if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL.name())) { event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success(); LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); } - return loginFormsProvider.createResponse(action); + return loginFormsProvider.createResponse(RequiredAction.valueOf(action)); } } diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 470494300f..6daf649a4c 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -117,6 +117,10 @@ public class LoginActionsService { return loginActionsBaseUrl(baseUriBuilder); } + public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) { + return loginActionsBaseUrl(uriInfo).path("auth-form"); + } + public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) { return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService"); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java index b9b45a9244..1ad0207225 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java @@ -151,7 +151,7 @@ public abstract class AbstractIdentityProviderTest { UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null); - assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)); + assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name())); } finally { getRealm().setVerifyEmail(false); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java index 25802429e8..5e1930e3ee 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/AbstractKerberosTest.java @@ -298,7 +298,7 @@ public abstract class AbstractKerberosTest { Assert.assertEquals(user.getLastName(), expectedLastname); if (updateProfileActionExpected) { - Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next().name()); + Assert.assertEquals(UserModel.RequiredAction.UPDATE_PROFILE.toString(), user.getRequiredActions().iterator().next()); } else { Assert.assertTrue(user.getRequiredActions().isEmpty()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java index b70e3fff59..d9e1aa5d0e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java @@ -110,28 +110,28 @@ public class UserModelTest extends AbstractModelTest { user = session.users().getUserByUsername("user", realm); Assert.assertEquals(1, user.getRequiredActions().size()); - Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP)); + Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name())); user.addRequiredAction(RequiredAction.CONFIGURE_TOTP); user = session.users().getUserByUsername("user", realm); Assert.assertEquals(1, user.getRequiredActions().size()); - Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP)); + Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name())); - user.addRequiredAction(RequiredAction.VERIFY_EMAIL); + user.addRequiredAction(RequiredAction.VERIFY_EMAIL.name()); user = session.users().getUserByUsername("user", realm); Assert.assertEquals(2, user.getRequiredActions().size()); - Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP)); - Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)); + Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name())); + Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name())); - user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP); + user.removeRequiredAction(RequiredAction.CONFIGURE_TOTP.name()); user = session.users().getUserByUsername("user", realm); Assert.assertEquals(1, user.getRequiredActions().size()); - Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)); + Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name())); - user.removeRequiredAction(RequiredAction.VERIFY_EMAIL); + user.removeRequiredAction(RequiredAction.VERIFY_EMAIL.name()); user = session.users().getUserByUsername("user", realm); Assert.assertTrue(user.getRequiredActions().isEmpty()); @@ -142,9 +142,9 @@ public class UserModelTest extends AbstractModelTest { Assert.assertEquals(expected.getFirstName(), actual.getFirstName()); Assert.assertEquals(expected.getLastName(), actual.getLastName()); - RequiredAction[] expectedRequiredActions = expected.getRequiredActions().toArray(new RequiredAction[expected.getRequiredActions().size()]); + String[] expectedRequiredActions = expected.getRequiredActions().toArray(new String[expected.getRequiredActions().size()]); Arrays.sort(expectedRequiredActions); - RequiredAction[] actualRequiredActions = actual.getRequiredActions().toArray(new RequiredAction[actual.getRequiredActions().size()]); + String[] actualRequiredActions = actual.getRequiredActions().toArray(new String[actual.getRequiredActions().size()]); Arrays.sort(actualRequiredActions); Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);