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 2d9c38bfb0..1314afbc9d 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 @@ -7,6 +7,13 @@ + + + + + + + @@ -145,6 +152,63 @@ ACTION = 3 + + + + + + + + + + ACTION = 0 + + + + ACTION = 1 + + + + ACTION = 2 + + + + ACTION = 3 + + + + ACTION = 4 + + + + ACTION = 5 + + + + ACTION = 6 + + + + ACTION = 7 + + + + ACTION = 8 + + + + ACTION = 9 + + @@ -162,10 +226,12 @@ + + diff --git a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java index 5d6823770d..01f524a4c4 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientSessionModel.java @@ -22,9 +22,9 @@ public interface ClientSessionModel { public void setTimestamp(int timestamp); - public Action getAction(); + public String getAction(); - public void setAction(Action action); + public void setAction(String action); public Set getRoles(); public void setRoles(Set roles); 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 61bdbee405..187c3fd91e 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -156,6 +156,13 @@ public interface RealmModel extends RoleContainerModel { void updateDefaultRoles(String[] defaultRoles); + Set getDefaultRequiredActions(); + + void addDefaultRequiredAction(String action); + void removeDefaultRequiredAction(String action); + + void setDefaultRequiredActions(Set action); + // Key is clientId Map getClientNameMap(); 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 827c80bc89..b7f258828d 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 @@ -2,8 +2,10 @@ package org.keycloak.models.entities; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; /** * @author Marek Posolda @@ -76,6 +78,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private List identityProviderMappers = new ArrayList(); private List authenticationFlows = new ArrayList<>(); private List authenticators = new ArrayList<>(); + private List defaultRequiredActions = new ArrayList<>(); public String getName() { @@ -500,6 +503,14 @@ public class RealmEntity extends AbstractIdentifiableEntity { public void setAuthenticators(List authenticators) { this.authenticators = authenticators; } + + public List getDefaultRequiredActions() { + return defaultRequiredActions; + } + + public void setDefaultRequiredActions(List defaultRequiredActions) { + this.defaultRequiredActions = defaultRequiredActions; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index af057b6fa0..1f37bf1094 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -104,5 +104,7 @@ public class DefaultAuthenticationFlows { execution.setAutheticatorFlow(false); realm.addAuthenticatorExecution(execution); + // + } } 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 1a19bad3d6..bc2ff0ab3b 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 @@ -1548,4 +1548,37 @@ public class RealmAdapter implements RealmModel { mapper.setConfig(config); return mapper; } + + @Override + public Set getDefaultRequiredActions() { + Set result = new HashSet(); + if (realm.getDefaultRequiredActions() != null) { + result.addAll(realm.getDefaultRequiredActions()); + } + return result; + } + + @Override + public void addDefaultRequiredAction(String action) { + Set actions = getDefaultRequiredActions(); + actions.add(action); + setDefaultRequiredActions(actions); + + } + + @Override + public void removeDefaultRequiredAction(String action) { + Set actions = getDefaultRequiredActions(); + actions.remove(action); + setDefaultRequiredActions(actions); + + } + + @Override + public void setDefaultRequiredActions(Set action) { + List result = new ArrayList(); + result.addAll(action); + realm.setDefaultRequiredActions(result); + + } } 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 75b8b82923..db3339c577 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 @@ -1116,4 +1116,30 @@ public class RealmAdapter implements RealmModel { if (updated != null) return updated.getAuthenticatorById(id); return cached.getAuthenticators().get(id); } + + @Override + public Set getDefaultRequiredActions() { + return cached.getDefaultRequiredActions(); + } + + @Override + public void addDefaultRequiredAction(String action) { + getDelegateForUpdate(); + updated.addDefaultRequiredAction(action); + + } + + @Override + public void removeDefaultRequiredAction(String action) { + getDelegateForUpdate(); + updated.removeDefaultRequiredAction(action); + + } + + @Override + public void setDefaultRequiredActions(Set action) { + getDelegateForUpdate(); + updated.setDefaultRequiredActions(action); + + } } 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 016870e37c..904081edc2 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 @@ -98,6 +98,7 @@ public class CachedRealm { private Set supportedLocales = new HashSet(); private String defaultLocale; private MultivaluedHashMap identityProviderMappers = new MultivaluedHashMap<>(); + private Set defaultRequiredActions = new HashSet<>(); public CachedRealm() { } @@ -200,6 +201,7 @@ public class CachedRealm { for (AuthenticatorModel authenticator : model.getAuthenticators()) { authenticators.put(authenticator.getId(), authenticator); } + this.defaultRequiredActions.addAll(model.getDefaultRequiredActions()); } @@ -438,4 +440,8 @@ public class CachedRealm { public Map getExecutionsById() { return executionsById; } + + public Set getDefaultRequiredActions() { + return defaultRequiredActions; + } } 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 7db1b47010..f2f1929a87 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 @@ -1707,5 +1707,30 @@ public class RealmAdapter implements RealmModel { return authenticators; } + @Override + public Set getDefaultRequiredActions() { + Set result = new HashSet(); + result.addAll(realm.getDefaultRequiredActions()); + return result; + } + + + + @Override + public void setDefaultRequiredActions(Set actions) { + realm.setDefaultRequiredActions(actions); + } + + @Override + public void addDefaultRequiredAction(String action) { + realm.getDefaultRequiredActions().add(action); + } + + @Override + public void removeDefaultRequiredAction(String action) { + realm.getDefaultRequiredActions().remove(action); + } + + } \ No newline at end of file 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 41be2b16cf..5ccb017fc9 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 @@ -113,6 +113,12 @@ public class RealmEntity { @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection roles = new ArrayList(); + @ElementCollection + @Column(name="VALUE") + @CollectionTable(name = "DEFAULT_REQUIRED_ACTIONS", joinColumns={ @JoinColumn(name="REALM_ID") }) + protected Set defaultRequiredActions = new HashSet(); + + @ElementCollection @MapKeyColumn(name="NAME") @Column(name="VALUE") @@ -568,5 +574,13 @@ public class RealmEntity { public void setAuthenticationFlows(Collection authenticationFlows) { this.authenticationFlows = authenticationFlows; } + + public Set getDefaultRequiredActions() { + return defaultRequiredActions; + } + + public void setDefaultRequiredActions(Set defaultRequiredActions) { + this.defaultRequiredActions = defaultRequiredActions; + } } 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 aa25be77b5..24d907c02e 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 @@ -1590,4 +1590,32 @@ public class RealmAdapter extends AbstractMongoAdapter impleme mapper.setConfig(config); return mapper; } + + @Override + public Set getDefaultRequiredActions() { + Set result = new HashSet(); + result.addAll(realm.getDefaultRequiredActions()); + return result; + } + + + + @Override + public void setDefaultRequiredActions(Set actions) { + List result = new ArrayList(); + result.addAll(actions); + getMongoEntity().setDefaultRequiredActions(result); + updateMongoEntity(); + } + + @Override + public void addDefaultRequiredAction(String action) { + getMongoStore().pushItemToList(getMongoEntity(), "defaultRequiredActions", action, true, invocationContext); + } + + @Override + public void removeDefaultRequiredAction(String action) { + getMongoStore().pullItemFromList(getMongoEntity(), "defaultRequiredActions", action, invocationContext); + } + } diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java index 226a2ec9cc..78be9ee8e5 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java @@ -100,12 +100,12 @@ public class ClientSessionAdapter implements ClientSessionModel { } @Override - public Action getAction() { + public String getAction() { return entity.getAction(); } @Override - public void setAction(Action action) { + public void setAction(String action) { entity.setAction(action); update(); } diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java index 53c82181c7..340bf92dd9 100755 --- a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java +++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java @@ -24,7 +24,7 @@ public class ClientSessionEntity extends SessionEntity { private int timestamp; - private ClientSessionModel.Action action; + private String action; private Set roles; private Set protocolMappers; @@ -81,11 +81,11 @@ public class ClientSessionEntity extends SessionEntity { this.timestamp = timestamp; } - public ClientSessionModel.Action getAction() { + public String getAction() { return action; } - public void setAction(ClientSessionModel.Action action) { + public void setAction(String action) { this.action = action; } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java index 0ce21fa348..2ce7034363 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/ClientSessionAdapter.java @@ -189,12 +189,12 @@ public class ClientSessionAdapter implements ClientSessionModel { } @Override - public Action getAction() { + public String getAction() { return entity.getAction(); } @Override - public void setAction(Action action) { + public void setAction(String action) { entity.setAction(action); } diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java index bf0c520b78..300c45a67e 100755 --- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java +++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientSessionEntity.java @@ -54,8 +54,8 @@ public class ClientSessionEntity { @Column(name="AUTH_METHOD") protected String authMethod; - @Column(name="ACTION") - protected ClientSessionModel.Action action; + @Column(name="CURRENT_ACTION") + protected String action; @Column(name="AUTH_USER_ID") protected String userId; @@ -123,11 +123,11 @@ public class ClientSessionEntity { this.redirectUri = redirectUri; } - public ClientSessionModel.Action getAction() { + public String getAction() { return action; } - public void setAction(ClientSessionModel.Action action) { + public void setAction(String action) { this.action = action; } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java index cbea1d6ac7..0e0647fd8e 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/ClientSessionAdapter.java @@ -93,12 +93,12 @@ public class ClientSessionAdapter implements ClientSessionModel { } @Override - public ClientSessionModel.Action getAction() { + public String getAction() { return entity.getAction(); } @Override - public void setAction(ClientSessionModel.Action action) { + public void setAction(String action) { entity.setAction(action); } diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java index bd6eac91d0..e76f62499d 100755 --- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java +++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/entities/ClientSessionEntity.java @@ -24,7 +24,7 @@ public class ClientSessionEntity { private String authMethod; private int timestamp; - private ClientSessionModel.Action action; + private String action; private Set roles; private Set protocolMappers; private Map notes = new HashMap<>(); @@ -78,11 +78,11 @@ public class ClientSessionEntity { this.timestamp = timestamp; } - public ClientSessionModel.Action getAction() { + public String getAction() { return action; } - public void setAction(ClientSessionModel.Action action) { + public void setAction(String action) { this.action = action; } diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java index 52fa0aa7c7..ad1d0d7ee2 100755 --- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java +++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/ClientSessionAdapter.java @@ -108,12 +108,12 @@ public class ClientSessionAdapter extends AbstractMongoAdapter roles; private List protocolMappers; private Map notes = new HashMap(); @@ -82,11 +82,11 @@ public class MongoClientSessionEntity extends AbstractIdentifiableEntity impleme this.timestamp = timestamp; } - public ClientSessionModel.Action getAction() { + public String getAction() { return action; } - public void setAction(ClientSessionModel.Action action) { + public void setAction(String action) { this.action = action; } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java index 246e115202..69dcbeffa3 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -21,6 +21,7 @@ import org.keycloak.login.LoginFormsProvider; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; @@ -32,6 +33,7 @@ import org.keycloak.saml.common.exceptions.ConfigurationException; import org.keycloak.saml.common.exceptions.ProcessingException; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder; import org.keycloak.services.ErrorPage; +import org.keycloak.services.Urls; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.HttpAuthenticationManager; @@ -59,6 +61,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.security.PublicKey; +import java.util.List; /** * Resource class for the oauth/openid connect token service @@ -264,7 +267,7 @@ public class SamlService { ClientSessionModel clientSession = session.sessions().createClientSession(realm, client); clientSession.setAuthMethod(SamlProtocol.LOGIN_PROTOCOL); clientSession.setRedirectUri(redirect); - clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); + clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(SamlProtocol.SAML_BINDING, bindingType); clientSession.setNote(GeneralConstants.RELAY_STATE, relayState); @@ -317,7 +320,22 @@ public class SamlService { return forms.createLogin(); } + private Response buildRedirectToIdentityProvider(String providerId, String accessCode) { + logger.debug("Automatically redirect to identity provider: " + providerId); + return Response.temporaryRedirect( + Urls.identityProviderAuthnRequest(uriInfo.getBaseUri(), providerId, realm.getName(), accessCode)) + .build(); + } + + protected Response newBrowserAuthentication(ClientSessionModel clientSession) { + List identityProviders = realm.getIdentityProviders(); + for (IdentityProviderModel identityProvider : identityProviders) { + if (identityProvider.isAuthenticateByDefault()) { + return buildRedirectToIdentityProvider(identityProvider.getAlias(), new ClientSessionCode(realm, clientSession).getCode() ); + } + } + String flowId = null; for (AuthenticationFlowModel flow : realm.getAuthenticationFlows()) { if (flow.getAlias().equals("browser")) { @@ -336,7 +354,11 @@ public class SamlService { .setUriInfo(uriInfo) .setRequest(request); - return processor.authenticate(); + try { + return processor.authenticate(); + } catch (Exception e) { + return processor.handleBrowserException(e); + } } @@ -394,7 +416,7 @@ public class SamlService { // remove client from logout requests for (ClientSessionModel clientSession : userSession.getClientSessions()) { if (clientSession.getClient().getId().equals(client.getId())) { - clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); + clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name()); } } logger.debug("browser Logout"); @@ -405,7 +427,7 @@ public class SamlService { if (clientSession == null) continue; if (clientSession.getClient().getClientId().equals(client.getClientId())) { // remove requesting client from logout - clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); + clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name()); } UserSessionModel userSession = clientSession.getUserSession(); try { diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java index c192a04513..7d3760ef58 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java @@ -4,6 +4,7 @@ import org.jboss.logging.Logger; import org.jboss.resteasy.spi.HttpRequest; import org.keycloak.ClientConnection; import org.keycloak.events.Details; +import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.models.AuthenticationExecutionModel; @@ -15,8 +16,10 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.services.ErrorPage; import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.services.managers.BruteForceProtector; +import org.keycloak.services.messages.Messages; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @@ -39,6 +42,7 @@ public class AuthenticationProcessor { protected EventBuilder event; protected HttpRequest request; protected String flowId; + protected String action; protected boolean userSessionCreated; @@ -134,6 +138,11 @@ public class AuthenticationProcessor { return this; } + public AuthenticationProcessor setAction(String action) { + this.action = action; + return this; + } + private class Result implements AuthenticatorContext { AuthenticatorModel model; AuthenticationExecutionModel execution; @@ -168,6 +177,11 @@ public class AuthenticationProcessor { this.model = model; } + @Override + public String getAction() { + return AuthenticationProcessor.this.action; + } + @Override public Authenticator getAuthenticator() { return authenticator; @@ -332,6 +346,34 @@ public class AuthenticationProcessor { return status == UserSessionModel.AuthenticatorStatus.SUCCESS; } + public Response handleBrowserException(Exception failure) { + if (failure instanceof AuthException) { + AuthException e = (AuthException)failure; + logger.error("failed authentication: " + e.getError().toString(), e); + if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) { + event.error(Errors.USER_NOT_FOUND); + return ErrorPage.error(session, Messages.INVALID_USER); + } else if (e.getError() == AuthenticationProcessor.Error.USER_DISABLED) { + event.error(Errors.USER_DISABLED); + return ErrorPage.error(session, Messages.ACCOUNT_DISABLED); + } else if (e.getError() == AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED) { + event.error(Errors.USER_TEMPORARILY_DISABLED); + return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED); + + } else { + event.error(Errors.INVALID_USER_CREDENTIALS); + return ErrorPage.error(session, Messages.INVALID_USER); + } + + } else { + logger.error("failed authentication", failure); + event.error(Errors.INVALID_USER_CREDENTIALS); + return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_REQUEST); + } + + } + + public Response authenticate() throws AuthException { logger.debug("AUTHENTICATE"); event.event(EventType.LOGIN); diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java index 91e9514861..1569c031ef 100755 --- a/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java +++ b/services/src/main/java/org/keycloak/authentication/AuthenticatorContext.java @@ -31,6 +31,8 @@ public interface AuthenticatorContext { void setAuthenticatorModel(AuthenticatorModel model); + String getAction(); + Authenticator getAuthenticator(); void setAuthenticator(Authenticator authenticator); diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java new file mode 100755 index 0000000000..62d4a9314a --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContext.java @@ -0,0 +1,32 @@ +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; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserSessionModel; +import org.keycloak.services.managers.BruteForceProtector; + +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface RequiredActionContext { + EventBuilder getEvent(); + UserModel getUser(); + RealmModel getRealm(); + ClientSessionModel getClientSession(); + UserSessionModel getUserSession(); + ClientConnection getConnection(); + UriInfo getUriInfo(); + KeycloakSession getSession(); + HttpRequest getHttpRequest(); +} diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java new file mode 100755 index 0000000000..b5cc21aedc --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/RequiredActionProvider.java @@ -0,0 +1,14 @@ +package org.keycloak.authentication; + +import org.keycloak.provider.Provider; + +import javax.ws.rs.core.Response; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface RequiredActionProvider extends Provider { + Response invokeRequiredAction(RequiredActionContext context); + Object jaxrsService(); +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java index 20099f4970..22bba0e0b0 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/AbstractFormAuthenticator.java @@ -19,26 +19,27 @@ import java.net.URI; * @version $Revision: 1 $ */ public class AbstractFormAuthenticator { - protected boolean isActionUrl(AuthenticatorContext context) { - URI expected = LoginActionsService.authenticationFormProcessor(context.getUriInfo()).build(context.getRealm().getName()); - String current = context.getUriInfo().getAbsolutePath().getPath(); - String expectedPath = expected.getPath(); - return expectedPath.equals(current); + public static final String LOGIN_FORM_ACTION = "login_form"; + public static final String ACTION = "action"; + + protected boolean isAction(AuthenticatorContext context, String action) { + return action.equals(context.getAction()); } protected LoginFormsProvider loginForm(AuthenticatorContext context) { ClientSessionCode code = new ClientSessionCode(context.getRealm(), context.getClientSession()); - code.setAction(ClientSessionModel.Action.AUTHENTICATE); - URI action = getActionUrl(context, code); + code.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); + URI action = getActionUrl(context, code, LOGIN_FORM_ACTION); return context.getSession().getProvider(LoginFormsProvider.class) .setActionUri(action) .setClientSessionCode(code.getCode()); } - public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code) { + public static URI getActionUrl(AuthenticatorContext context, ClientSessionCode code, String action) { return LoginActionsService.authenticationFormProcessor(context.getUriInfo()) - .queryParam(OAuth2Constants.CODE, code.getCode()) + .queryParam(OAuth2Constants.CODE, code.getCode()) + .queryParam(ACTION, action) .build(context.getRealm().getName()); } 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 a75015876c..bac37f7822 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormOTPAuthenticator.java @@ -27,7 +27,7 @@ public class LoginFormOTPAuthenticator extends LoginFormUsernameAuthenticator { @Override public void authenticate(AuthenticatorContext context) { - if (!isActionUrl(context)) { + if (!isAction(context, LOGIN_FORM_ACTION)) { context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR); return; } 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 3995b3a60d..680be06aa0 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormPasswordAuthenticator.java @@ -9,7 +9,6 @@ import org.keycloak.models.RealmModel; 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; @@ -28,7 +27,7 @@ public class LoginFormPasswordAuthenticator extends LoginFormUsernameAuthenticat @Override public void authenticate(AuthenticatorContext context) { - if (!isActionUrl(context)) { + if (!isAction(context, LOGIN_FORM_ACTION)) { context.failure(AuthenticationProcessor.Error.INTERNAL_ERROR); return; } 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 6550d138cf..7904470a48 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/LoginFormUsernameAuthenticator.java @@ -33,7 +33,7 @@ public class LoginFormUsernameAuthenticator extends AbstractFormAuthenticator im @Override public void authenticate(AuthenticatorContext context) { - if (!isActionUrl(context)) { + if (!isAction(context, LOGIN_FORM_ACTION)) { MultivaluedMap formData = new MultivaluedMapImpl<>(); String loginHint = context.getClientSession().getNote(OIDCLoginProtocol.LOGIN_HINT_PARAM); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java index cf0d4fa79d..325f3cd77d 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/OTPFormAuthenticator.java @@ -13,7 +13,6 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.messages.Messages; -import org.keycloak.services.resources.LoginActionsService; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -26,6 +25,7 @@ import java.util.List; * @version $Revision: 1 $ */ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements Authenticator { + public static final String TOTP_FORM_ACTION = "totp"; protected AuthenticatorModel model; public OTPFormAuthenticator(AuthenticatorModel model) { @@ -34,7 +34,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A @Override public void authenticate(AuthenticatorContext context) { - if (!isActionUrl(context)) { + if (!isAction(context, TOTP_FORM_ACTION)) { Response challengeResponse = challenge(context, null); context.challenge(challengeResponse); return; @@ -43,7 +43,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A } public void validateOTP(AuthenticatorContext context) { - MultivaluedMap inputData = context.getHttpRequest().getFormParameters(); + MultivaluedMap inputData = context.getHttpRequest().getDecodedFormParameters(); List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.TOTP); if (password == null) { @@ -70,7 +70,7 @@ public class OTPFormAuthenticator extends AbstractFormAuthenticator implements A protected Response challenge(AuthenticatorContext context, String error) { ClientSessionCode clientSessionCode = new ClientSessionCode(context.getRealm(), context.getClientSession()); - URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode); + URI action = AbstractFormAuthenticator.getActionUrl(context, clientSessionCode, TOTP_FORM_ACTION); LoginFormsProvider forms = context.getSession().getProvider(LoginFormsProvider.class) .setActionUri(action) .setClientSessionCode(clientSessionCode.getCode()); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java new file mode 100755 index 0000000000..563d8f27c9 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticator.java @@ -0,0 +1,48 @@ +package org.keycloak.authentication.authenticators; + +import org.keycloak.authentication.AuthenticationProcessor; +import org.keycloak.authentication.Authenticator; +import org.keycloak.authentication.AuthenticatorContext; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.services.managers.AuthenticationManager; + +/** + * No auth, but it sets a required action. + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class SetRequiredActionAuthenticator implements Authenticator { + + @Override + public boolean requiresUser() { + return false; + } + + @Override + public void authenticate(AuthenticatorContext context) { + UserModel user = context.getUser(); + if (user == null) { + throw new AuthenticationProcessor.AuthException(AuthenticationProcessor.Error.UNKNOWN_USER); + } + user.addRequiredAction(context.getAuthenticatorModel().getConfig().get("required.action")); + context.success(); + } + + @Override + public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) { + return true; + } + + @Override + public String getRequiredAction() { + return null; + } + + @Override + public void close() { + + } +} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticatorFactory.java new file mode 100755 index 0000000000..79f9f551d6 --- /dev/null +++ b/services/src/main/java/org/keycloak/authentication/authenticators/SetRequiredActionAuthenticatorFactory.java @@ -0,0 +1,69 @@ +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 SetRequiredActionAuthenticatorFactory implements AuthenticatorFactory { + public static final String PROVIDER_ID = "auth-set-required-action"; + static SetRequiredActionAuthenticator SINGLETON = new SetRequiredActionAuthenticator(); + @Override + public Authenticator create(AuthenticatorModel model) { + return SINGLETON; + } + + @Override + public Authenticator create(KeycloakSession session) { + throw new IllegalStateException("illegal call"); + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String getDisplayCategory() { + return "Action"; + } + + @Override + public String getDisplayType() { + return "Set Required Action"; + } + + @Override + public String getHelpText() { + return "Doesn't do any authentication. Instead it just sets a configured required action for the user."; + } + + @Override + public List getConfigProperties() { + return null; + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index b7c7381aa7..4ef8544aab 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -130,7 +130,7 @@ public class OIDCLoginProtocol implements LoginProtocol { ClientSessionModel clientSession = accessCode.getClientSession(); String redirect = clientSession.getRedirectUri(); String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM); - accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN); + accessCode.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name()); UriBuilder redirectUri = UriBuilder.fromUri(redirect).queryParam(OAuth2Constants.CODE, accessCode.getCode()); log.debugv("redirectAccessCode: state: {0}", state); if (state != null) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java index a5baa07fb0..3c66dbeb38 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java @@ -220,7 +220,7 @@ public class AuthorizationEndpoint { clientSession = session.sessions().createClientSession(realm, client); clientSession.setAuthMethod(OIDCLoginProtocol.LOGIN_PROTOCOL); clientSession.setRedirectUri(redirectUri); - clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); + clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setNote(ClientSessionCode.ACTION_KEY, KeycloakModelUtils.generateCodeSecret()); clientSession.setNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, responseType); clientSession.setNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUriParam); @@ -277,7 +277,12 @@ public class AuthorizationEndpoint { .setUriInfo(uriInfo) .setRequest(request); - Response challenge = processor.authenticateOnly(); + Response challenge = null; + try { + challenge = processor.authenticateOnly(); + } catch (Exception e) { + return processor.handleBrowserException(e); + } if (challenge != null && prompt != null && prompt.equals("none")) { if (processor.isUserSessionCreated()) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 53c74c96b1..7582dcd8a7 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -191,7 +191,7 @@ public class TokenEndpoint { ClientSessionModel clientSession = accessCode.getClientSession(); event.detail(Details.CODE_ID, clientSession.getId()); - if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN)) { + if (!accessCode.isValid(ClientSessionModel.Action.CODE_TO_TOKEN.name())) { event.error(Errors.INVALID_CODE); throw new ErrorResponseException("invalid_grant", "Code is expired", Response.Status.BAD_REQUEST); } diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 6b12a43132..f445bfe7b3 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -148,7 +148,7 @@ public class AuthenticationManager { public static void backchannelLogoutClientSession(KeycloakSession session, RealmModel realm, ClientSessionModel clientSession, UserSessionModel userSession, UriInfo uriInfo, HttpHeaders headers) { ClientModel client = clientSession.getClient(); - if (client instanceof ClientModel && !client.isFrontchannelLogout() && clientSession.getAction() != ClientSessionModel.Action.LOGGED_OUT) { + if (client instanceof ClientModel && !client.isFrontchannelLogout() && !ClientSessionModel.Action.LOGGED_OUT.name().equals(clientSession.getAction())) { String authMethod = clientSession.getAuthMethod(); if (authMethod == null) return; // must be a keycloak service like account LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod); @@ -156,7 +156,7 @@ public class AuthenticationManager { .setHttpHeaders(headers) .setUriInfo(uriInfo); protocol.backchannelLogout(userSession, clientSession); - clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); + clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name()); } } @@ -188,7 +188,7 @@ public class AuthenticationManager { List redirectClients = new LinkedList(); for (ClientSessionModel clientSession : userSession.getClientSessions()) { ClientModel client = clientSession.getClient(); - if (clientSession.getAction() == ClientSessionModel.Action.LOGGED_OUT) continue; + if (ClientSessionModel.Action.LOGGED_OUT.name().equals(clientSession.getAction())) continue; if (client.isFrontchannelLogout()) { String authMethod = clientSession.getAuthMethod(); if (authMethod == null) continue; // must be a keycloak service like account @@ -205,7 +205,7 @@ public class AuthenticationManager { try { logger.debugv("backchannel logout to: {0}", client.getClientId()); protocol.backchannelLogout(userSession, clientSession); - clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT); + clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name()); } catch (Exception e) { logger.warn("Failed to logout client, continuing", e); } @@ -219,7 +219,7 @@ public class AuthenticationManager { .setHttpHeaders(headers) .setUriInfo(uriInfo); // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not - nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT); + nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT.name()); try { logger.debugv("frontchannel logout to: {0}", nextRedirectClient.getClient().getClientId()); Response response = protocol.frontchannelLogout(userSession, nextRedirectClient); @@ -476,7 +476,7 @@ public class AuthenticationManager { } if (client.isConsentRequired()) { - accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT); + accessCode.setAction(ClientSessionModel.Action.OAUTH_GRANT.name()); UserConsentModel grantedConsent = user.getConsentByClient(client.getId()); diff --git a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java index 99fc5f2678..8ddc02f74b 100755 --- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java +++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java @@ -80,8 +80,8 @@ public class ClientSessionCode { return clientSession; } - public boolean isValid(ClientSessionModel.Action requestedAction) { - ClientSessionModel.Action action = clientSession.getAction(); + public boolean isValid(String requestedAction) { + String action = clientSession.getAction(); if (action == null) { return false; } @@ -93,18 +93,14 @@ public class ClientSessionCode { } int lifespan; - switch (action) { - case CODE_TO_TOKEN: - lifespan = realm.getAccessCodeLifespan(); - break; - case AUTHENTICATE: - lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction(); - break; - default: - lifespan = realm.getAccessCodeLifespanUserAction(); - break; - } + if (action.equals(ClientSessionModel.Action.CODE_TO_TOKEN.name())) { + lifespan = realm.getAccessCodeLifespan(); + } else if (action.equals(ClientSessionModel.Action.AUTHENTICATE.name())) { + lifespan = realm.getAccessCodeLifespanLogin() > 0 ? realm.getAccessCodeLifespanLogin() : realm.getAccessCodeLifespanUserAction(); + } else { + lifespan = realm.getAccessCodeLifespanUserAction(); + } return timestamp + lifespan > Time.currentTime(); } @@ -132,7 +128,7 @@ public class ClientSessionCode { return requestedProtocolMappers; } - public void setAction(ClientSessionModel.Action action) { + public void setAction(String action) { clientSession.setAction(action); clientSession.setNote(ACTION_KEY, UUID.randomUUID().toString()); clientSession.setTimestamp(Time.currentTime()); @@ -142,16 +138,16 @@ public class ClientSessionCode { setAction(convertToAction(requiredAction)); } - private ClientSessionModel.Action convertToAction(RequiredAction requiredAction) { + private String convertToAction(RequiredAction requiredAction) { switch (requiredAction) { case CONFIGURE_TOTP: - return ClientSessionModel.Action.CONFIGURE_TOTP; + return ClientSessionModel.Action.CONFIGURE_TOTP.name(); case UPDATE_PASSWORD: - return ClientSessionModel.Action.UPDATE_PASSWORD; + return ClientSessionModel.Action.UPDATE_PASSWORD.name(); case UPDATE_PROFILE: - return ClientSessionModel.Action.UPDATE_PROFILE; + return ClientSessionModel.Action.UPDATE_PROFILE.name(); case VERIFY_EMAIL: - return ClientSessionModel.Action.VERIFY_EMAIL; + return ClientSessionModel.Action.VERIFY_EMAIL.name(); default: throw new IllegalArgumentException("Unknown required action " + requiredAction); } diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 697a28fe59..4ceebb399a 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -738,7 +738,7 @@ public class AccountService { try { ClientSessionModel clientSession = auth.getClientSession(); ClientSessionCode clientSessionCode = new ClientSessionCode(realm, clientSession); - clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE); + clientSessionCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); clientSession.setRedirectUri(redirectUri); clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString()); diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index e1d6bfa5b8..5b27759a86 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -373,7 +373,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal private ClientSessionCode parseClientSessionCode(String code) { ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel); - if (clientCode != null && clientCode.isValid(AUTHENTICATE)) { + if (clientCode != null && clientCode.isValid(AUTHENTICATE.name())) { ClientSessionModel clientSession = clientCode.getClientSession(); if (clientSession != null) { diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 18c1d10cd9..a81327ee19 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -166,7 +166,7 @@ public class LoginActionsService { ClientSessionCode clientCode; Response response; - boolean check(String code, ClientSessionModel.Action requiredAction) { + boolean check(String code, String requiredAction) { if (!check(code)) { return false; } else if (!clientCode.isValid(requiredAction)) { @@ -178,7 +178,7 @@ public class LoginActionsService { } } - boolean check(String code, ClientSessionModel.Action requiredAction, ClientSessionModel.Action alternativeRequiredAction) { + boolean check(String code, String requiredAction, String alternativeRequiredAction) { if (!check(code)) { return false; } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) { @@ -231,9 +231,9 @@ public class LoginActionsService { ClientSessionCode clientSessionCode = checks.clientCode; ClientSessionModel clientSession = clientSessionCode.getClientSession(); - if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) { + if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD.name())) { TokenManager.dettachClientSession(session.sessions(), realm, clientSession); - clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE); + clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); } return session.getProvider(LoginFormsProvider.class) @@ -281,7 +281,8 @@ public class LoginActionsService { @Path("auth-form") @POST @Consumes(MediaType.APPLICATION_FORM_URLENCODED) - public Response authForm(@QueryParam("code") String code) { + public Response authForm(@QueryParam("code") String code, + @QueryParam("action") String action) { event.event(EventType.LOGIN); if (!checkSsl()) { event.error(Errors.SSL_REQUIRED); @@ -301,8 +302,8 @@ public class LoginActionsService { ClientSessionModel clientSession = clientCode.getClientSession(); event.detail(Details.CODE_ID, clientSession.getId()); - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) { - clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE); + if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) { + clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); return session.getProvider(LoginFormsProvider.class) .setError(Messages.EXPIRED_CODE) @@ -338,39 +339,17 @@ public class LoginActionsService { .setRealm(realm) .setSession(session) .setUriInfo(uriInfo) + .setAction(action) .setRequest(request); try { return processor.authenticate(); - } catch (AuthenticationProcessor.AuthException e) { - return handleError(e, code); } catch (Exception e) { - event.error(Errors.INVALID_USER_CREDENTIALS); - logger.error("failed authentication", e); - return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE); - + return processor.handleBrowserException(e); } } - protected Response handleError(AuthenticationProcessor.AuthException e, String code) { - logger.error("failed authentication: " + e.getError().toString(), e); - if (e.getError() == AuthenticationProcessor.Error.INVALID_USER) { - event.error(Errors.USER_NOT_FOUND); - return ErrorPage.error(session, Messages.INVALID_USER); - } else if (e.getError() == AuthenticationProcessor.Error.USER_DISABLED) { - event.error(Errors.USER_DISABLED); - return ErrorPage.error(session, Messages.ACCOUNT_DISABLED); - } else if (e.getError() == AuthenticationProcessor.Error.USER_TEMPORARILY_DISABLED) { - event.error(Errors.USER_TEMPORARILY_DISABLED); - return ErrorPage.error(session, Messages.ACCOUNT_TEMPORARILY_DISABLED); - - } else { - event.error(Errors.INVALID_USER_CREDENTIALS); - return ErrorPage.error(session, Messages.INVALID_USER); - } - } - /** * URL called after login page. YOU SHOULD NEVER INVOKE THIS DIRECTLY! * @@ -402,8 +381,8 @@ public class LoginActionsService { ClientSessionModel clientSession = clientCode.getClientSession(); event.detail(Details.CODE_ID, clientSession.getId()); - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) { - clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE); + if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name()) || clientSession.getUserSession() != null) { + clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE.name()); event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE); return session.getProvider(LoginFormsProvider.class) .setError(Messages.EXPIRED_CODE) @@ -538,7 +517,7 @@ public class LoginActionsService { event.error(Errors.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_CODE); } - if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) { + if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE.name())) { event.error(Errors.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_CODE); } @@ -676,7 +655,7 @@ public class LoginActionsService { String code = formData.getFirst("code"); ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm); - if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) { + if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT.name())) { event.error(Errors.INVALID_CODE); return ErrorPage.error(session, Messages.INVALID_ACCESS_CODE); } @@ -742,7 +721,7 @@ public class LoginActionsService { final MultivaluedMap formData) { event.event(EventType.UPDATE_PROFILE); Checks checks = new Checks(); - if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE)) { + if (!checks.check(code, ClientSessionModel.Action.UPDATE_PROFILE.name())) { return checks.response; } ClientSessionCode accessCode = checks.clientCode; @@ -804,7 +783,7 @@ public class LoginActionsService { final MultivaluedMap formData) { event.event(EventType.UPDATE_TOTP); Checks checks = new Checks(); - if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP)) { + if (!checks.check(code, ClientSessionModel.Action.CONFIGURE_TOTP.name())) { return checks.response; } ClientSessionCode accessCode = checks.clientCode; @@ -849,7 +828,7 @@ public class LoginActionsService { final MultivaluedMap formData) { event.event(EventType.UPDATE_PASSWORD); Checks checks = new Checks(); - if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD, ClientSessionModel.Action.RECOVER_PASSWORD)) { + if (!checks.check(code, ClientSessionModel.Action.UPDATE_PASSWORD.name(), ClientSessionModel.Action.RECOVER_PASSWORD.name())) { return checks.response; } ClientSessionCode accessCode = checks.clientCode; @@ -890,7 +869,7 @@ public class LoginActionsService { event.event(EventType.UPDATE_PASSWORD).success(); - if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) { + if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD.name())) { String actionCookieValue = getActionCookie(); if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) { return session.getProvider(LoginFormsProvider.class) @@ -911,7 +890,7 @@ public class LoginActionsService { event.event(EventType.VERIFY_EMAIL); if (key != null) { Checks checks = new Checks(); - if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL)) { + if (!checks.check(key, ClientSessionModel.Action.VERIFY_EMAIL.name())) { return checks.response; } ClientSessionCode accessCode = checks.clientCode; @@ -937,7 +916,7 @@ public class LoginActionsService { return redirectOauth(user, accessCode, clientSession, userSession); } else { Checks checks = new Checks(); - if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL)) { + if (!checks.check(code, ClientSessionModel.Action.VERIFY_EMAIL.name())) { return checks.response; } ClientSessionCode accessCode = checks.clientCode; @@ -960,7 +939,7 @@ public class LoginActionsService { event.event(EventType.RESET_PASSWORD); if (key != null) { Checks checks = new Checks(); - if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD)) { + if (!checks.check(key, ClientSessionModel.Action.RECOVER_PASSWORD.name())) { return checks.response; } ClientSessionCode accessCode = checks.clientCode; @@ -1038,7 +1017,7 @@ public class LoginActionsService { event.session(userSession); TokenManager.attachClientSession(userSession, clientSession); - accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD); + accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name()); try { UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri()); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 55d97b0418..57140477d3 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -788,7 +788,7 @@ public class UsersResource { ClientSessionModel clientSession = createClientSession(user, redirectUri, clientId); ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); - accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD); + accessCode.setAction(ClientSessionModel.Action.RECOVER_PASSWORD.name()); try { UriBuilder builder = Urls.loginPasswordResetBuilder(uriInfo.getBaseUri()); @@ -838,7 +838,7 @@ public class UsersResource { ClientSessionModel clientSession = createClientSession(user, redirectUri, clientId); ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); - accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL); + accessCode.setAction(ClientSessionModel.Action.VERIFY_EMAIL.name()); try { UriBuilder builder = Urls.loginActionEmailVerificationBuilder(uriInfo.getBaseUri()); 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 index 8fbf02e5c7..cb6bdc77e2 100755 --- a/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory @@ -3,4 +3,5 @@ org.keycloak.authentication.authenticators.LoginFormOTPAuthenticatorFactory org.keycloak.authentication.authenticators.LoginFormPasswordAuthenticatorFactory org.keycloak.authentication.authenticators.LoginFormUsernameAuthenticatorFactory org.keycloak.authentication.authenticators.OTPFormAuthenticatorFactory -org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory \ No newline at end of file +org.keycloak.authentication.authenticators.SpnegoAuthenticatorFactory +org.keycloak.authentication.authenticators.SetRequiredActionAuthenticatorFactory \ No newline at end of file diff --git a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java index 64eeb62c3b..d662141681 100755 --- a/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java +++ b/social/twitter/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java @@ -166,7 +166,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProviderStian Thorgersen - */ -public class LoginTest { - - @ClassRule - public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - UserModel user = manager.getSession().users().addUser(appRealm, "login-test"); - user.setEmail("login@test.com"); - user.setEnabled(true); - - userId = user.getId(); - - UserCredentialModel creds = new UserCredentialModel(); - creds.setType(CredentialRepresentation.PASSWORD); - creds.setValue("password"); - - user.updateCredential(creds); - } - }); - - @Rule - public AssertEvents events = new AssertEvents(keycloakRule); - - @Rule - public WebRule webRule = new WebRule(this); - - @WebResource - protected OAuthClient oauth; - - @WebResource - protected WebDriver driver; - - @WebResource - protected AppPage appPage; - - @WebResource - protected LoginPage loginPage; - - @WebResource - protected LoginPasswordUpdatePage updatePasswordPage; - - private static String userId; - - @Test - public void testBrowserSecurityHeaders() { - Client client = ClientBuilder.newClient(); - Response response = client.target(oauth.getLoginFormUrl()).request().get(); - Assert.assertEquals(200, response.getStatus()); - for (Map.Entry entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) { - String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey()); - String headerValue = response.getHeaderString(headerName); - Assert.assertNotNull(headerValue); - Assert.assertEquals(headerValue, entry.getValue()); - } - response.close(); - } - - @Test - public void loginInvalidPassword() { - loginPage.open(); - loginPage.login("login-test", "invalid"); - - loginPage.assertCurrent(); - - Assert.assertEquals("Invalid username or password.", loginPage.getError()); - - events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent(); - } - - @Test - public void loginInvalidPasswordDisabledUser() { - keycloakRule.configure(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - session.users().getUserByUsername("login-test", appRealm).setEnabled(false); - } - }); - - try { - loginPage.open(); - loginPage.login("login-test", "invalid"); - - loginPage.assertCurrent(); - - Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); - - events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); - } finally { - keycloakRule.configure(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - session.users().getUserByUsername("login-test", appRealm).setEnabled(true); - } - }); - } - } - - @Test - public void loginDisabledUser() { - keycloakRule.configure(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - session.users().getUserByUsername("login-test", appRealm).setEnabled(false); - } - }); - - try { - loginPage.open(); - loginPage.login("login-test", "password"); - - loginPage.assertCurrent(); - - Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); - - events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); - } finally { - keycloakRule.configure(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - session.users().getUserByUsername("login-test", appRealm).setEnabled(true); - } - }); - } - } - - @Test - public void loginInvalidUsername() { - loginPage.open(); - loginPage.login("invalid", "password"); - - loginPage.assertCurrent(); - - Assert.assertEquals("Invalid username or password.", loginPage.getError()); - - events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").assertEvent(); - } - - @Test - public void loginSuccess() { - loginPage.open(); - loginPage.login("login-test", "password"); - - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); - - events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); - } - - @Test - public void loginPromptNone() { - driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none"); - - assertFalse(loginPage.isCurrent()); - assertTrue(appPage.isCurrent()); - - loginPage.open(); - loginPage.login("login-test", "password"); - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - - events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); - - driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none"); - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - - events.expectLogin().user(userId).removeDetail(Details.USERNAME).assertEvent(); - } - - @Test - public void loginWithForcePasswordChangePolicy() { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)")); - } - }); - - try { - // Setting offset to more than one day to force password update - // elapsedTime > timeToExpire - Time.setOffset(86405); - - loginPage.open(); - - loginPage.login("login-test", "password"); - - updatePasswordPage.assertCurrent(); - - updatePasswordPage.changePassword("updatedPassword", "updatedPassword"); - - events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent(); - - assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - - events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); - - } finally { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - appRealm.setPasswordPolicy(new PasswordPolicy(null)); - - UserModel user = manager.getSession().users().getUserByUsername("login-test", appRealm); - UserCredentialModel cred = new UserCredentialModel(); - cred.setType(CredentialRepresentation.PASSWORD); - cred.setValue("password"); - user.updateCredential(cred); - } - }); - Time.setOffset(0); - } - } - - @Test - public void loginWithoutForcePasswordChangePolicy() { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)")); - } - }); - - try { - // Setting offset to less than one day to avoid forced password update - // elapsedTime < timeToExpire - Time.setOffset(86205); - - loginPage.open(); - - loginPage.login("login-test", "password"); - - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); - - events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); - - } finally { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - appRealm.setPasswordPolicy(new PasswordPolicy(null)); - } - }); - Time.setOffset(0); - } - } - - @Test - public void loginNoTimeoutWithLongWait() { - try { - loginPage.open(); - - Time.setOffset(1700); - - loginPage.login("login-test", "password"); - - events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId(); - } finally { - Time.setOffset(0); - } - } - - @Test - public void loginTimeout() { - try { - loginPage.open(); - - Time.setOffset(1850); - - loginPage.login("login-test", "password"); - - events.expectLogin().clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).user((String) null).session((String) null).error("expired_code").assertEvent().getSessionId(); - } finally { - Time.setOffset(0); - } - } - - @Test - public void loginLoginHint() { - String loginFormUrl = oauth.getLoginFormUrl() + "&login_hint=login-test"; - driver.navigate().to(loginFormUrl); - - Assert.assertEquals("login-test", loginPage.getUsername()); - loginPage.login("password"); - - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); - - events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); - } - - @Test - public void loginWithEmailSuccess() { - loginPage.open(); - loginPage.login("login@test.com", "password"); - - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); - - events.expectLogin().user(userId).assertEvent(); - } - - @Test - public void loginWithRememberMe() { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - appRealm.setRememberMe(true); - } - }); - - try { - loginPage.open(); - assertFalse(loginPage.isRememberMeChecked()); - loginPage.setRememberMe(true); - assertTrue(loginPage.isRememberMeChecked()); - loginPage.login("login-test", "password"); - - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); - Event loginEvent = events.expectLogin().user(userId) - .detail(Details.USERNAME, "login-test") - .detail(Details.REMEMBER_ME, "true") - .assertEvent(); - String sessionId = loginEvent.getSessionId(); - - // Expire session - keycloakRule.removeUserSession(sessionId); - - // Assert rememberMe checked and username/email prefilled - loginPage.open(); - assertTrue(loginPage.isRememberMeChecked()); - Assert.assertEquals("login-test", loginPage.getUsername()); - - loginPage.setRememberMe(false); - } finally { - keycloakRule.update(new KeycloakRule.KeycloakSetup() { - @Override - public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - appRealm.setRememberMe(false); - } - }); - } - } - - @Test - public void loginCancel() { - loginPage.open(); - loginPage.cancel(); - - Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); - Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); - - events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).assertEvent(); - } - - // KEYCLOAK-1037 - @Test - public void loginExpiredCode() { - try { - loginPage.open(); - Time.setOffset(5000); - loginPage.login("login@test.com", "password"); - - loginPage.assertCurrent(); - Assert.assertEquals("Login timeout. Please login again.", loginPage.getError()); - - events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent(); - - } finally { - Time.setOffset(0); - } - } - -} +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.testsuite.forms; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.keycloak.OAuth2Constants; +import org.keycloak.events.Details; +import org.keycloak.events.Event; +import org.keycloak.events.EventType; +import org.keycloak.models.BrowserSecurityHeaders; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.AssertEvents; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.AppPage.RequestType; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.Time; +import org.openqa.selenium.WebDriver; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.core.Response; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author Stian Thorgersen + */ +public class LoginTest { + + @ClassRule + public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + UserModel user = manager.getSession().users().addUser(appRealm, "login-test"); + user.setEmail("login@test.com"); + user.setEnabled(true); + + userId = user.getId(); + + UserCredentialModel creds = new UserCredentialModel(); + creds.setType(CredentialRepresentation.PASSWORD); + creds.setValue("password"); + + user.updateCredential(creds); + } + }); + + @Rule + public AssertEvents events = new AssertEvents(keycloakRule); + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + protected OAuthClient oauth; + + @WebResource + protected WebDriver driver; + + @WebResource + protected AppPage appPage; + + @WebResource + protected LoginPage loginPage; + + @WebResource + protected LoginPasswordUpdatePage updatePasswordPage; + + private static String userId; + + @Test + public void testBrowserSecurityHeaders() { + Client client = ClientBuilder.newClient(); + Response response = client.target(oauth.getLoginFormUrl()).request().get(); + Assert.assertEquals(200, response.getStatus()); + for (Map.Entry entry : BrowserSecurityHeaders.defaultHeaders.entrySet()) { + String headerName = BrowserSecurityHeaders.headerAttributeMap.get(entry.getKey()); + String headerValue = response.getHeaderString(headerName); + Assert.assertNotNull(headerValue); + Assert.assertEquals(headerValue, entry.getValue()); + } + response.close(); + } + + @Test + public void loginInvalidPassword() { + loginPage.open(); + loginPage.login("login-test", "invalid"); + + loginPage.assertCurrent(); + + Assert.assertEquals("Invalid username or password.", loginPage.getError()); + + events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials").detail(Details.USERNAME, "login-test").assertEvent(); + } + + @Test + public void loginInvalidPasswordDisabledUser() { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + session.users().getUserByUsername("login-test", appRealm).setEnabled(false); + } + }); + + try { + loginPage.open(); + loginPage.login("login-test", "invalid"); + + loginPage.assertCurrent(); + + Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); + + events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); + } finally { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + session.users().getUserByUsername("login-test", appRealm).setEnabled(true); + } + }); + } + } + + @Test + public void loginDisabledUser() { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + session.users().getUserByUsername("login-test", appRealm).setEnabled(false); + } + }); + + try { + loginPage.open(); + loginPage.login("login-test", "password"); + + loginPage.assertCurrent(); + + Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); + + events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent(); + } finally { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + session.users().getUserByUsername("login-test", appRealm).setEnabled(true); + } + }); + } + } + + @Test + public void loginInvalidUsername() { + loginPage.open(); + loginPage.login("invalid", "password"); + + loginPage.assertCurrent(); + + Assert.assertEquals("Invalid username or password.", loginPage.getError()); + + events.expectLogin().user((String) null).session((String) null).error("user_not_found").detail(Details.USERNAME, "invalid").assertEvent(); + } + + @Test + public void loginSuccess() { + loginPage.open(); + loginPage.login("login-test", "password"); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); + } + + @Test + public void loginPromptNone() { + driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none"); + + assertFalse(loginPage.isCurrent()); + assertTrue(appPage.isCurrent()); + + loginPage.open(); + loginPage.login("login-test", "password"); + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); + + driver.navigate().to(oauth.getLoginFormUrl().toString() + "&prompt=none"); + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + events.expectLogin().user(userId).removeDetail(Details.USERNAME).assertEvent(); + } + + @Test + public void loginWithForcePasswordChangePolicy() { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)")); + } + }); + + try { + // Setting offset to more than one day to force password update + // elapsedTime > timeToExpire + Time.setOffset(86405); + + loginPage.open(); + + loginPage.login("login-test", "password"); + + updatePasswordPage.assertCurrent(); + + updatePasswordPage.changePassword("updatedPassword", "updatedPassword"); + + events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).detail(Details.USERNAME, "login-test").assertEvent(); + + assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); + + } finally { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setPasswordPolicy(new PasswordPolicy(null)); + + UserModel user = manager.getSession().users().getUserByUsername("login-test", appRealm); + UserCredentialModel cred = new UserCredentialModel(); + cred.setType(CredentialRepresentation.PASSWORD); + cred.setValue("password"); + user.updateCredential(cred); + } + }); + Time.setOffset(0); + } + } + + @Test + public void loginWithoutForcePasswordChangePolicy() { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)")); + } + }); + + try { + // Setting offset to less than one day to avoid forced password update + // elapsedTime < timeToExpire + Time.setOffset(86205); + + loginPage.open(); + + loginPage.login("login-test", "password"); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); + + } finally { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setPasswordPolicy(new PasswordPolicy(null)); + } + }); + Time.setOffset(0); + } + } + + @Test + public void loginNoTimeoutWithLongWait() { + try { + loginPage.open(); + + Time.setOffset(1700); + + loginPage.login("login-test", "password"); + + events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent().getSessionId(); + } finally { + Time.setOffset(0); + } + } + + @Test + public void loginTimeout() { + try { + loginPage.open(); + + Time.setOffset(1850); + + loginPage.login("login-test", "password"); + + events.expectLogin().clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).user((String) null).session((String) null).error("expired_code").assertEvent().getSessionId(); + } finally { + Time.setOffset(0); + } + } + + @Test + public void loginLoginHint() { + String loginFormUrl = oauth.getLoginFormUrl() + "&login_hint=login-test"; + driver.navigate().to(loginFormUrl); + + Assert.assertEquals("login-test", loginPage.getUsername()); + loginPage.login("password"); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + events.expectLogin().user(userId).detail(Details.USERNAME, "login-test").assertEvent(); + } + + @Test + public void loginWithEmailSuccess() { + loginPage.open(); + loginPage.login("login@test.com", "password"); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + events.expectLogin().user(userId).assertEvent(); + } + + @Test + public void loginWithRememberMe() { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setRememberMe(true); + } + }); + + try { + loginPage.open(); + assertFalse(loginPage.isRememberMeChecked()); + loginPage.setRememberMe(true); + assertTrue(loginPage.isRememberMeChecked()); + loginPage.login("login-test", "password"); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + Event loginEvent = events.expectLogin().user(userId) + .detail(Details.USERNAME, "login-test") + .detail(Details.REMEMBER_ME, "true") + .assertEvent(); + String sessionId = loginEvent.getSessionId(); + + // Expire session + keycloakRule.removeUserSession(sessionId); + + // Assert rememberMe checked and username/email prefilled + loginPage.open(); + assertTrue(loginPage.isRememberMeChecked()); + Assert.assertEquals("login-test", loginPage.getUsername()); + + loginPage.setRememberMe(false); + } finally { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setRememberMe(false); + } + }); + } + } + + @Test + public void loginCancel() { + loginPage.open(); + loginPage.cancel(); + + Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertEquals("access_denied", oauth.getCurrentQuery().get(OAuth2Constants.ERROR)); + + events.expectLogin().error("rejected_by_user").user((String) null).session((String) null).removeDetail(Details.USERNAME).assertEvent(); + } + + // KEYCLOAK-1037 + @Test + public void loginExpiredCode() { + try { + loginPage.open(); + Time.setOffset(5000); + loginPage.login("login@test.com", "password"); + + loginPage.assertCurrent(); + Assert.assertEquals("Login timeout. Please login again.", loginPage.getError()); + + events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent(); + + } finally { + Time.setOffset(0); + } + } + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java index c5b236ac59..7ca33e2148 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java @@ -117,14 +117,14 @@ public class UserSessionProviderTest { int time = clientSession.getTimestamp(); assertEquals(null, clientSession.getAction()); - clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN); + clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name()); clientSession.setTimestamp(time + 10); kc.stopSession(session, true); session = kc.startSession(); ClientSessionModel updated = session.sessions().getClientSession(realm, id); - assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN, updated.getAction()); + assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction()); assertEquals(time + 10, updated.getTimestamp()); }