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
index 73b98121b6..c469581b7a 100755
--- 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
@@ -22,6 +22,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -58,7 +96,7 @@
-
+
@@ -67,10 +105,10 @@
-
+
-
+
@@ -91,6 +129,10 @@
ACTION = 3
+
+
+
+
@@ -98,6 +140,10 @@
+
+
+
+
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index a5af4652f9..be9c2811f3 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -25,6 +25,9 @@
org.keycloak.models.jpa.entities.UserConsentEntity
org.keycloak.models.jpa.entities.UserConsentRoleEntity
org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity
+ org.keycloak.models.jpa.entities.AuthenticationFlowEntity
+ org.keycloak.models.jpa.entities.AuthenticationExecutionEntity
+ org.keycloak.models.jpa.entities.AuthenticatorEntity
org.keycloak.models.sessions.jpa.entities.ClientSessionEntity
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java
new file mode 100755
index 0000000000..8cea65bc80
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java
@@ -0,0 +1,83 @@
+package org.keycloak.models;
+
+/**
+* @author Bill Burke
+* @version $Revision: 1 $
+*/
+public class AuthenticationExecutionModel {
+
+ private String id;
+ private String authenticator;
+ private boolean autheticatorFlow;
+ private Requirement requirement;
+ private boolean userSetupAllowed;
+ private int priority;
+ private String parentFlow;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getAuthenticator() {
+ return authenticator;
+ }
+
+ public void setAuthenticator(String authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ 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 String getParentFlow() {
+ return parentFlow;
+ }
+
+ public void setParentFlow(String parentFlow) {
+ this.parentFlow = parentFlow;
+ }
+
+ /**
+ * Is the referenced authenticator a flow?
+ *
+ * @return
+ */
+ public boolean isAutheticatorFlow() {
+ return autheticatorFlow;
+ }
+
+ public void setAutheticatorFlow(boolean autheticatorFlow) {
+ this.autheticatorFlow = autheticatorFlow;
+ }
+
+ public enum Requirement {
+ REQUIRED,
+ OPTIONAL,
+ ALTERNATIVE
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java
new file mode 100755
index 0000000000..b9e53223f7
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java
@@ -0,0 +1,35 @@
+package org.keycloak.models;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class AuthenticationFlowModel {
+ private String id;
+ private String alias;
+ private String description;
+
+ 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 getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java b/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
index 0ca9322076..0a6cacd90a 100755
--- a/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
+++ b/model/api/src/main/java/org/keycloak/models/AuthenticatorModel.java
@@ -9,23 +9,9 @@ import java.util.Map;
*/
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();
@@ -53,70 +39,6 @@ public class AuthenticatorModel {
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;
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 508a84cdce..43eaa30eb9 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -173,6 +173,25 @@ public interface RealmModel extends RoleContainerModel {
void setSmtpConfig(Map smtpConfig);
+ List getAuthenticationFlows();
+ AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model);
+ AuthenticationFlowModel getAuthenticationFlowById(String id);
+ void removeAuthenticationFlow(AuthenticationFlowModel model);
+ void updateAuthenticationFlow(AuthenticationFlowModel model);
+
+ List getAuthenticationExecutions(String flowId);
+ AuthenticationExecutionModel getAuthenticationExecutionById(String id);
+ AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model);
+ void updateAuthenticatorExecution(AuthenticationExecutionModel model);
+ void removeAuthenticatorExecution(AuthenticationExecutionModel model);
+
+
+ List getAuthenticators();
+ AuthenticatorModel addAuthenticator(AuthenticatorModel model);
+ void updateAuthenticator(AuthenticatorModel model);
+ void removeAuthenticator(AuthenticatorModel model);
+ AuthenticatorModel getAuthenticatorById(String id);
+
List getIdentityProviders();
IdentityProviderModel getIdentityProviderByAlias(String alias);
void addIdentityProvider(IdentityProviderModel identityProvider);
diff --git a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java
new file mode 100755
index 0000000000..c90a6575b0
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java
@@ -0,0 +1,74 @@
+package org.keycloak.models.entities;
+
+import org.keycloak.models.AuthenticationExecutionModel;
+
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class AuthenticationExecutionEntity {
+ protected String id;
+ protected String authenticator;
+ protected AuthenticationExecutionModel.Requirement requirement;
+ protected int priority;
+ private boolean userSetupAllowed;
+ private boolean autheticatorFlow;
+ private String parentFlow;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getAuthenticator() {
+ return authenticator;
+ }
+
+ public void setAuthenticator(String authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ public AuthenticationExecutionModel.Requirement getRequirement() {
+ return requirement;
+ }
+
+ public void setRequirement(AuthenticationExecutionModel.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 isAutheticatorFlow() {
+ return autheticatorFlow;
+ }
+
+ public void setAutheticatorFlow(boolean autheticatorFlow) {
+ this.autheticatorFlow = autheticatorFlow;
+ }
+
+ public String getParentFlow() {
+ return parentFlow;
+ }
+
+ public void setParentFlow(String parentFlow) {
+ this.parentFlow = parentFlow;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java
new file mode 100755
index 0000000000..b79d1a5f9d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java
@@ -0,0 +1,47 @@
+package org.keycloak.models.entities;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class AuthenticationFlowEntity {
+ protected String id;
+ protected String alias;
+ protected String description;
+ List executions = new ArrayList();
+ 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 getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public List getExecutions() {
+ return executions;
+ }
+
+ public void setExecutions(List executions) {
+ this.executions = executions;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/AuthenticatorEntity.java b/model/api/src/main/java/org/keycloak/models/entities/AuthenticatorEntity.java
new file mode 100755
index 0000000000..c9077c0f01
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/AuthenticatorEntity.java
@@ -0,0 +1,46 @@
+package org.keycloak.models.entities;
+
+import java.util.Map;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class AuthenticatorEntity {
+ protected String id;
+ protected String alias;
+ protected String providerId;
+ private Map config;
+
+ 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 Map getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map config) {
+ this.config = config;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 1b8ccf21df..7c393bbb33 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -74,6 +74,8 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private List supportedLocales = new ArrayList();
private String defaultLocale;
private List identityProviderMappers = new ArrayList();
+ private List authenticationFlows = new ArrayList<>();
+ private List authenticators = new ArrayList<>();
public String getName() {
@@ -482,6 +484,22 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setIdentityProviderMappers(List identityProviderMappers) {
this.identityProviderMappers = identityProviderMappers;
}
+
+ public List getAuthenticationFlows() {
+ return authenticationFlows;
+ }
+
+ public void setAuthenticationFlows(List authenticationFlows) {
+ this.authenticationFlows = authenticationFlows;
+ }
+
+ public List getAuthenticators() {
+ return authenticators;
+ }
+
+ public void setAuthenticators(List authenticators) {
+ this.authenticators = authenticators;
+ }
}
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index 30b272d2fd..26e09bb53e 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -18,6 +18,9 @@ package org.keycloak.models.file.adapter;
import org.keycloak.connections.file.InMemoryModel;
import org.keycloak.enums.SslRequired;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
@@ -31,6 +34,9 @@ import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderCreationEventImpl;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
+import org.keycloak.models.entities.AuthenticationExecutionEntity;
+import org.keycloak.models.entities.AuthenticationFlowEntity;
+import org.keycloak.models.entities.AuthenticatorEntity;
import org.keycloak.models.entities.ClientEntity;
import org.keycloak.models.entities.IdentityProviderMapperEntity;
import org.keycloak.models.entities.RealmEntity;
@@ -1191,6 +1197,234 @@ public class RealmAdapter implements RealmModel {
return mapping;
}
+ @Override
+ public List getAuthenticationFlows() {
+ List flows = realm.getAuthenticationFlows();
+ if (flows.size() == 0) return Collections.EMPTY_LIST;
+ List models = new LinkedList<>();
+ for (AuthenticationFlowEntity entity : flows) {
+ AuthenticationFlowModel model = entityToModel(entity);
+ models.add(model);
+ }
+ return models;
+ }
+
+ protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) {
+ AuthenticationFlowModel model = new AuthenticationFlowModel();
+ model.setId(entity.getId());
+ model.setAlias(entity.getAlias());
+ model.setDescription(entity.getDescription());
+ return model;
+ }
+
+ @Override
+ public AuthenticationFlowModel getAuthenticationFlowById(String id) {
+ for (AuthenticationFlowModel model : getAuthenticationFlows()) {
+ if (model.getId().equals(id)) return model;
+ }
+ return null;
+ }
+
+ protected AuthenticationFlowEntity getFlowEntity(String id) {
+ List flows = realm.getAuthenticationFlows();
+ for (AuthenticationFlowEntity entity : flows) {
+ if (id.equals(entity.getId())) return entity;
+ }
+ return null;
+
+ }
+
+ @Override
+ public void removeAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity toDelete = getFlowEntity(model.getId());
+ if (toDelete == null) return;
+ realm.getAuthenticationFlows().remove(toDelete);
+ }
+
+ @Override
+ public void updateAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity toUpdate = getFlowEntity(model.getId());
+ if (toUpdate == null) return;
+ toUpdate.setAlias(model.getAlias());
+ toUpdate.setDescription(model.getDescription());
+
+ }
+
+ @Override
+ public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity entity = new AuthenticationFlowEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setAlias(model.getAlias());
+ entity.setDescription(model.getDescription());
+ realm.getAuthenticationFlows().add(entity);
+ model.setId(entity.getId());
+ return model;
+ }
+
+ @Override
+ public List getAuthenticationExecutions(String flowId) {
+ AuthenticationFlowEntity flow = getFlowEntity(flowId);
+ if (flow == null) return Collections.EMPTY_LIST;
+
+ List queryResult = flow.getExecutions();
+ List executions = new LinkedList<>();
+ for (AuthenticationExecutionEntity entity : queryResult) {
+ AuthenticationExecutionModel model = entityToModel(entity);
+ executions.add(model);
+ }
+ return executions;
+ }
+
+ public AuthenticationExecutionModel entityToModel(AuthenticationExecutionEntity entity) {
+ AuthenticationExecutionModel model = new AuthenticationExecutionModel();
+ model.setId(entity.getId());
+ model.setUserSetupAllowed(entity.isUserSetupAllowed());
+ model.setRequirement(entity.getRequirement());
+ model.setPriority(entity.getPriority());
+ model.setAuthenticator(entity.getAuthenticator());
+ model.setParentFlow(entity.getParentFlow());
+ model.setAutheticatorFlow(entity.isAutheticatorFlow());
+ return model;
+ }
+
+ @Override
+ public AuthenticationExecutionModel getAuthenticationExecutionById(String id) {
+ AuthenticationExecutionEntity execution = getAuthenticationExecutionEntity(id);
+ return entityToModel(execution);
+ }
+
+ public AuthenticationExecutionEntity getAuthenticationExecutionEntity(String id) {
+ List flows = realm.getAuthenticationFlows();
+ for (AuthenticationFlowEntity entity : flows) {
+ for (AuthenticationExecutionEntity exe : entity.getExecutions()) {
+ if (exe.getId().equals(id)) {
+ return exe;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = new AuthenticationExecutionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setAuthenticator(model.getAuthenticator());
+ entity.setPriority(model.getPriority());
+ entity.setRequirement(model.getRequirement());
+ entity.setUserSetupAllowed(model.isUserSetupAllowed());
+ entity.setAutheticatorFlow(model.isAutheticatorFlow());
+ AuthenticationFlowEntity flow = getFlowEntity(model.getId());
+ flow.getExecutions().add(entity);
+ model.setId(entity.getId());
+ return model;
+
+ }
+
+ @Override
+ public void updateAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = null;
+ AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow());
+ for (AuthenticationExecutionEntity exe : flow.getExecutions()) {
+ if (exe.getId().equals(model.getId())) {
+ entity = exe;
+ }
+ }
+ if (entity == null) return;
+ entity.setAutheticatorFlow(model.isAutheticatorFlow());
+ entity.setAuthenticator(model.getAuthenticator());
+ entity.setPriority(model.getPriority());
+ entity.setRequirement(model.getRequirement());
+ entity.setUserSetupAllowed(model.isUserSetupAllowed());
+ }
+
+ @Override
+ public void removeAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = null;
+ AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow());
+ for (AuthenticationExecutionEntity exe : flow.getExecutions()) {
+ if (exe.getId().equals(model.getId())) {
+ entity = exe;
+ }
+ }
+ if (entity == null) return;
+ flow.getExecutions().remove(entity);
+
+ }
+
+ @Override
+ public List getAuthenticators() {
+ List authenticators = new LinkedList<>();
+ for (AuthenticatorEntity entity : realm.getAuthenticators()) {
+ authenticators.add(entityToModel(entity));
+ }
+ return authenticators;
+ }
+
+ @Override
+ public AuthenticatorModel addAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity auth = new AuthenticatorEntity();
+ auth.setId(KeycloakModelUtils.generateId());
+ auth.setAlias(model.getAlias());
+ auth.setProviderId(model.getProviderId());
+ auth.setConfig(model.getConfig());
+ realm.getAuthenticators().add(auth);
+ model.setId(auth.getId());
+ return model;
+ }
+
+ @Override
+ public void removeAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity entity = getAuthenticatorEntity(model.getId());
+ if (entity == null) return;
+ realm.getAuthenticators().remove(entity);
+
+ }
+
+ @Override
+ public AuthenticatorModel getAuthenticatorById(String id) {
+ AuthenticatorEntity entity = getAuthenticatorEntity(id);
+ if (entity == null) return null;
+ return entityToModel(entity);
+ }
+
+ public AuthenticatorEntity getAuthenticatorEntity(String id) {
+ AuthenticatorEntity entity = null;
+ for (AuthenticatorEntity auth : realm.getAuthenticators()) {
+ if (auth.getId().equals(id)) {
+ entity = auth;
+ break;
+ }
+ }
+ return entity;
+ }
+
+ public AuthenticatorModel entityToModel(AuthenticatorEntity entity) {
+ AuthenticatorModel model = new AuthenticatorModel();
+ model.setId(entity.getId());
+ model.setProviderId(entity.getProviderId());
+ model.setAlias(entity.getAlias());
+ Map config = new HashMap<>();
+ if (entity.getConfig() != null) config.putAll(entity.getConfig());
+ model.setConfig(config);
+ return model;
+ }
+
+ @Override
+ public void updateAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity entity = getAuthenticatorEntity(model.getId());
+ if (entity == null) return;
+ entity.setAlias(model.getAlias());
+ entity.setProviderId(model.getProviderId());
+ if (entity.getConfig() == null) {
+ entity.setConfig(model.getConfig());
+ } else {
+ entity.getConfig().clear();
+ entity.getConfig().putAll(model.getConfig());
+ }
+ }
+
+
@Override
public Set getUserFederationMappers() {
Set mappers = new HashSet();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 778472470e..afb1c2c574 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -2,6 +2,9 @@ package org.keycloak.models.cache;
import org.keycloak.Config;
import org.keycloak.enums.SslRequired;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
@@ -1015,4 +1018,104 @@ public class RealmAdapter implements RealmModel {
return null;
}
+ @Override
+ public List getAuthenticationFlows() {
+ if (updated != null) return updated.getAuthenticationFlows();
+ List models = new ArrayList<>();
+ models.addAll(cached.getAuthenticationFlows().values());
+ return models;
+ }
+
+ @Override
+ public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) {
+ getDelegateForUpdate();
+ return updated.addAuthenticationFlow(model);
+ }
+
+ @Override
+ public AuthenticationFlowModel getAuthenticationFlowById(String id) {
+ if (updated != null) return updated.getAuthenticationFlowById(id);
+ return cached.getAuthenticationFlows().get(id);
+ }
+
+ @Override
+ public void removeAuthenticationFlow(AuthenticationFlowModel model) {
+ getDelegateForUpdate();
+ updated.removeAuthenticationFlow(model);
+
+ }
+
+ @Override
+ public void updateAuthenticationFlow(AuthenticationFlowModel model) {
+ getDelegateForUpdate();
+ updated.updateAuthenticationFlow(model);
+
+ }
+
+ @Override
+ public List getAuthenticationExecutions(String flowId) {
+ if (updated != null) return updated.getAuthenticationExecutions(flowId);
+ List models = new ArrayList<>();
+ return cached.getAuthenticationExecutions().get(flowId);
+ }
+
+ @Override
+ public AuthenticationExecutionModel getAuthenticationExecutionById(String id) {
+ if (updated != null) return updated.getAuthenticationExecutionById(id);
+ return cached.getExecutionsById().get(id);
+ }
+
+ @Override
+ public AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model) {
+ getDelegateForUpdate();
+ return updated.addAuthenticatorExecution(model);
+ }
+
+ @Override
+ public void updateAuthenticatorExecution(AuthenticationExecutionModel model) {
+ getDelegateForUpdate();
+ updated.updateAuthenticatorExecution(model);
+
+ }
+
+ @Override
+ public void removeAuthenticatorExecution(AuthenticationExecutionModel model) {
+ getDelegateForUpdate();
+ updated.removeAuthenticatorExecution(model);
+
+ }
+
+ @Override
+ public List getAuthenticators() {
+ if (updated != null) return updated.getAuthenticators();
+ List models = new ArrayList<>();
+ models.addAll(cached.getAuthenticators().values());
+ return models;
+ }
+
+ @Override
+ public AuthenticatorModel addAuthenticator(AuthenticatorModel model) {
+ getDelegateForUpdate();
+ return updated.addAuthenticator(model);
+ }
+
+ @Override
+ public void updateAuthenticator(AuthenticatorModel model) {
+ getDelegateForUpdate();
+ updated.updateAuthenticator(model);
+
+ }
+
+ @Override
+ public void removeAuthenticator(AuthenticatorModel model) {
+ getDelegateForUpdate();
+ updated.removeAuthenticator(model);
+
+ }
+
+ @Override
+ public AuthenticatorModel getAuthenticatorById(String id) {
+ if (updated != null) return updated.getAuthenticatorById(id);
+ return cached.getAuthenticators().get(id);
+ }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index faf7ba27d5..d93acf6948 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -1,6 +1,9 @@
package org.keycloak.models.cache.entities;
import org.keycloak.enums.SslRequired;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
@@ -76,6 +79,10 @@ public class CachedRealm {
private Map browserSecurityHeaders = new HashMap();
private Map smtpConfig = new HashMap();
+ private Map authenticationFlows = new HashMap<>();
+ private Map authenticators = new HashMap<>();
+ private MultivaluedHashMap authenticationExecutions = new MultivaluedHashMap<>();
+ private Map executionsById = new HashMap<>();
private boolean eventsEnabled;
private long eventsExpiration;
@@ -183,6 +190,16 @@ public class CachedRealm {
internationalizationEnabled = model.isInternationalizationEnabled();
supportedLocales.addAll(model.getSupportedLocales());
defaultLocale = model.getDefaultLocale();
+ for (AuthenticationFlowModel flow : model.getAuthenticationFlows()) {
+ authenticationFlows.put(flow.getId(), flow);
+ for (AuthenticationExecutionModel execution : model.getAuthenticationExecutions(flow.getId())) {
+ authenticationExecutions.add(flow.getId(), execution);
+ executionsById.put(execution.getId(), execution);
+ }
+ }
+ for (AuthenticatorModel authenticator : model.getAuthenticators()) {
+ authenticators.put(authenticator.getId(), authenticator);
+ }
}
@@ -405,4 +422,20 @@ public class CachedRealm {
public MultivaluedHashMap getIdentityProviderMappers() {
return identityProviderMappers;
}
+
+ public Map getAuthenticationFlows() {
+ return authenticationFlows;
+ }
+
+ public Map getAuthenticators() {
+ return authenticators;
+ }
+
+ public MultivaluedHashMap getAuthenticationExecutions() {
+ return authenticationExecutions;
+ }
+
+ public Map getExecutionsById() {
+ return executionsById;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 5c1d7e3624..6689a24970 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1,6 +1,9 @@
package org.keycloak.models.jpa;
import org.keycloak.enums.SslRequired;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
@@ -13,6 +16,9 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderCreationEventImpl;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.jpa.entities.AuthenticationExecutionEntity;
+import org.keycloak.models.jpa.entities.AuthenticationFlowEntity;
+import org.keycloak.models.jpa.entities.AuthenticatorEntity;
import org.keycloak.models.jpa.entities.ClientEntity;
import org.keycloak.models.jpa.entities.IdentityProviderEntity;
import org.keycloak.models.jpa.entities.IdentityProviderMapperEntity;
@@ -1486,4 +1492,207 @@ public class RealmAdapter implements RealmModel {
return mapper;
}
+ @Override
+ public List getAuthenticationFlows() {
+ TypedQuery query = em.createNamedQuery("getAuthenticationFlowsByRealm", AuthenticationFlowEntity.class);
+ query.setParameter("realm", realm);
+ List flows = query.getResultList();
+ if (flows.size() == 0) return Collections.EMPTY_LIST;
+ List models = new LinkedList<>();
+ for (AuthenticationFlowEntity entity : flows) {
+ AuthenticationFlowModel model = entityToModel(entity);
+ models.add(model);
+ }
+ return models;
+ }
+
+ protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) {
+ AuthenticationFlowModel model = new AuthenticationFlowModel();
+ model.setId(entity.getId());
+ model.setAlias(entity.getAlias());
+ model.setDescription(entity.getDescription());
+ return model;
+ }
+
+ @Override
+ public AuthenticationFlowModel getAuthenticationFlowById(String id) {
+ AuthenticationFlowEntity entity = em.find(AuthenticationFlowEntity.class, id);
+ if (entity == null) return null;
+ return entityToModel(entity);
+ }
+
+ @Override
+ public void removeAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity entity = em.find(AuthenticationFlowEntity.class, model.getId());
+ if (entity == null) return;
+ em.remove(entity);
+ em.flush();
+ }
+
+ @Override
+ public void updateAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity entity = em.find(AuthenticationFlowEntity.class, model.getId());
+ if (entity == null) return;
+ entity.setAlias(model.getAlias());
+ entity.setDescription(model.getDescription());
+
+ }
+
+ @Override
+ public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity entity = new AuthenticationFlowEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setAlias(model.getAlias());
+ entity.setDescription(model.getDescription());
+ entity.setRealm(realm);
+ realm.getAuthenticationFlows().add(entity);
+ em.persist(entity);
+ em.flush();
+ model.setId(entity.getId());
+ return model;
+ }
+
+ @Override
+ public List getAuthenticationExecutions(String flowId) {
+ TypedQuery query = em.createNamedQuery("getAuthenticationExecutionsByFlow", AuthenticationExecutionEntity.class);
+ AuthenticationFlowEntity flow = em.getReference(AuthenticationFlowEntity.class, flowId);
+ query.setParameter("realm", realm);
+ query.setParameter("flow", flow);
+ List queryResult = query.getResultList();
+ List executions = new LinkedList<>();
+ for (AuthenticationExecutionEntity entity : queryResult) {
+ AuthenticationExecutionModel model = entityToModel(entity);
+ executions.add(model);
+ }
+ return executions;
+ }
+
+ public AuthenticationExecutionModel entityToModel(AuthenticationExecutionEntity entity) {
+ AuthenticationExecutionModel model = new AuthenticationExecutionModel();
+ model.setId(entity.getId());
+ model.setUserSetupAllowed(entity.isUserSetupAllowed());
+ model.setRequirement(entity.getRequirement());
+ model.setPriority(entity.getPriority());
+ model.setAuthenticator(entity.getAuthenticator());
+ model.setParentFlow(entity.getFlow().getId());
+ model.setAutheticatorFlow(entity.isAutheticatorFlow());
+ return model;
+ }
+
+ @Override
+ public AuthenticationExecutionModel getAuthenticationExecutionById(String id) {
+ AuthenticationExecutionEntity entity = em.find(AuthenticationExecutionEntity.class, id);
+ if (entity == null) return null;
+ return entityToModel(entity);
+ }
+
+ @Override
+ public AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = new AuthenticationExecutionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setAuthenticator(model.getAuthenticator());
+ entity.setPriority(model.getPriority());
+ entity.setRequirement(model.getRequirement());
+ AuthenticationFlowEntity flow = em.find(AuthenticationFlowEntity.class, model.getParentFlow());
+ entity.setFlow(flow);
+ flow.getExecutions().add(entity);
+ entity.setRealm(realm);
+ entity.setUserSetupAllowed(model.isUserSetupAllowed());
+ entity.setAutheticatorFlow(model.isAutheticatorFlow());
+ em.persist(entity);
+ em.flush();
+ model.setId(entity.getId());
+ return model;
+
+ }
+
+ @Override
+ public void updateAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = em.find(AuthenticationExecutionEntity.class, model.getId());
+ if (entity == null) return;
+ entity.setAutheticatorFlow(model.isAutheticatorFlow());
+ entity.setAuthenticator(model.getAuthenticator());
+ entity.setPriority(model.getPriority());
+ entity.setRequirement(model.getRequirement());
+ entity.setUserSetupAllowed(model.isUserSetupAllowed());
+ em.flush();
+ }
+
+ @Override
+ public void removeAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = em.find(AuthenticationExecutionEntity.class, model.getId());
+ if (entity == null) return;
+ em.remove(entity);
+ em.flush();
+
+ }
+
+ @Override
+ public AuthenticatorModel addAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity auth = new AuthenticatorEntity();
+ auth.setId(KeycloakModelUtils.generateId());
+ auth.setAlias(model.getAlias());
+ auth.setRealm(realm);
+ auth.setProviderId(model.getProviderId());
+ auth.setConfig(model.getConfig());
+ realm.getAuthenticators().add(auth);
+ em.persist(auth);
+ em.flush();
+ model.setId(auth.getId());
+ return model;
+ }
+
+ @Override
+ public void removeAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity entity = em.find(AuthenticatorEntity.class, model.getId());
+ if (entity == null) return;
+ em.remove(entity);
+ em.flush();
+
+ }
+
+ @Override
+ public AuthenticatorModel getAuthenticatorById(String id) {
+ AuthenticatorEntity entity = em.find(AuthenticatorEntity.class, id);
+ if (entity == null) return null;
+ return entityToModel(entity);
+ }
+
+ public AuthenticatorModel entityToModel(AuthenticatorEntity entity) {
+ AuthenticatorModel model = new AuthenticatorModel();
+ model.setId(entity.getId());
+ model.setProviderId(entity.getProviderId());
+ model.setAlias(entity.getAlias());
+ Map config = new HashMap<>();
+ if (entity.getConfig() != null) config.putAll(entity.getConfig());
+ model.setConfig(config);
+ return model;
+ }
+
+ @Override
+ public void updateAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity entity = em.find(AuthenticatorEntity.class, model.getId());
+ if (entity == null) return;
+ entity.setAlias(model.getAlias());
+ entity.setProviderId(model.getProviderId());
+ if (entity.getConfig() == null) {
+ entity.setConfig(model.getConfig());
+ } else {
+ entity.getConfig().clear();
+ entity.getConfig().putAll(model.getConfig());
+ }
+ em.flush();
+
+ }
+
+ @Override
+ public List getAuthenticators() {
+ List authenticators = new LinkedList<>();
+ for (AuthenticatorEntity entity : realm.getAuthenticators()) {
+ authenticators.add(entityToModel(entity));
+ }
+ return authenticators;
+ }
+
+
}
\ No newline at end of file
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
new file mode 100755
index 0000000000..be8720aad8
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java
@@ -0,0 +1,117 @@
+package org.keycloak.models.jpa.entities;
+
+import org.keycloak.models.AuthenticationExecutionModel;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+@Table(name="AUTHENTICATION_EXECUTION")
+@Entity
+@NamedQueries({
+ @NamedQuery(name="getAuthenticationExecutionsByFlow", query="select authenticator from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.flow = :flow"),
+ @NamedQuery(name="deleteAuthenticationExecutionsByRealm", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm"),
+ @NamedQuery(name="deleteAuthenticationExecutionsByRealmAndFlow", query="delete from AuthenticationExecutionEntity authenticator where authenticator.realm = :realm and authenticator.flow = :flow"),
+})
+public class AuthenticationExecutionEntity {
+ @Id
+ @Column(name="ID", length = 36)
+ protected String id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "REALM_ID")
+ protected RealmEntity realm;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "FLOW_ID")
+ protected AuthenticationFlowEntity flow;
+
+ @Column(name="AUTHENTICATOR")
+ protected String authenticator;
+
+ @Column(name="REQUIREMENT")
+ protected AuthenticationExecutionModel.Requirement requirement;
+
+ @Column(name="PRIORITY")
+ protected int priority;
+
+ @Column(name="USER_SETUP_ALLOWED")
+ private boolean userSetupAllowed;
+
+ @Column(name="AUTHENTICATOR_FLOW")
+ private boolean autheticatorFlow;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ public String getAuthenticator() {
+ return authenticator;
+ }
+
+ public void setAuthenticator(String authenticator) {
+ this.authenticator = authenticator;
+ }
+
+ public AuthenticationExecutionModel.Requirement getRequirement() {
+ return requirement;
+ }
+
+ public void setRequirement(AuthenticationExecutionModel.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 isAutheticatorFlow() {
+ return autheticatorFlow;
+ }
+
+ public void setAutheticatorFlow(boolean autheticatorFlow) {
+ this.autheticatorFlow = autheticatorFlow;
+ }
+
+ public AuthenticationFlowEntity getFlow() {
+ return flow;
+ }
+
+ public void setFlow(AuthenticationFlowEntity flow) {
+ this.flow = flow;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
new file mode 100755
index 0000000000..464fb4a770
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java
@@ -0,0 +1,89 @@
+package org.keycloak.models.jpa.entities;
+
+import org.keycloak.models.AuthenticatorModel;
+
+import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+@Table(name="AUTHENTICATION_FLOW")
+@Entity
+@NamedQueries({
+ @NamedQuery(name="getAuthenticationFlowsByRealm", query="select flow from AuthenticationFlowEntity flow where flow.realm = :realm"),
+ @NamedQuery(name="deleteAuthenticationFlowByRealm", query="delete from AuthenticationFlowEntity flow where flow.realm = :realm")
+})
+public class AuthenticationFlowEntity {
+ @Id
+ @Column(name="ID", length = 36)
+ protected String id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "REALM_ID")
+ protected RealmEntity realm;
+
+ @Column(name="ALIAS")
+ protected String alias;
+
+ @Column(name="DESCRIPTION")
+ protected String description;
+
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "flow")
+ Collection executions = new ArrayList();
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ public String getAlias() {
+ return alias;
+ }
+
+ public void setAlias(String alias) {
+ this.alias = alias;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Collection getExecutions() {
+ return executions;
+ }
+
+ public void setExecutions(Collection executions) {
+ this.executions = executions;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorEntity.java
new file mode 100755
index 0000000000..1e97cd2e5b
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorEntity.java
@@ -0,0 +1,89 @@
+package org.keycloak.models.jpa.entities;
+
+import org.keycloak.models.AuthenticatorModel;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+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.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+@Table(name="AUTHENTICATOR")
+@Entity
+@NamedQueries({
+ @NamedQuery(name="deleteAuthenticatorsByRealm", query="delete from AuthenticatorEntity authenticator where authenticator.realm = :realm"),})
+public class AuthenticatorEntity {
+ @Id
+ @Column(name="ID", length = 36)
+ protected String id;
+
+ @Column(name="ALIAS")
+ protected String alias;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "REALM_ID")
+ protected RealmEntity realm;
+
+ @Column(name="PROVIDER_ID")
+ protected String providerId;
+
+ @ElementCollection
+ @MapKeyColumn(name="NAME")
+ @Column(name="VALUE")
+ @CollectionTable(name="AUTHENTICATOR_CONFIG", joinColumns={ @JoinColumn(name="AUTHENTICATOR_ID") })
+ private Map config;
+
+ 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 RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ public Map getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map config) {
+ this.config = config;
+ }
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 6c28b9f9fa..089cd67039 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -154,6 +154,12 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection identityProviderMappers = new ArrayList();
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ Collection authenticators = new ArrayList<>();
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ Collection authenticationFlows = new ArrayList<>();
+
@Column(name="INTERNATIONALIZATION_ENABLED")
@@ -546,5 +552,21 @@ public class RealmEntity {
public void setIdentityProviderMappers(Collection identityProviderMappers) {
this.identityProviderMappers = identityProviderMappers;
}
+
+ public Collection getAuthenticators() {
+ return authenticators;
+ }
+
+ public void setAuthenticators(Collection authenticators) {
+ this.authenticators = authenticators;
+ }
+
+ public Collection getAuthenticationFlows() {
+ return authenticationFlows;
+ }
+
+ public void setAuthenticationFlows(Collection authenticationFlows) {
+ this.authenticationFlows = authenticationFlows;
+ }
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index a36f83e645..9b57238072 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -5,6 +5,9 @@ import com.mongodb.QueryBuilder;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.enums.SslRequired;
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticationFlowModel;
+import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
@@ -18,6 +21,9 @@ import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProviderCreationEventImpl;
import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.entities.AuthenticationExecutionEntity;
+import org.keycloak.models.entities.AuthenticationFlowEntity;
+import org.keycloak.models.entities.AuthenticatorEntity;
import org.keycloak.models.entities.IdentityProviderEntity;
import org.keycloak.models.entities.IdentityProviderMapperEntity;
import org.keycloak.models.entities.RequiredCredentialEntity;
@@ -1178,7 +1184,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme
this.realm.getIdentityProviderMappers().remove(toDelete);
updateMongoEntity();
}
-
}
@Override
@@ -1222,6 +1227,243 @@ public class RealmAdapter extends AbstractMongoAdapter impleme
return mapping;
}
+ @Override
+ public List getAuthenticationFlows() {
+ List flows = getMongoEntity().getAuthenticationFlows();
+ if (flows.size() == 0) return Collections.EMPTY_LIST;
+ List models = new LinkedList<>();
+ for (AuthenticationFlowEntity entity : flows) {
+ AuthenticationFlowModel model = entityToModel(entity);
+ models.add(model);
+ }
+ return models;
+ }
+
+ protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) {
+ AuthenticationFlowModel model = new AuthenticationFlowModel();
+ model.setId(entity.getId());
+ model.setAlias(entity.getAlias());
+ model.setDescription(entity.getDescription());
+ return model;
+ }
+
+ @Override
+ public AuthenticationFlowModel getAuthenticationFlowById(String id) {
+ for (AuthenticationFlowModel model : getAuthenticationFlows()) {
+ if (model.getId().equals(id)) return model;
+ }
+ return null;
+ }
+
+ protected AuthenticationFlowEntity getFlowEntity(String id) {
+ List flows = getMongoEntity().getAuthenticationFlows();
+ for (AuthenticationFlowEntity entity : flows) {
+ if (id.equals(entity.getId())) return entity;
+ }
+ return null;
+
+ }
+
+ @Override
+ public void removeAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity toDelete = getFlowEntity(model.getId());
+ if (toDelete == null) return;
+ getMongoEntity().getAuthenticationFlows().remove(toDelete);
+ updateMongoEntity();
+ }
+
+ @Override
+ public void updateAuthenticationFlow(AuthenticationFlowModel model) {
+ List flows = getMongoEntity().getAuthenticationFlows();
+ AuthenticationFlowEntity toUpdate = getFlowEntity(model.getId());;
+ if (toUpdate == null) return;
+ toUpdate.setAlias(model.getAlias());
+ toUpdate.setDescription(model.getDescription());
+ updateMongoEntity();
+ }
+
+ @Override
+ public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) {
+ AuthenticationFlowEntity entity = new AuthenticationFlowEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setAlias(model.getAlias());
+ entity.setDescription(model.getDescription());
+ getMongoEntity().getAuthenticationFlows().add(entity);
+ model.setId(entity.getId());
+ updateMongoEntity();
+ return model;
+ }
+
+ @Override
+ public List getAuthenticationExecutions(String flowId) {
+ AuthenticationFlowEntity flow = getFlowEntity(flowId);
+ if (flow == null) return Collections.EMPTY_LIST;
+
+ List queryResult = flow.getExecutions();
+ List executions = new LinkedList<>();
+ for (AuthenticationExecutionEntity entity : queryResult) {
+ AuthenticationExecutionModel model = entityToModel(entity);
+ executions.add(model);
+ }
+ return executions;
+ }
+
+ public AuthenticationExecutionModel entityToModel(AuthenticationExecutionEntity entity) {
+ AuthenticationExecutionModel model = new AuthenticationExecutionModel();
+ model.setId(entity.getId());
+ model.setUserSetupAllowed(entity.isUserSetupAllowed());
+ model.setRequirement(entity.getRequirement());
+ model.setPriority(entity.getPriority());
+ model.setAuthenticator(entity.getAuthenticator());
+ model.setParentFlow(entity.getParentFlow());
+ model.setAutheticatorFlow(entity.isAutheticatorFlow());
+ return model;
+ }
+
+ @Override
+ public AuthenticationExecutionModel getAuthenticationExecutionById(String id) {
+ AuthenticationExecutionEntity execution = getAuthenticationExecutionEntity(id);
+ return entityToModel(execution);
+ }
+
+ public AuthenticationExecutionEntity getAuthenticationExecutionEntity(String id) {
+ List flows = getMongoEntity().getAuthenticationFlows();
+ for (AuthenticationFlowEntity entity : flows) {
+ for (AuthenticationExecutionEntity exe : entity.getExecutions()) {
+ if (exe.getId().equals(id)) {
+ return exe;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = new AuthenticationExecutionEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setAuthenticator(model.getAuthenticator());
+ entity.setPriority(model.getPriority());
+ entity.setRequirement(model.getRequirement());
+ entity.setUserSetupAllowed(model.isUserSetupAllowed());
+ entity.setAutheticatorFlow(model.isAutheticatorFlow());
+ AuthenticationFlowEntity flow = getFlowEntity(model.getId());
+ flow.getExecutions().add(entity);
+ updateMongoEntity();
+ model.setId(entity.getId());
+ return model;
+
+ }
+
+ @Override
+ public void updateAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = null;
+ AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow());
+ for (AuthenticationExecutionEntity exe : flow.getExecutions()) {
+ if (exe.getId().equals(model.getId())) {
+ entity = exe;
+ }
+ }
+ if (entity == null) return;
+ entity.setAutheticatorFlow(model.isAutheticatorFlow());
+ entity.setAuthenticator(model.getAuthenticator());
+ entity.setPriority(model.getPriority());
+ entity.setRequirement(model.getRequirement());
+ entity.setUserSetupAllowed(model.isUserSetupAllowed());
+ updateMongoEntity();
+ }
+
+ @Override
+ public void removeAuthenticatorExecution(AuthenticationExecutionModel model) {
+ AuthenticationExecutionEntity entity = null;
+ AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow());
+ for (AuthenticationExecutionEntity exe : flow.getExecutions()) {
+ if (exe.getId().equals(model.getId())) {
+ entity = exe;
+ }
+ }
+ if (entity == null) return;
+ flow.getExecutions().remove(entity);
+ updateMongoEntity();
+
+ }
+
+ @Override
+ public List getAuthenticators() {
+ List authenticators = new LinkedList<>();
+ for (AuthenticatorEntity entity : getMongoEntity().getAuthenticators()) {
+ authenticators.add(entityToModel(entity));
+ }
+ return authenticators;
+ }
+
+ @Override
+ public AuthenticatorModel addAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity auth = new AuthenticatorEntity();
+ auth.setId(KeycloakModelUtils.generateId());
+ auth.setAlias(model.getAlias());
+ auth.setProviderId(model.getProviderId());
+ auth.setConfig(model.getConfig());
+ realm.getAuthenticators().add(auth);
+ model.setId(auth.getId());
+ updateMongoEntity();
+ return model;
+ }
+
+ @Override
+ public void removeAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity entity = getAuthenticatorEntity(model.getId());
+ if (entity == null) return;
+ getMongoEntity().getAuthenticators().remove(entity);
+ updateMongoEntity();
+
+ }
+
+ @Override
+ public AuthenticatorModel getAuthenticatorById(String id) {
+ AuthenticatorEntity entity = getAuthenticatorEntity(id);
+ if (entity == null) return null;
+ return entityToModel(entity);
+ }
+
+ public AuthenticatorEntity getAuthenticatorEntity(String id) {
+ AuthenticatorEntity entity = null;
+ for (AuthenticatorEntity auth : getMongoEntity().getAuthenticators()) {
+ if (auth.getId().equals(id)) {
+ entity = auth;
+ break;
+ }
+ }
+ return entity;
+ }
+
+ public AuthenticatorModel entityToModel(AuthenticatorEntity entity) {
+ AuthenticatorModel model = new AuthenticatorModel();
+ model.setId(entity.getId());
+ model.setProviderId(entity.getProviderId());
+ model.setAlias(entity.getAlias());
+ Map config = new HashMap<>();
+ if (entity.getConfig() != null) config.putAll(entity.getConfig());
+ model.setConfig(config);
+ return model;
+ }
+
+ @Override
+ public void updateAuthenticator(AuthenticatorModel model) {
+ AuthenticatorEntity entity = getAuthenticatorEntity(model.getId());
+ if (entity == null) return;
+ entity.setAlias(model.getAlias());
+ entity.setProviderId(model.getProviderId());
+ if (entity.getConfig() == null) {
+ entity.setConfig(model.getConfig());
+ } else {
+ entity.getConfig().clear();
+ entity.getConfig().putAll(model.getConfig());
+ }
+ updateMongoEntity();
+ }
+
+
@Override
public Set getUserFederationMappers() {
Set mappers = new HashSet();
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 6f630049b6..5b20edf43c 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -3,6 +3,7 @@ package org.keycloak.authentication;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.ClientConnection;
import org.keycloak.events.EventBuilder;
+import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
@@ -52,7 +53,7 @@ public class AuthenticationProcessor {
protected ClientConnection connection;
protected UriInfo uriInfo;
protected KeycloakSession session;
- protected List authenticators;
+ protected List executions;
protected BruteForceProtector protector;
protected EventBuilder eventBuilder;
protected HttpRequest request;
@@ -159,6 +160,13 @@ public class AuthenticationProcessor {
this.status = Status.FAILURE_CHALLENGE;
this.challenge = challenge;
+ }
+ @Override
+ public void failure(Error error, Response challenge) {
+ this.error = error;
+ this.status = Status.FAILED;
+ this.challenge = challenge;
+
}
@Override
@@ -264,28 +272,29 @@ public class AuthenticationProcessor {
validateUser(authUser);
Response challenge = null;
Map previousAttempts = clientSession.getAuthenticators();
- for (AuthenticatorModel model : authenticators) {
- UserSessionModel.AuthenticatorStatus oldStatus = previousAttempts.get(model.getAlias());
+ for (AuthenticationExecutionModel model : executions) {
+ UserSessionModel.AuthenticatorStatus oldStatus = previousAttempts.get(model.getId());
if (isProcessed(oldStatus)) continue;
- AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, model.getProviderId());
- Authenticator authenticator = factory.create(model);
+ AuthenticatorModel authenticatorModel = realm.getAuthenticatorById(model.getAuthenticator());
+ AuthenticatorFactory factory = (AuthenticatorFactory)session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, authenticatorModel.getProviderId());
+ Authenticator authenticator = factory.create(authenticatorModel);
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);
+ if (authUser != null && model.getRequirement() == AuthenticationExecutionModel.Requirement.ALTERNATIVE) {
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SKIPPED);
continue;
}
authUser = clientSession.getAuthenticatedUser();
if (authenticator.requiresUser() && authUser != null && !authenticator.configuredFor(authUser)) {
- if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) {
+ if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
if (model.isUserSetupAllowed()) {
- clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SETUP_REQUIRED);
authUser.addRequiredAction(authenticator.getRequiredAction());
} else {
@@ -294,25 +303,26 @@ public class AuthenticationProcessor {
}
continue;
}
- Result context = new Result(model, authenticator);
+ Result context = new Result(authenticatorModel, authenticator);
authenticator.authenticate(context);
Status result = context.getStatus();
if (result == Status.SUCCESS){
- clientSession.setAuthenticatorStatus(model.getAlias(), UserSessionModel.AuthenticatorStatus.SUCCESS);
- if (model.isMasterAuthenticator()) return authenticationComplete();
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.SUCCESS);
+ //if (model.isMasterAuthenticator()) return authenticationComplete();
continue;
} else if (result == Status.FAILED) {
+ if (context.challenge != null) return context.challenge;
throw new AuthException(context.error);
} else if (result == Status.CHALLENGE) {
- if (model.getRequirement() == AuthenticatorModel.Requirement.REQUIRED) return context.challenge;
+ if (model.getRequirement() == AuthenticationExecutionModel.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);
+ if (model.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) throw new AuthException(Error.INVALID_CREDENTIALS);
+ clientSession.setAuthenticatorStatus(model.getId(), UserSessionModel.AuthenticatorStatus.ATTEMPTED);
continue;
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
index 162c9b649f..8a91d5e08a 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java
@@ -9,6 +9,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.services.managers.BruteForceProtector;
+import org.keycloak.services.managers.ClientSessionCode;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -48,6 +49,7 @@ public interface AuthenticatorContext {
void success();
void failure(AuthenticationProcessor.Error error);
+ void failure(AuthenticationProcessor.Error error, Response response);
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
index 28290fc22e..04b445e90c 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorFactory.java
@@ -1,13 +1,16 @@
package org.keycloak.authentication;
import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderFactory;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
-public interface AuthenticatorFactory extends ProviderFactory {
+public interface AuthenticatorFactory extends ProviderFactory, ConfiguredProvider {
Authenticator create(AuthenticatorModel model);
+ String getDisplayCategory();
+ String getDisplayType();
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java
new file mode 100755
index 0000000000..684d07420c
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java
@@ -0,0 +1,33 @@
+package org.keycloak.authentication;
+
+import org.keycloak.protocol.ProtocolMapper;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author Stian Thorgersen
+ */
+public class AuthenticatorSpi implements Spi {
+
+ @Override
+ public boolean isPrivate() {
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "authenticator";
+ }
+
+ @Override
+ public Class extends Provider> getProviderClass() {
+ return Authenticator.class;
+ }
+
+ @Override
+ public Class extends ProviderFactory> getProviderFactoryClass() {
+ return AuthenticatorFactory.class;
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/authenticators/AuthenticationFlow.java
new file mode 100755
index 0000000000..98e5826554
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/AuthenticationFlow.java
@@ -0,0 +1,60 @@
+package org.keycloak.authentication.authenticators;
+
+import org.keycloak.models.AuthenticationExecutionModel;
+import org.keycloak.models.AuthenticatorModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class AuthenticationFlow {
+
+ /**
+ * Hardcoded models just to test this stuff. It is temporary
+ */
+ static List hardcoded = new ArrayList<>();
+
+ /*
+ static {
+ AuthenticationExecutionModel model = new AuthenticationExecutionModel();
+ model.setId("1");
+ model.setAlias("cookie");
+ model.setMasterAuthenticator(true);
+ model.setProviderId(CookieAuthenticatorFactory.PROVIDER_ID);
+ model.setPriority(0);
+ model.setRequirement(AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+ model.setUserSetupAllowed(false);
+ hardcoded.add(model);
+ model = new AuthenticatorModel();
+ model.setId("2");
+ model.setAlias("user form");
+ model.setMasterAuthenticator(false);
+ model.setProviderId(LoginFormUsernameAuthenticatorFactory.PROVIDER_ID);
+ model.setPriority(1);
+ model.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+ model.setUserSetupAllowed(false);
+ hardcoded.add(model);
+ model = new AuthenticatorModel();
+ model.setId("3");
+ model.setAlias("password form");
+ model.setMasterAuthenticator(false);
+ model.setProviderId(LoginFormUsernameAuthenticatorFactory.PROVIDER_ID);
+ model.setPriority(2);
+ model.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
+ model.setUserSetupAllowed(false);
+ hardcoded.add(model);
+ model = new AuthenticatorModel();
+ model.setId("4");
+ model.setAlias("otp form");
+ model.setMasterAuthenticator(false);
+ model.setProviderId(OTPFormAuthenticatorFactory.PROVIDER_ID);
+ model.setPriority(3);
+ model.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
+ model.setUserSetupAllowed(false);
+ hardcoded.add(model);
+ }
+ */
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
index ab50b3dc51..0f2ec076a6 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/CookieAuthenticatorFactory.java
@@ -6,12 +6,16 @@ import org.keycloak.authentication.AuthenticatorFactory;
import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
/**
* @author Bill Burke
* @version $Revision: 1 $
*/
public class CookieAuthenticatorFactory implements AuthenticatorFactory {
+ public static final String PROVIDER_ID = "auth-cookie";
static CookieAuthenticator SINGLETON = new CookieAuthenticator();
@Override
public Authenticator create(AuthenticatorModel model) {
@@ -40,6 +44,26 @@ public class CookieAuthenticatorFactory implements AuthenticatorFactory {
@Override
public String getId() {
- return "auth-cookie";
+ return PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "Complete Authenticator";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Cookie Authenticator";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Validates the SSO cookie set by the auth server.";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
index 7cf5b71705..f2f2ef25dc 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java
@@ -6,6 +6,7 @@ import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.messages.Messages;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@@ -31,19 +32,23 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator {
validateOTP(context);
}
+ protected Response badPassword(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.INVALID_USER).createLogin();
+ }
+
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);
+ Response challengeResponse = badPassword(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);
+ Response challengeResponse = badPassword(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return;
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java
new file mode 100755
index 0000000000..ef240cb052
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java
@@ -0,0 +1,70 @@
+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;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class LoginFormOTPAuthenticatorFactory implements AuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "auth-login-form-otp";
+
+ @Override
+ public Authenticator create(AuthenticatorModel model) {
+ return new LoginFormOTPAuthenticator(model);
+ }
+
+ @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 PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "Credential Validation";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Login Form OTP";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Validates an OTP that is specified on the login page.";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
index 1aae3b2445..bcc4c5e6fd 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java
@@ -6,6 +6,7 @@ import org.keycloak.models.AuthenticatorModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.services.messages.Messages;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@@ -31,19 +32,24 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat
validatePassword(context);
}
+ protected Response badPassword(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.INVALID_USER).createLogin();
+ }
+
+
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);
+ Response challengeResponse = badPassword(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);
+ Response challengeResponse = badPassword(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_CREDENTIALS, challengeResponse);
return;
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java
new file mode 100755
index 0000000000..5da119e5c3
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java
@@ -0,0 +1,70 @@
+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;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class LoginFormPasswordAuthenticatorFactory implements AuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "auth-login-form-password";
+
+ @Override
+ public Authenticator create(AuthenticatorModel model) {
+ return new LoginFormPasswordAuthenticator(model);
+ }
+
+ @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 PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "Credential Validation";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Login Form Password";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Validates a user password that is specified on the login page.";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
index 1d1f73f896..78b9096bee 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java
@@ -6,11 +6,13 @@ import org.keycloak.authentication.Authenticator;
import org.keycloak.authentication.AuthenticatorContext;
import org.keycloak.login.LoginFormsProvider;
import org.keycloak.models.AuthenticatorModel;
+import org.keycloak.models.ClientSessionModel;
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.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import javax.ws.rs.core.MultivaluedMap;
@@ -59,19 +61,37 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
}
protected Response challenge(AuthenticatorContext context, MultivaluedMap formData) {
- LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(new ClientSessionCode(context.getRealm(), context.getClientSession()).getCode());
+ LoginFormsProvider forms = loginForm(context);
if (formData.size() > 0) forms.setFormData(formData);
return forms.createLogin();
}
+ protected LoginFormsProvider loginForm(AuthenticatorContext context) {
+ ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession());
+ code.setAction(ClientSessionModel.Action.AUTHENTICATE);
+ return context.getSession().getProvider(LoginFormsProvider.class)
+ .setClientSessionCode(code.getCode());
+ }
+
+ protected Response invalidUser(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.INVALID_USER).createLogin();
+ }
+
+ protected Response disabledUser(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.ACCOUNT_DISABLED).createLogin();
+ }
+
+ protected Response temporarilyDisabledUser(AuthenticatorContext context) {
+ return loginForm(context).setError(Messages.ACCOUNT_TEMPORARILY_DISABLED).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);
+ Response challengeResponse = invalidUser(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
return;
}
@@ -82,17 +102,19 @@ public class LoginFormUsernameAuthenticator implements Authenticator {
public boolean invalidUser(AuthenticatorContext context, UserModel user) {
if (user == null) {
- Response challengeResponse = challenge(context);
+ Response challengeResponse = invalidUser(context);
context.failureChallenge(AuthenticationProcessor.Error.INVALID_USER, challengeResponse);
return true;
}
if (!user.isEnabled()) {
- context.failure(AuthenticationProcessor.Error.USER_DISABLED);
+ Response challengeResponse = disabledUser(context);
+ context.failureChallenge(AuthenticationProcessor.Error.USER_DISABLED, challengeResponse);
return true;
}
if (context.getRealm().isBruteForceProtected()) {
if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) {
- context.failure(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED);
+ Response challengeResponse = temporarilyDisabledUser(context);
+ context.failureChallenge(AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED, challengeResponse);
return true;
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java
new file mode 100755
index 0000000000..de86b08610
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java
@@ -0,0 +1,70 @@
+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;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class LoginFormUsernameAuthenticatorFactory implements AuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "auth-login-form-username";
+
+ @Override
+ public Authenticator create(AuthenticatorModel model) {
+ return new LoginFormUsernameAuthenticator(model);
+ }
+
+ @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 PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "User Validation";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "Login Form Username";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Validates a username that is specified on the login page.";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+}
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
new file mode 100755
index 0000000000..f5fe0e71a7
--- /dev/null
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java
@@ -0,0 +1,70 @@
+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;
+import org.keycloak.provider.ProviderConfigProperty;
+
+import java.util.List;
+
+/**
+ * @author Bill Burke
+ * @version $Revision: 1 $
+ */
+public class OTPFormAuthenticatorFactory implements AuthenticatorFactory {
+
+ public static final String PROVIDER_ID = "auth-otp-form";
+
+ @Override
+ public Authenticator create(AuthenticatorModel model) {
+ return new OTPFormAuthenticator(model);
+ }
+
+ @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 PROVIDER_ID;
+ }
+
+ @Override
+ public String getDisplayCategory() {
+ return "Credential Validation";
+ }
+
+ @Override
+ public String getDisplayType() {
+ return "OTP Form";
+ }
+
+ @Override
+ public String getHelpText() {
+ return "Validates a OTP on a separate OTP form.";
+ }
+
+ @Override
+ public List getConfigProperties() {
+ return null;
+ }
+}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
new file mode 100755
index 0000000000..20dff3bd8f
--- /dev/null
+++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory
@@ -0,0 +1,5 @@
+org.keycloak.authentication.authenticators.CookieAuthenticatorFactory
+org.keycloak.authentication.authenticators.LoginFormOTPAuthenticatorFactory
+org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory
+org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory
+org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory
\ No newline at end of file