1st phase auth/required action spi

This commit is contained in:
Bill Burke 2015-05-18 16:32:38 -04:00
parent e4204a56f5
commit 338300df32
41 changed files with 1574 additions and 58 deletions

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="bburke@redhat.com" id="1.3.0.Beta1">
<delete tableName="CLIENT_SESSION_ROLE"/>
<delete tableName="CLIENT_SESSION_NOTE"/>
<delete tableName="CLIENT_SESSION"/>
<delete tableName="USER_SESSION_NOTE"/>
<delete tableName="USER_SESSION"/>
<createTable tableName="CLIENT_SESSION_AUTH_STATUS">
<column name="AUTHENTICATOR" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
<column name="STATUS" type="INT"/>
<column name="CLIENT_SESSION" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<addColumn tableName="CLIENT_SESSION">
<column name="AUTH_USER_ID" type="VARCHAR(32)"/>
</addColumn>
<addColumn tableName="USER_REQUIRED_ACTION">
<column name="REQUIRED_ACTION" type="VARCHAR(32)">
<constraints nullable="false"/>
</column>
</addColumn>
<!-- VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD -->
<update tableName="USER_REQUIRED_ACTION">
<column name="REQUIRED_ACTION" value="VERIFY_EMAIL"/>
<where>ACTION = 0</where>
</update>
<update tableName="USER_REQUIRED_ACTION">
<column name="REQUIRED_ACTION" value="UPDATE_PROFILE"/>
<where>ACTION = 1</where>
</update>
<update tableName="USER_REQUIRED_ACTION">
<column name="REQUIRED_ACTION" value="CONFIGURE_TOTP"/>
<where>ACTION = 2</where>
</update>
<update tableName="USER_REQUIRED_ACTION">
<column name="REQUIRED_ACTION" value="UPDATE_PASSWORD"/>
<where>ACTION = 3</where>
</update>
<dropPrimaryKey constraintName="CONSTRAINT_2" tableName="USER_REQUIRED_ACTION"/>
<dropColumn tableName="USER_REQUIRED_ACTION" columnName="ACTION"/>
<addPrimaryKey columnNames="REQUIRED_ACTION, USER_ID" constraintName="CONSTRAINT_REQUIRED_ACTION" tableName="USER_REQUIRED_ACTION"/>
<addPrimaryKey columnNames="CLIENT_SESSION, AUTHENTICATOR" constraintName="CONSTRAINT_AUTH_STATUS_PK" tableName="CLIENT_SESSION_AUTH_STATUS"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_SESSION_AUTH_STATUS" constraintName="AUTH_STATUS_CONSTRAINT" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
</changeSet>
</databaseChangeLog>

View file

@ -5,4 +5,5 @@
<include file="META-INF/jpa-changelog-1.1.0.Final.xml"/> <include file="META-INF/jpa-changelog-1.1.0.Final.xml"/>
<include file="META-INF/jpa-changelog-1.2.0.Beta1.xml"/> <include file="META-INF/jpa-changelog-1.2.0.Beta1.xml"/>
<include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/> <include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/>
<include file="META-INF/jpa-changelog-1.3.0.Beta1.xml"/>
</databaseChangeLog> </databaseChangeLog>

View file

@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
public String FIRST_VERSION = "1.0.0.Final"; public String FIRST_VERSION = "1.0.0.Final";
public String LAST_VERSION = "1.2.0.RC1"; public String LAST_VERSION = "1.3.0.Beta1";
public String getCurrentVersionSql(); public String getCurrentVersionSql();

View file

@ -28,6 +28,7 @@
<!-- JpaUserSessionProvider --> <!-- JpaUserSessionProvider -->
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class> <class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>

View file

@ -0,0 +1,127 @@
package org.keycloak.models;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> config = new HashMap<String, String>();
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<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -31,6 +32,14 @@ public interface ClientSessionModel {
public Set<String> getProtocolMappers(); public Set<String> getProtocolMappers();
public void setProtocolMappers(Set<String> protocolMappers); public void setProtocolMappers(Set<String> protocolMappers);
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators();
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status);
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status);
public UserModel getAuthenticatedUser();
public void setAuthenticatedUser(UserModel user);
/** /**
* Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc. * Authentication request type, i.e. OAUTH, SAML 2.0, SAML 1.1, etc.
* *

View file

@ -35,7 +35,11 @@ public interface UserModel {
Map<String, String> getAttributes(); Map<String, String> getAttributes();
Set<RequiredAction> getRequiredActions(); Set<String> getRequiredActions();
void addRequiredAction(String action);
void removeRequiredAction(String action);
void addRequiredAction(RequiredAction action); void addRequiredAction(RequiredAction action);
@ -65,6 +69,14 @@ public interface UserModel {
void updateCredentialDirectly(UserCredentialValueModel cred); void updateCredentialDirectly(UserCredentialValueModel cred);
/**
* Is the use configured to use this credential type
*
* @param type
* @return
*/
boolean configuredForCredentialType(String type);
Set<RoleModel> getRealmRoleMappings(); Set<RoleModel> getRealmRoleMappings();
Set<RoleModel> getClientRoleMappings(ClientModel app); Set<RoleModel> getClientRoleMappings(ClientModel app);
boolean hasRole(RoleModel role); boolean hasRole(RoleModel role);

View file

@ -1,6 +1,7 @@
package org.keycloak.models; package org.keycloak.models;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a> * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -36,6 +37,13 @@ public interface UserSessionModel {
List<ClientSessionModel> getClientSessions(); List<ClientSessionModel> getClientSessions();
public static enum AuthenticatorStatus {
SUCCESS,
SETUP_REQUIRED,
ATTEMPTED,
SKIPPED
}
public String getNote(String name); public String getNote(String name);
public void setNote(String name, String value); public void setNote(String name, String value);
public void removeNote(String name); public void removeNote(String name);

View file

@ -24,7 +24,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
private List<String> roleIds; private List<String> roleIds;
private Map<String, String> attributes; private Map<String, String> attributes;
private List<UserModel.RequiredAction> requiredActions; private List<String> requiredActions;
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(); private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
private List<FederatedIdentityEntity> federatedIdentities; private List<FederatedIdentityEntity> federatedIdentities;
private String federationLink; private String federationLink;
@ -109,11 +109,11 @@ public class UserEntity extends AbstractIdentifiableEntity {
this.attributes = attributes; this.attributes = attributes;
} }
public List<UserModel.RequiredAction> getRequiredActions() { public List<String> getRequiredActions() {
return requiredActions; return requiredActions;
} }
public void setRequiredActions(List<UserModel.RequiredAction> requiredActions) { public void setRequiredActions(List<String> requiredActions) {
this.requiredActions = requiredActions; this.requiredActions = requiredActions;
} }

View file

@ -55,8 +55,8 @@ public class ModelToRepresentation {
rep.setFederationLink(user.getFederationLink()); rep.setFederationLink(user.getFederationLink());
List<String> reqActions = new ArrayList<String>(); List<String> reqActions = new ArrayList<String>();
for (UserModel.RequiredAction ra : user.getRequiredActions()){ for (String ra : user.getRequiredActions()){
reqActions.add(ra.name()); reqActions.add(ra);
} }
rep.setRequiredActions(reqActions); rep.setRequiredActions(reqActions);

View file

@ -73,10 +73,25 @@ public class UserModelDelegate implements UserModel {
} }
@Override @Override
public Set<RequiredAction> getRequiredActions() { public Set<String> getRequiredActions() {
return delegate.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 @Override
public void addRequiredAction(RequiredAction action) { public void addRequiredAction(RequiredAction action) {
delegate.addRequiredAction(action); delegate.addRequiredAction(action);
@ -211,4 +226,5 @@ public class UserModelDelegate implements UserModel {
public boolean revokeConsentForClient(String clientId) { public boolean revokeConsentForClient(String clientId) {
return delegate.revokeConsentForClient(clientId); return delegate.revokeConsentForClient(clientId);
} }
} }

View file

@ -183,28 +183,53 @@ public class UserAdapter implements UserModel, Comparable {
} }
@Override @Override
public Set<RequiredAction> getRequiredActions() { public Set<String> getRequiredActions() {
List<RequiredAction> requiredActions = user.getRequiredActions(); List<String> requiredActions = user.getRequiredActions();
if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>(); if (requiredActions == null) requiredActions = new ArrayList<String>();
return new HashSet(requiredActions); return new HashSet(requiredActions);
} }
@Override @Override
public void addRequiredAction(RequiredAction action) { public void addRequiredAction(RequiredAction action) {
List<RequiredAction> requiredActions = user.getRequiredActions(); String actionName = action.name();
if (requiredActions == null) requiredActions = new ArrayList<RequiredAction>(); addRequiredAction(actionName);
if (!requiredActions.contains(action)) requiredActions.add(action); }
@Override
public void addRequiredAction(String actionName) {
List<String> requiredActions = user.getRequiredActions();
if (requiredActions == null) requiredActions = new ArrayList<>();
if (!requiredActions.contains(actionName)) {
requiredActions.add(actionName);
}
user.setRequiredActions(requiredActions); user.setRequiredActions(requiredActions);
} }
@Override @Override
public void removeRequiredAction(RequiredAction action) { public void removeRequiredAction(RequiredAction action) {
List<RequiredAction> requiredActions = user.getRequiredActions(); String actionName = action.name();
removeRequiredAction(actionName);
}
@Override
public void removeRequiredAction(String actionName) {
List<String> requiredActions = user.getRequiredActions();
if (requiredActions == null) return; if (requiredActions == null) return;
requiredActions.remove(action); requiredActions.remove(actionName);
user.setRequiredActions(requiredActions); user.setRequiredActions(requiredActions);
} }
@Override
public boolean configuredForCredentialType(String type) {
List<UserCredentialValueModel> creds = getCredentialsDirectly();
for (UserCredentialValueModel cred : creds) {
if (cred.getType().equals(type)) return true;
}
return false;
}
@Override @Override
public boolean isTotp() { public boolean isTotp() {
return user.isTotp(); return user.isTotp();

View file

@ -102,7 +102,7 @@ public class UserAdapter implements UserModel {
} }
@Override @Override
public Set<RequiredAction> getRequiredActions() { public Set<String> getRequiredActions() {
if (updated != null) return updated.getRequiredActions(); if (updated != null) return updated.getRequiredActions();
return cached.getRequiredActions(); return cached.getRequiredActions();
} }
@ -119,6 +119,27 @@ public class UserAdapter implements UserModel {
updated.removeRequiredAction(action); 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<UserCredentialValueModel> creds = getCredentialsDirectly();
for (UserCredentialValueModel cred : creds) {
if (cred.getType().equals(type)) return true;
}
return false;
}
@Override @Override
public String getFirstName() { public String getFirstName() {
if (updated != null) return updated.getFirstName(); if (updated != null) return updated.getFirstName();

View file

@ -28,8 +28,8 @@ public class CachedUser {
private boolean enabled; private boolean enabled;
private boolean totp; private boolean totp;
private String federationLink; private String federationLink;
private Map<String, String> attributes = new HashMap<String, String>(); private Map<String, String> attributes = new HashMap<>();
private Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>(); private Set<String> requiredActions = new HashSet<>();
private Set<String> roleMappings = new HashSet<String>(); private Set<String> roleMappings = new HashSet<String>();
@ -96,7 +96,7 @@ public class CachedUser {
return attributes; return attributes;
} }
public Set<UserModel.RequiredAction> getRequiredActions() { public Set<String> getRequiredActions() {
return requiredActions; return requiredActions;
} }

View file

@ -139,8 +139,8 @@ public class UserAdapter implements UserModel {
} }
@Override @Override
public Set<RequiredAction> getRequiredActions() { public Set<String> getRequiredActions() {
Set<RequiredAction> result = new HashSet<RequiredAction>(); Set<String> result = new HashSet<>();
for (UserRequiredActionEntity attr : user.getRequiredActions()) { for (UserRequiredActionEntity attr : user.getRequiredActions()) {
result.add(attr.getAction()); result.add(attr.getAction());
} }
@ -149,13 +149,19 @@ public class UserAdapter implements UserModel {
@Override @Override
public void addRequiredAction(RequiredAction action) { public void addRequiredAction(RequiredAction action) {
String actionName = action.name();
addRequiredAction(actionName);
}
@Override
public void addRequiredAction(String actionName) {
for (UserRequiredActionEntity attr : user.getRequiredActions()) { for (UserRequiredActionEntity attr : user.getRequiredActions()) {
if (attr.getAction().equals(action)) { if (attr.getAction().equals(actionName)) {
return; return;
} }
} }
UserRequiredActionEntity attr = new UserRequiredActionEntity(); UserRequiredActionEntity attr = new UserRequiredActionEntity();
attr.setAction(action); attr.setAction(actionName);
attr.setUser(user); attr.setUser(user);
em.persist(attr); em.persist(attr);
user.getRequiredActions().add(attr); user.getRequiredActions().add(attr);
@ -163,16 +169,31 @@ public class UserAdapter implements UserModel {
@Override @Override
public void removeRequiredAction(RequiredAction action) { public void removeRequiredAction(RequiredAction action) {
String actionName = action.name();
removeRequiredAction(actionName);
}
@Override
public void removeRequiredAction(String actionName) {
Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator(); Iterator<UserRequiredActionEntity> it = user.getRequiredActions().iterator();
while (it.hasNext()) { while (it.hasNext()) {
UserRequiredActionEntity attr = it.next(); UserRequiredActionEntity attr = it.next();
if (attr.getAction().equals(action)) { if (attr.getAction().equals(actionName)) {
it.remove(); it.remove();
em.remove(attr); em.remove(attr);
} }
} }
} }
@Override
public boolean configuredForCredentialType(String type) {
List<UserCredentialValueModel> creds = getCredentialsDirectly();
for (UserCredentialValueModel cred : creds) {
if (cred.getType().equals(type)) return true;
}
return false;
}
@Override @Override
public String getFirstName() { public String getFirstName() {
return user.getFirstName(); return user.getFirstName();

View file

@ -33,14 +33,14 @@ public class UserRequiredActionEntity {
protected UserEntity user; protected UserEntity user;
@Id @Id
@Column(name="ACTION") @Column(name="REQUIRED_ACTION")
protected UserModel.RequiredAction action; protected String action;
public UserModel.RequiredAction getAction() { public String getAction() {
return action; return action;
} }
public void setAction(UserModel.RequiredAction action) { public void setAction(String action) {
this.action = action; this.action = action;
} }
@ -56,12 +56,12 @@ public class UserRequiredActionEntity {
protected UserEntity user; protected UserEntity user;
protected UserModel.RequiredAction action; protected String action;
public Key() { public Key() {
} }
public Key(UserEntity user, UserModel.RequiredAction action) { public Key(UserEntity user, String action) {
this.user = user; this.user = user;
this.action = action; this.action = action;
} }
@ -70,7 +70,7 @@ public class UserRequiredActionEntity {
return user; return user;
} }
public UserModel.RequiredAction getAction() { public String getAction() {
return action; return action;
} }

View file

@ -159,8 +159,8 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override @Override
public Set<RequiredAction> getRequiredActions() { public Set<String> getRequiredActions() {
Set<RequiredAction> result = new HashSet<RequiredAction>(); Set<String> result = new HashSet<String>();
if (user.getRequiredActions() != null) { if (user.getRequiredActions() != null) {
result.addAll(user.getRequiredActions()); result.addAll(user.getRequiredActions());
} }
@ -169,12 +169,24 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override @Override
public void addRequiredAction(RequiredAction action) { 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 @Override
public void removeRequiredAction(RequiredAction action) { 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 @Override
@ -320,6 +332,16 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
return result; return result;
} }
@Override
public boolean configuredForCredentialType(String type) {
List<UserCredentialValueModel> creds = getCredentialsDirectly();
for (UserCredentialValueModel cred : creds) {
if (cred.getType().equals(type)) return true;
}
return false;
}
@Override @Override
public void updateCredentialDirectly(UserCredentialValueModel credModel) { public void updateCredentialDirectly(UserCredentialValueModel credModel) {
CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType()); CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType());

View file

@ -5,11 +5,13 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity; import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity; import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -165,5 +167,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
void update() { void update() {
provider.getTx().replace(cache, entity.getId(), entity); provider.getTx().replace(cache, entity.getId(), entity);
} }
@Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
return entity.getAuthenticatorStatus();
}
@Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status);
}
@Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> 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());
}
} }

View file

@ -1,7 +1,9 @@
package org.keycloak.models.sessions.infinispan.entities; package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserSessionModel;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -27,6 +29,8 @@ public class ClientSessionEntity extends SessionEntity {
private Set<String> roles; private Set<String> roles;
private Set<String> protocolMappers; private Set<String> protocolMappers;
private Map<String, String> notes; private Map<String, String> notes;
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
public String getClient() { public String getClient() {
return client; return client;
@ -107,4 +111,20 @@ public class ClientSessionEntity extends SessionEntity {
public void setNotes(Map<String, String> notes) { public void setNotes(Map<String, String> notes) {
this.notes = notes; this.notes = notes;
} }
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
return authenticatorStatus;
}
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
public String getAuthUserId() {
return authUserId;
}
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
} }

View file

@ -4,6 +4,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity; import org.keycloak.models.sessions.jpa.entities.ClientSessionEntity;
import org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity; 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 javax.persistence.EntityManager;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -210,4 +212,29 @@ public class ClientSessionAdapter implements ClientSessionModel {
} }
} }
} }
@Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
return null;
}
@Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
}
@Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> status) {
}
@Override
public UserModel getAuthenticatedUser() {
return null;
}
@Override
public void setAuthenticatedUser(UserModel user) {
}
} }

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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;
}
}
}

View file

@ -57,6 +57,9 @@ public class ClientSessionEntity {
@Column(name="ACTION") @Column(name="ACTION")
protected ClientSessionModel.Action action; protected ClientSessionModel.Action action;
@Column(name="AUTH_USER_ID")
protected String userId;
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession") @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>(); protected Collection<ClientSessionRoleEntity> roles = new ArrayList<ClientSessionRoleEntity>();
@ -66,6 +69,9 @@ public class ClientSessionEntity {
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession") @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>(); protected Collection<ClientSessionNoteEntity> notes = new ArrayList<ClientSessionNoteEntity>();
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="clientSession")
protected Collection<ClientSessionAuthStatusEntity> authanticatorStatus = new ArrayList<>();
public String getId() { public String getId() {
return id; return id;
} }
@ -153,4 +159,20 @@ public class ClientSessionEntity {
public void setAuthMethod(String authMethod) { public void setAuthMethod(String authMethod) {
this.authMethod = authMethod; this.authMethod = authMethod;
} }
public Collection<ClientSessionAuthStatusEntity> getAuthanticatorStatus() {
return authanticatorStatus;
}
public void setAuthanticatorStatus(Collection<ClientSessionAuthStatusEntity> authanticatorStatus) {
this.authanticatorStatus = authanticatorStatus;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
} }

View file

@ -4,10 +4,12 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.mem.entities.ClientSessionEntity; import org.keycloak.models.sessions.mem.entities.ClientSessionEntity;
import org.keycloak.models.sessions.mem.entities.UserSessionEntity; import org.keycloak.models.sessions.mem.entities.UserSessionEntity;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -141,4 +143,30 @@ public class ClientSessionAdapter implements ClientSessionModel {
public void setAuthMethod(String method) { public void setAuthMethod(String method) {
entity.setAuthMethod(method); entity.setAuthMethod(method);
} }
@Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
return entity.getAuthenticatorStatus();
}
@Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status);
}
@Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> 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());
}
} }

View file

@ -1,6 +1,7 @@
package org.keycloak.models.sessions.mem.entities; package org.keycloak.models.sessions.mem.entities;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserSessionModel;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -14,6 +15,8 @@ public class ClientSessionEntity {
private String id; private String id;
private String clientId; private String clientId;
private String realmId; private String realmId;
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
private UserSessionEntity session; private UserSessionEntity session;
@ -24,7 +27,7 @@ public class ClientSessionEntity {
private ClientSessionModel.Action action; private ClientSessionModel.Action action;
private Set<String> roles; private Set<String> roles;
private Set<String> protocolMappers; private Set<String> protocolMappers;
private Map<String, String> notes = new HashMap<String, String>(); private Map<String, String> notes = new HashMap<>();
public String getId() { public String getId() {
return id; return id;
@ -109,4 +112,20 @@ public class ClientSessionEntity {
public void setAuthMethod(String authMethod) { public void setAuthMethod(String authMethod) {
this.authMethod = authMethod; this.authMethod = authMethod;
} }
public String getAuthUserId() {
return authUserId;
}
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
return authenticatorStatus;
}
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
} }

View file

@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity; import org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity;
import org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity; 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.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
@ -154,6 +156,37 @@ public class ClientSessionAdapter extends AbstractMongoAdapter<MongoClientSessio
updateMongoEntity(); updateMongoEntity();
} }
@Override
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticators() {
return entity.getAuthenticatorStatus();
}
@Override
public void setAuthenticatorStatus(String authenticator, UserSessionModel.AuthenticatorStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status);
updateMongoEntity();
}
@Override
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> 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 @Override
public String getAuthMethod() { public String getAuthMethod() {
return entity.getAuthMethod(); return entity.getAuthMethod();

View file

@ -4,6 +4,7 @@ import org.keycloak.connections.mongo.api.MongoCollection;
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity; import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ClientSessionModel; import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.entities.AbstractIdentifiableEntity; import org.keycloak.models.entities.AbstractIdentifiableEntity;
import java.util.HashMap; import java.util.HashMap;
@ -29,6 +30,8 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
private List<String> roles; private List<String> roles;
private List<String> protocolMappers; private List<String> protocolMappers;
private Map<String, String> notes = new HashMap<String, String>(); private Map<String, String> notes = new HashMap<String, String>();
private Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
public String getId() { public String getId() {
return id; return id;
@ -118,6 +121,22 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public Map<String, UserSessionModel.AuthenticatorStatus> getAuthenticatorStatus() {
return authenticatorStatus;
}
public void setAuthenticatorStatus(Map<String, UserSessionModel.AuthenticatorStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
public String getAuthUserId() {
return authUserId;
}
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
@Override @Override
public void afterRemove(MongoStoreInvocationContext context) { public void afterRemove(MongoStoreInvocationContext context) {
} }

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<AuthenticatorModel> 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<String, UserSessionModel.AuthenticatorStatus> 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);
}
}

View file

@ -0,0 +1,17 @@
package org.keycloak.authentication;
import org.keycloak.models.UserModel;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface Authenticator extends Provider {
boolean requiresUser();
void authenticate(AuthenticatorContext context);
boolean configuredFor(UserModel user);
String getRequiredAction();
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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();
}

View file

@ -0,0 +1,13 @@
package org.keycloak.authentication;
import org.keycloak.models.AuthenticatorModel;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface AuthenticatorFactory extends ProviderFactory<Authenticator> {
Authenticator create(AuthenticatorModel model);
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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() {
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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";
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> inputData = context.getHttpRequest().getFormParameters();
List<UserCredentialModel> 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() {
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> inputData = context.getHttpRequest().getFormParameters();
List<UserCredentialModel> 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() {
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> 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<String, String> 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<String, String> 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<String, String> 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() {
}
}

View file

@ -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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String, String> inputData = context.getHttpRequest().getFormParameters();
List<UserCredentialModel> 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<String, String> 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<String, String> 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() {
}
}

View file

@ -406,7 +406,7 @@ public class AuthenticationManager {
} }
} }
} }
if (userSession.getState() != UserSessionModel.State.LOGGED_IN) userSession.setState(UserSessionModel.State.LOGGED_IN);
// refresh the cookies! // refresh the cookies!
createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection); createLoginCookie(realm, userSession.getUser(), userSession, uriInfo, clientConnection);
if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), 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()); event.detail(Details.CODE_ID, clientSession.getId());
Set<UserModel.RequiredAction> requiredActions = user.getRequiredActions(); Set<String> requiredActions = user.getRequiredActions();
if (!requiredActions.isEmpty()) { if (!requiredActions.isEmpty()) {
Iterator<RequiredAction> i = user.getRequiredActions().iterator(); Iterator<String> i = user.getRequiredActions().iterator();
UserModel.RequiredAction action = i.next(); 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()) if (i.hasNext())
action = i.next(); action = i.next();
else else
@ -447,16 +447,16 @@ public class AuthenticationManager {
} }
if (action != null) { if (action != null) {
accessCode.setRequiredAction(action); accessCode.setRequiredAction(RequiredAction.valueOf(action));
LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode()) LoginFormsProvider loginFormsProvider = session.getProvider(LoginFormsProvider.class).setClientSessionCode(accessCode.getCode())
.setUser(user); .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(); event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId()); LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
} }
return loginFormsProvider.createResponse(action); return loginFormsProvider.createResponse(RequiredAction.valueOf(action));
} }
} }

View file

@ -117,6 +117,10 @@ public class LoginActionsService {
return loginActionsBaseUrl(baseUriBuilder); return loginActionsBaseUrl(baseUriBuilder);
} }
public static UriBuilder authenticationFormProcessor(UriInfo uriInfo) {
return loginActionsBaseUrl(uriInfo).path("auth-form");
}
public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) { public static UriBuilder loginActionsBaseUrl(UriBuilder baseUriBuilder) {
return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService"); return baseUriBuilder.path(RealmsResource.class).path(RealmsResource.class, "getLoginActionsService");
} }

View file

@ -151,7 +151,7 @@ public abstract class AbstractIdentityProviderTest {
UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null); UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null);
assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)); assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name()));
} finally { } finally {
getRealm().setVerifyEmail(false); getRealm().setVerifyEmail(false);

View file

@ -298,7 +298,7 @@ public abstract class AbstractKerberosTest {
Assert.assertEquals(user.getLastName(), expectedLastname); Assert.assertEquals(user.getLastName(), expectedLastname);
if (updateProfileActionExpected) { 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 { } else {
Assert.assertTrue(user.getRequiredActions().isEmpty()); Assert.assertTrue(user.getRequiredActions().isEmpty());
} }

View file

@ -110,28 +110,28 @@ public class UserModelTest extends AbstractModelTest {
user = session.users().getUserByUsername("user", realm); user = session.users().getUserByUsername("user", realm);
Assert.assertEquals(1, user.getRequiredActions().size()); 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.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
user = session.users().getUserByUsername("user", realm); user = session.users().getUserByUsername("user", realm);
Assert.assertEquals(1, user.getRequiredActions().size()); 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); user = session.users().getUserByUsername("user", realm);
Assert.assertEquals(2, user.getRequiredActions().size()); Assert.assertEquals(2, user.getRequiredActions().size());
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP)); Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.CONFIGURE_TOTP.name()));
Assert.assertTrue(user.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL)); 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); user = session.users().getUserByUsername("user", realm);
Assert.assertEquals(1, user.getRequiredActions().size()); 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); user = session.users().getUserByUsername("user", realm);
Assert.assertTrue(user.getRequiredActions().isEmpty()); Assert.assertTrue(user.getRequiredActions().isEmpty());
@ -142,9 +142,9 @@ public class UserModelTest extends AbstractModelTest {
Assert.assertEquals(expected.getFirstName(), actual.getFirstName()); Assert.assertEquals(expected.getFirstName(), actual.getFirstName());
Assert.assertEquals(expected.getLastName(), actual.getLastName()); 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); 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); Arrays.sort(actualRequiredActions);
Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions); Assert.assertArrayEquals(expectedRequiredActions, actualRequiredActions);