From 68976f5b0cc678a5c2713bfa734d639fbdb22510 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 22 May 2015 16:03:26 -0400 Subject: [PATCH] auth spi datamodel --- .../META-INF/jpa-changelog-1.3.0.Beta1.xml | 52 +++- .../main/resources/META-INF/persistence.xml | 3 + .../models/AuthenticationExecutionModel.java | 83 ++++++ .../models/AuthenticationFlowModel.java | 35 +++ .../keycloak/models/AuthenticatorModel.java | 78 ------ .../java/org/keycloak/models/RealmModel.java | 19 ++ .../AuthenticationExecutionEntity.java | 74 ++++++ .../entities/AuthenticationFlowEntity.java | 47 ++++ .../models/entities/AuthenticatorEntity.java | 46 ++++ .../keycloak/models/entities/RealmEntity.java | 18 ++ .../models/file/adapter/RealmAdapter.java | 234 +++++++++++++++++ .../keycloak/models/cache/RealmAdapter.java | 104 ++++++++ .../models/cache/entities/CachedRealm.java | 33 +++ .../org/keycloak/models/jpa/RealmAdapter.java | 209 +++++++++++++++ .../AuthenticationExecutionEntity.java | 117 +++++++++ .../entities/AuthenticationFlowEntity.java | 89 +++++++ .../jpa/entities/AuthenticatorEntity.java | 89 +++++++ .../models/jpa/entities/RealmEntity.java | 22 ++ .../mongo/keycloak/adapters/RealmAdapter.java | 244 ++++++++++++++++++ .../AuthenticationProcessor.java | 40 +-- .../authentication/AuthenticatorContext.java | 2 + .../authentication/AuthenticatorFactory.java | 5 +- .../authentication/AuthenticatorSpi.java | 33 +++ .../authenticators/AuthenticationFlow.java | 60 +++++ .../CookieAuthenticatorFactory.java | 26 +- .../LoginFormOTPAuthenticator.java | 9 +- .../LoginFormOTPAuthenticatorFactory.java | 70 +++++ .../LoginFormPasswordAuthenticator.java | 10 +- ...LoginFormPasswordAuthenticatorFactory.java | 70 +++++ .../LoginFormUsernameAuthenticator.java | 34 ++- ...LoginFormUsernameAuthenticatorFactory.java | 70 +++++ .../OTPFormAuthenticatorFactory.java | 70 +++++ ...ycloak.authentication.AuthenticatorFactory | 5 + 33 files changed, 1992 insertions(+), 108 deletions(-) create mode 100755 model/api/src/main/java/org/keycloak/models/AuthenticationExecutionModel.java create mode 100755 model/api/src/main/java/org/keycloak/models/AuthenticationFlowModel.java create mode 100755 model/api/src/main/java/org/keycloak/models/entities/AuthenticationExecutionEntity.java create mode 100755 model/api/src/main/java/org/keycloak/models/entities/AuthenticationFlowEntity.java create mode 100755 model/api/src/main/java/org/keycloak/models/entities/AuthenticatorEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationExecutionEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationFlowEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticatorEntity.java create mode 100755 services/src/main/java/org/keycloak/authentication/AuthenticatorSpi.java create mode 100755 services/src/main/java/org/keycloak/authentication/authenticators/AuthenticationFlow.java create mode 100755 services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticatorFactory.java create mode 100755 services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticatorFactory.java create mode 100755 services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticatorFactory.java create mode 100755 services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticatorFactory.java create mode 100755 services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory 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 27869eb1a5..516b9f4811 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 @@ -21,6 +21,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -30,7 +68,7 @@ - + @@ -39,10 +77,10 @@ - + - + @@ -63,10 +101,18 @@ ACTION = 3 + + + + + + + + diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index 5bcb77ee99..6399049ea4 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -24,6 +24,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 dca2067d98..90afbb3da6 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -168,6 +168,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 718a1c5ed1..822edc4017 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 @@ -73,6 +73,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() { @@ -473,6 +475,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 d8351027b3..feae19896f 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; @@ -29,6 +32,9 @@ import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; 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; @@ -1184,4 +1190,232 @@ 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()); + } + } + + } 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 33f2356106..2f6f8d61bc 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; @@ -17,6 +20,7 @@ import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -948,4 +952,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 22f57a95df..5329a9c60b 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; @@ -74,6 +77,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; @@ -178,6 +185,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); + } } @@ -396,4 +413,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 66b94d01ac..9a7eb979c6 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; @@ -10,6 +13,9 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; 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; @@ -1351,4 +1357,207 @@ public class RealmAdapter implements RealmModel { return mapping; } + @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 cdf314c17c..9ac1c3ed8a 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 @@ -151,6 +151,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") @@ -535,5 +541,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 d797de9fa3..579aea00ab 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; @@ -15,6 +18,9 @@ import org.keycloak.models.RealmProvider; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; 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; @@ -1169,6 +1175,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme if (toDelete != null) { this.realm.getIdentityProviderMappers().remove(toDelete); } + updateMongoEntity(); } @@ -1213,4 +1220,241 @@ 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(); + } + + } 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 getProviderClass() { + return Authenticator.class; + } + + @Override + public Class 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