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());
}