keycloak-aplcache
Changes
adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java 2(+1 -1)
examples/kerberos/README.md 2(+1 -1)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java 24(+13 -11)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java 292(+0 -292)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticatedClientSessionEntity.java 11(+1 -10)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java 152(+0 -152)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java 16(+5 -11)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java 2(+2 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java 335(+92 -243)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java 8(+1 -7)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java 129(+0 -129)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionsOfUserSessionMapper.java 77(+0 -77)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/ClientSessionPredicate.java 114(+0 -114)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java 15(+15 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java 38(+12 -26)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java 11(+11 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java 51(+33 -18)
model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java 24(+10 -14)
model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java 1(+0 -1)
model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java 35(+17 -18)
server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java 1(+0 -1)
server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java 4(+2 -2)
server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java 15(+5 -10)
server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java 8(+0 -8)
server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java 23(+10 -13)
server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java 4(+2 -2)
services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java 29(+14 -15)
services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java 2(+1 -1)
services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java 27(+9 -18)
services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java 4(+2 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java 189(+189 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 2(+0 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java 19(+6 -13)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java 130(+124 -6)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java 251(+126 -125)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java 791(+396 -395)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java 786(+378 -408)
testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderTest.java 275(+93 -182)
testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java 4(+2 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java 32(+13 -19)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java 38(+21 -17)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java 21(+13 -8)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java 17(+8 -9)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java 6(+4 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java 6(+5 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java 24(+19 -5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java 44(+43 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java 9(+2 -7)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java 4(+3 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java 25(+25 -0)
Details
diff --git a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
index e6d6588..38fa9d3 100755
--- a/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
+++ b/adapters/oidc/adapter-core/src/main/java/org/keycloak/adapters/HttpClientBuilder.java
@@ -170,8 +170,8 @@ public class HttpClientBuilder {
return this;
}
- public HttpClientBuilder disableCookieCache() {
- this.disableCookieCache = true;
+ public HttpClientBuilder disableCookieCache(boolean disable) {
+ this.disableCookieCache = disable;
return this;
}
@@ -334,7 +334,7 @@ public class HttpClientBuilder {
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
- disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
+ disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {
diff --git a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
index e28500b..e03158f 100755
--- a/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
+++ b/adapters/oidc/servlet-filter/src/main/java/org/keycloak/adapters/servlet/OIDCFilterSessionStore.java
@@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
- if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
+ if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
}
diff --git a/core/src/main/java/org/keycloak/representations/AccessToken.java b/core/src/main/java/org/keycloak/representations/AccessToken.java
index 4ef6831..36778e1 100755
--- a/core/src/main/java/org/keycloak/representations/AccessToken.java
+++ b/core/src/main/java/org/keycloak/representations/AccessToken.java
@@ -97,9 +97,6 @@ public class AccessToken extends IDToken {
}
}
- @JsonProperty("client_session")
- protected String clientSession;
-
@JsonProperty("trusted-certs")
protected Set<String> trustedCertificates;
@@ -156,10 +153,6 @@ public class AccessToken extends IDToken {
return resourceAccess.get(resource);
}
- public String getClientSession() {
- return clientSession;
- }
-
public Access addAccess(String service) {
Access access = resourceAccess.get(service);
if (access != null) return access;
@@ -168,11 +161,6 @@ public class AccessToken extends IDToken {
return access;
}
- public AccessToken clientSession(String session) {
- this.clientSession = session;
- return this;
- }
-
@Override
public AccessToken id(String id) {
return (AccessToken) super.id(id);
diff --git a/core/src/main/java/org/keycloak/representations/RefreshToken.java b/core/src/main/java/org/keycloak/representations/RefreshToken.java
index 4b89cf6..3a7a951 100755
--- a/core/src/main/java/org/keycloak/representations/RefreshToken.java
+++ b/core/src/main/java/org/keycloak/representations/RefreshToken.java
@@ -40,7 +40,6 @@ public class RefreshToken extends AccessToken {
*/
public RefreshToken(AccessToken token) {
this();
- this.clientSession = token.getClientSession();
this.issuer = token.issuer;
this.subject = token.subject;
this.issuedFor = token.issuedFor;
examples/kerberos/README.md 2(+1 -1)
diff --git a/examples/kerberos/README.md b/examples/kerberos/README.md
index 02bffdd..2c1d335 100644
--- a/examples/kerberos/README.md
+++ b/examples/kerberos/README.md
@@ -47,7 +47,7 @@ is in your `/etc/hosts` before other records for the 127.0.0.1 host to avoid iss
**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm for host `localhost` and enable `forwardable` flag, which is needed
for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server.
-See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/test/resources/kerberos/test-krb5.conf) for inspiration.
+See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/test-krb5.conf) for inspiration.
On OS X the file to edit (or create) is `/Library/Preferences/edu.mit.Kerberos` with the same syntax as `krb5.conf`.
On Windows the file to edit (or create) is `c:\Windows\krb5.ini` with the same syntax as `krb5.conf`.
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
index 6eaba69..546e86f 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticatedClientSessionAdapter.java
@@ -27,7 +27,7 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@@ -36,14 +36,16 @@ import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
*/
public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
- private final ClientLoginSessionEntity entity;
+ private final AuthenticatedClientSessionEntity entity;
+ private final ClientModel client;
private final InfinispanUserSessionProvider provider;
private final Cache<String, SessionEntity> cache;
private UserSessionAdapter userSession;
- public AuthenticatedClientSessionAdapter(ClientLoginSessionEntity entity, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
+ public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
this.provider = provider;
this.entity = entity;
+ this.client = client;
this.cache = cache;
this.userSession = userSession;
}
@@ -55,23 +57,23 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override
public void setUserSession(UserSessionModel userSession) {
- String clientUUID = entity.getClient();
+ String clientUUID = client.getId();
UserSessionEntity sessionEntity = this.userSession.getEntity();
// Dettach userSession
if (userSession == null) {
- if (sessionEntity.getClientLoginSessions() != null) {
- sessionEntity.getClientLoginSessions().remove(clientUUID);
+ if (sessionEntity.getAuthenticatedClientSessions() != null) {
+ sessionEntity.getAuthenticatedClientSessions().remove(clientUUID);
update();
this.userSession = null;
}
} else {
this.userSession = (UserSessionAdapter) userSession;
- if (sessionEntity.getClientLoginSessions() == null) {
- sessionEntity.setClientLoginSessions(new HashMap<>());
+ if (sessionEntity.getAuthenticatedClientSessions() == null) {
+ sessionEntity.setAuthenticatedClientSessions(new HashMap<>());
}
- sessionEntity.getClientLoginSessions().put(clientUUID, entity);
+ sessionEntity.getAuthenticatedClientSessions().put(clientUUID, entity);
update();
}
}
@@ -104,8 +106,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override
public ClientModel getClient() {
- String client = entity.getClient();
- return getRealm().getClientById(client);
+ return client;
}
@Override
@@ -192,4 +193,5 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
copy.putAll(entity.getNotes());
return copy;
}
+
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java
index e55cf31..19cb7c7 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/Consumers.java
@@ -19,11 +19,9 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
-import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -41,21 +39,6 @@ public class Consumers {
return new UserSessionModelsConsumer(provider, realm, offline);
}
- public static class UserSessionIdAndTimestampConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
-
- private Map<String, Integer> sessions = new HashMap<>();
-
- @Override
- public void accept(Map.Entry<String, SessionEntity> entry) {
- SessionEntity e = entry.getValue();
- if (e instanceof ClientSessionEntity) {
- ClientSessionEntity ce = (ClientSessionEntity) e;
- sessions.put(ce.getUserSession(), ce.getTimestamp());
- }
- }
-
- }
-
public static class UserSessionModelsConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private InfinispanUserSessionProvider provider;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
index 3a5a4eb..54d182f 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
@@ -46,13 +46,11 @@ public class UserSessionEntity extends SessionEntity {
private int lastSessionRefresh;
- private Set<String> clientSessions = new CopyOnWriteArraySet<>();
-
private UserSessionModel.State state;
private Map<String, String> notes = new ConcurrentHashMap<>();
- private Map<String, ClientLoginSessionEntity> clientLoginSessions;
+ private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions;
public String getUser() {
return user;
@@ -110,10 +108,6 @@ public class UserSessionEntity extends SessionEntity {
this.lastSessionRefresh = lastSessionRefresh;
}
- public Set<String> getClientSessions() {
- return clientSessions;
- }
-
public Map<String, String> getNotes() {
return notes;
}
@@ -122,12 +116,12 @@ public class UserSessionEntity extends SessionEntity {
this.notes = notes;
}
- public Map<String, ClientLoginSessionEntity> getClientLoginSessions() {
- return clientLoginSessions;
+ public Map<String, AuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
+ return authenticatedClientSessions;
}
- public void setClientLoginSessions(Map<String, ClientLoginSessionEntity> clientLoginSessions) {
- this.clientLoginSessions = clientLoginSessions;
+ public void setAuthenticatedClientSessions(Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions) {
+ this.authenticatedClientSessions = authenticatedClientSessions;
}
public UserSessionModel.State getState() {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java
index 1e23524..a802544 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java
@@ -120,6 +120,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
+ // TODO: Should likely listen to "RealmRemovedEvent" received from cluster and clean just local sessions
@Override
public void onRealmRemoved(RealmModel realm) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId())).iterator();
@@ -128,6 +129,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
}
}
+ // TODO: Should likely listen to "ClientRemovedEvent" received from cluster and clean just local sessions
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index e87347e..0e50a73 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -25,9 +25,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
@@ -35,31 +33,29 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
-import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
-import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.Comparators;
import org.keycloak.models.sessions.infinispan.stream.Mappers;
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
-import org.keycloak.models.utils.RealmInfoUtil;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
+import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@@ -90,31 +86,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return offline ? offlineSessionCache : sessionCache;
}
-
- // TODO:mposolda remove
- @Override
- public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
- String id = KeycloakModelUtils.generateId();
-
- ClientSessionEntity entity = new ClientSessionEntity();
- entity.setId(id);
- entity.setRealm(realm.getId());
- entity.setTimestamp(Time.currentTime());
- entity.setClient(client.getId());
-
-
- tx.put(sessionCache, id, entity);
-
- ClientSessionAdapter wrap = wrap(realm, entity, false);
- return wrap;
- }
-
@Override
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
- ClientLoginSessionEntity entity = new ClientLoginSessionEntity();
- entity.setClient(client.getId());
+ AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
- AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, (UserSessionAdapter) userSession, this, sessionCache);
+ AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, sessionCache);
adapter.setUserSession(userSession);
return adapter;
}
@@ -123,6 +99,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(id);
+
+ updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
+
+ tx.putIfAbsent(sessionCache, id, entity);
+
+ return wrap(realm, entity, false);
+ }
+
+ void updateSessionEntity(UserSessionEntity entity, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
entity.setRealm(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
@@ -137,41 +122,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(currentTime);
entity.setLastSessionRefresh(currentTime);
- tx.putIfAbsent(sessionCache, id, entity);
- return wrap(realm, entity, false);
- }
-
- @Override
- public ClientSessionModel getClientSession(RealmModel realm, String id) {
- return getClientSession(realm, id, false);
- }
-
- protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
- Cache<String, SessionEntity> cache = getCache(offline);
- ClientSessionEntity entity = (ClientSessionEntity) tx.get(cache, id); // Chance created in this transaction
-
- if (entity == null) {
- entity = (ClientSessionEntity) cache.get(id);
- }
-
- return wrap(realm, entity, offline);
- }
-
- @Override
- public ClientSessionModel getClientSession(String id) {
- // Chance created in this transaction
- ClientSessionEntity entity = (ClientSessionEntity) tx.get(sessionCache, id);
-
- if (entity == null) {
- entity = (ClientSessionEntity) sessionCache.get(id);
- }
-
- if (entity != null) {
- RealmModel realm = session.realms().getRealm(entity.getRealm());
- return wrap(realm, entity, false);
- }
- return null;
}
@Override
@@ -230,37 +181,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
final Cache<String, SessionEntity> cache = getCache(offline);
- Iterator<UserSessionTimestamp> itr = cache.entrySet().stream()
- .filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession())
- .map(Mappers.clientSessionToUserSessionTimestamp())
- .iterator();
-
- Map<String, UserSessionTimestamp> m = new HashMap<>();
- while(itr.hasNext()) {
- UserSessionTimestamp next = itr.next();
- if (!m.containsKey(next.getUserSessionId()) || m.get(next.getUserSessionId()).getClientSessionTimestamp() < next.getClientSessionTimestamp()) {
- m.put(next.getUserSessionId(), next);
- }
+ Stream<UserSessionEntity> stream = cache.entrySet().stream()
+ .filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
+ .map(Mappers.userSessionEntity())
+ .sorted(Comparators.userSessionLastSessionRefresh());
+
+ // Doesn't work due to ISPN-6575 . TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0
+// if (firstResult > 0) {
+// stream = stream.skip(firstResult);
+// }
+//
+// if (maxResults > 0) {
+// stream = stream.limit(maxResults);
+// }
+//
+// List<UserSessionEntity> entities = stream.collect(Collectors.toList());
+
+
+ // Workaround for ISPN-6575 TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0 and replace with the more effective code above
+ if (firstResult < 0) {
+ firstResult = 0;
+ }
+ if (maxResults < 0) {
+ maxResults = Integer.MAX_VALUE;
}
- Stream<UserSessionTimestamp> stream = new LinkedList<>(m.values()).stream().sorted(Comparators.userSessionTimestamp());
-
- if (firstResult > 0) {
- stream = stream.skip(firstResult);
+ int count = firstResult + maxResults;
+ if (count > 0) {
+ stream = stream.limit(count);
}
+ List<UserSessionEntity> entities = stream.collect(Collectors.toList());
- if (maxResults > 0) {
- stream = stream.limit(maxResults);
+ if (firstResult > entities.size()) {
+ return Collections.emptyList();
}
+ maxResults = Math.min(maxResults, entities.size() - firstResult);
+ entities = entities.subList(firstResult, firstResult + maxResults);
+
+
final List<UserSessionModel> sessions = new LinkedList<>();
- stream.forEach(new Consumer<UserSessionTimestamp>() {
+ entities.stream().forEach(new Consumer<UserSessionEntity>() {
@Override
- public void accept(UserSessionTimestamp userSessionTimestamp) {
- SessionEntity entity = cache.get(userSessionTimestamp.getUserSessionId());
- if (entity != null) {
- sessions.add(wrap(realm, (UserSessionEntity) entity, offline));
- }
+ public void accept(UserSessionEntity userSessionEntity) {
+ sessions.add(wrap(realm, userSessionEntity, offline));
}
});
@@ -273,7 +237,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
- return getCache(offline).entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession()).map(Mappers.clientSessionToUserSessionId()).distinct().count();
+ return getCache(offline).entrySet().stream()
+ .filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
+ .count();
}
@Override
@@ -303,9 +269,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void removeExpired(RealmModel realm) {
log.debugf("Removing expired sessions");
removeExpiredUserSessions(realm);
- removeExpiredClientSessions(realm);
removeExpiredOfflineUserSessions(realm);
- removeExpiredOfflineClientSessions(realm);
removeExpiredClientInitialAccess(realm);
}
@@ -322,33 +286,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
counter++;
UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
tx.remove(sessionCache, entity.getId());
-
- if (entity.getClientSessions() != null) {
- for (String clientSessionId : entity.getClientSessions()) {
- tx.remove(sessionCache, clientSessionId);
- }
- }
}
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
- private void removeExpiredClientSessions(RealmModel realm) {
- int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
-
- // Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
- Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
- .entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredDettachedClientSession).requireNullUserSession()).iterator();
-
- int counter = 0;
- while (itr.hasNext()) {
- counter++;
- tx.remove(sessionCache, itr.next().getKey());
- }
-
- log.debugf("Removed %d expired client sessions for realm '%s'", counter, realm.getName());
- }
-
private void removeExpiredOfflineUserSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
@@ -366,33 +308,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
persister.removeUserSession(entity.getId(), true);
- for (String clientSessionId : entity.getClientSessions()) {
- tx.remove(offlineSessionCache, clientSessionId);
+ for (String clientUUID : entity.getAuthenticatedClientSessions().keySet()) {
+ persister.removeClientSession(entity.getId(), clientUUID, true);
}
}
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
}
- private void removeExpiredOfflineClientSessions(RealmModel realm) {
- UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
- int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
-
- // Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
- Iterator<String> itr = offlineSessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
- .entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredOffline)).map(Mappers.sessionId()).iterator();
-
- int counter = 0;
- while (itr.hasNext()) {
- counter++;
- String sessionId = itr.next();
- tx.remove(offlineSessionCache, sessionId);
- persister.removeClientSession(sessionId, true);
- }
-
- log.debugf("Removed %d expired offline client sessions for realm '%s'", counter, realm.getName());
- }
-
private void removeExpiredClientInitialAccess(RealmModel realm) {
Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
@@ -454,21 +377,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
- onClientRemoved(realm, client, true);
- onClientRemoved(realm, client, false);
- }
-
- private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
- Cache<String, SessionEntity> cache = getCache(offline);
-
- Iterator<Map.Entry<String, SessionEntity>> itr = cache.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
- while (itr.hasNext()) {
- ClientSessionEntity entity = (ClientSessionEntity) itr.next().getValue();
- ClientSessionAdapter adapter = wrap(realm, entity, offline);
- adapter.setUserSession(null);
-
- tx.remove(cache, entity.getId());
- }
+ // Nothing for now. userSession.getAuthenticatedClientSessions() will check lazily if particular client exists and update userSession on-the-fly.
}
@@ -484,55 +393,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void close() {
}
- void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
- UserSessionEntity entity = userSession.getEntity();
- String clientSessionId = clientSession.getId();
- if (!entity.getClientSessions().contains(clientSessionId)) {
- entity.getClientSessions().add(clientSessionId);
- userSession.update();
- }
- }
-
- @Override
- public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
- removeClientSession(realm, clientSession, false);
- }
-
- protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
- Cache<String, SessionEntity> cache = getCache(offline);
-
- UserSessionModel userSession = clientSession.getUserSession();
- if (userSession != null) {
- UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
- if (entity.getClientSessions() != null) {
- entity.getClientSessions().remove(clientSession.getId());
-
- }
- tx.replace(cache, entity.getId(), entity);
- }
- tx.remove(cache, clientSession.getId());
- }
-
-
- void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
- UserSessionEntity entity = userSession.getEntity();
- String clientSessionId = clientSession.getId();
- if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
- entity.getClientSessions().remove(clientSessionId);
- userSession.update();
- }
- }
-
protected void removeUserSession(RealmModel realm, UserSessionEntity sessionEntity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
tx.remove(cache, sessionEntity.getId());
-
- if (sessionEntity.getClientSessions() != null) {
- for (String clientSessionId : sessionEntity.getClientSessions()) {
- tx.remove(cache, clientSessionId);
- }
- }
}
InfinispanKeycloakTransaction getTx() {
@@ -560,11 +424,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return models;
}
- ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
- Cache<String, SessionEntity> cache = getCache(offline);
- return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
- }
-
ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
Cache<String, SessionEntity> cache = getCache(false);
return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
@@ -574,14 +433,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
}
- List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
- List<ClientSessionModel> models = new LinkedList<>();
- for (ClientSessionEntity e : entities) {
- models.add(wrap(realm, e, offline));
- }
- return models;
- }
-
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
if (userSession instanceof UserSessionAdapter) {
return ((UserSessionAdapter) userSession).getEntity();
@@ -594,7 +445,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
- UserSessionAdapter offlineUserSession = importUserSession(userSession, true);
+ UserSessionAdapter offlineUserSession = importUserSession(userSession, true, false);
// started and lastSessionRefresh set to current time
int currentTime = Time.currentTime();
@@ -605,7 +456,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
- public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
+ public UserSessionAdapter getOfflineUserSession(RealmModel realm, String userSessionId) {
return getUserSession(realm, userSessionId, true);
}
@@ -617,26 +468,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
}
- // TODO:mposolda
- /*
+
+
@Override
- public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
- ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
+ public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession) {
+ UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
+ getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
+
+ AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession);
// update timestamp to current time
offlineClientSession.setTimestamp(Time.currentTime());
return offlineClientSession;
- }*/
-
- @Override
- public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession) {
- return null;
- }
-
- @Override
- public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
- return getClientSession(realm, clientSessionId, true);
}
@Override
@@ -654,12 +498,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
- public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
- ClientSessionModel clientSession = getOfflineClientSession(realm, clientSessionId);
- removeClientSession(realm, clientSession, true);
- }
-
- @Override
public long getOfflineSessionsCount(RealmModel realm, ClientModel client) {
return getUserSessionsCount(realm, client, true);
}
@@ -670,7 +508,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
- public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
+ public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline, boolean importAuthenticatedClientSessions) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(userSession.getId());
entity.setRealm(userSession.getRealm().getId());
@@ -688,34 +526,45 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(userSession.getStarted());
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
+
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, userSession.getId(), entity);
- return wrap(userSession.getRealm(), entity, offline);
+ UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline);
+
+ // Handle client sessions
+ if (importAuthenticatedClientSessions) {
+ for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+ importClientSession(importedSession, clientSession);
+ }
+ }
+
+ return importedSession;
}
- @Override
- public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
- ClientSessionEntity entity = new ClientSessionEntity();
- entity.setId(clientSession.getId());
- entity.setRealm(clientSession.getRealm().getId());
+
+ private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession) {
+ AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setAction(clientSession.getAction());
- entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
entity.setAuthMethod(clientSession.getProtocol());
- if (clientSession.getAuthenticatedUser() != null) {
- entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
- }
- entity.setClient(clientSession.getClient().getId());
+
entity.setNotes(clientSession.getNotes());
entity.setProtocolMappers(clientSession.getProtocolMappers());
entity.setRedirectUri(clientSession.getRedirectUri());
entity.setRoles(clientSession.getRoles());
entity.setTimestamp(clientSession.getTimestamp());
- entity.setUserSessionNotes(clientSession.getUserSessionNotes());
- Cache<String, SessionEntity> cache = getCache(offline);
- tx.put(cache, clientSession.getId(), entity);
- return wrap(clientSession.getRealm(), entity, offline);
+ Map<String, AuthenticatedClientSessionEntity> clientSessions = importedUserSession.getEntity().getAuthenticatedClientSessions();
+ if (clientSessions == null) {
+ clientSessions = new HashMap<>();
+ importedUserSession.getEntity().setAuthenticatedClientSessions(clientSessions);
+ }
+
+ clientSessions.put(clientSession.getClient().getId(), entity);
+
+ importedUserSession.update();
+
+ return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
index 83a3885..2b6fb71 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
@@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.initializer;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
@@ -64,12 +63,7 @@ public class OfflineUserSessionLoader implements SessionLoader {
for (UserSessionModel persistentSession : sessions) {
// Save to memory/infinispan
- UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true);
-
- for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
- ClientSessionModel offlineClientSession = session.sessions().importClientSession(persistentClientSession, true);
- offlineClientSession.setUserSession(offlineUserSession);
- }
+ UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true, true);
}
return true;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java
index 4907ec1..ec2a2cb 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Comparators.java
@@ -18,6 +18,8 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Comparator;
@@ -38,4 +40,17 @@ public class Comparators {
}
}
+
+ public static Comparator<UserSessionEntity> userSessionLastSessionRefresh() {
+ return new UserSessionLastSessionRefreshComparator();
+ }
+
+ private static class UserSessionLastSessionRefreshComparator implements Comparator<UserSessionEntity>, Serializable {
+
+ @Override
+ public int compare(UserSessionEntity u1, UserSessionEntity u2) {
+ return u1.getLastSessionRefresh() - u2.getLastSessionRefresh();
+ }
+ }
+
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java
index 6bf1358..dd2db68 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/Mappers.java
@@ -18,10 +18,10 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
-import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Map;
@@ -33,10 +33,6 @@ import java.util.function.Function;
*/
public class Mappers {
- public static Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp> clientSessionToUserSessionTimestamp() {
- return new ClientSessionToUserSessionTimestampMapper();
- }
-
public static Function<Map.Entry<String, Optional<UserSessionTimestamp>>, UserSessionTimestamp> userSessionTimestamp() {
return new UserSessionTimestampMapper();
}
@@ -49,21 +45,12 @@ public class Mappers {
return new SessionEntityMapper();
}
- public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
- return new LoginFailureIdMapper();
+ public static Function<Map.Entry<String, SessionEntity>, UserSessionEntity> userSessionEntity() {
+ return new UserSessionEntityMapper();
}
- public static Function<Map.Entry<String, SessionEntity>, String> clientSessionToUserSessionId() {
- return new ClientSessionToUserSessionIdMapper();
- }
-
- private static class ClientSessionToUserSessionTimestampMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp>, Serializable {
- @Override
- public UserSessionTimestamp apply(Map.Entry<String, SessionEntity> entry) {
- SessionEntity e = entry.getValue();
- ClientSessionEntity entity = (ClientSessionEntity) e;
- return new UserSessionTimestamp(entity.getUserSession(), entity.getTimestamp());
- }
+ public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
+ return new LoginFailureIdMapper();
}
private static class UserSessionTimestampMapper implements Function<Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>>, org.keycloak.models.sessions.infinispan.UserSessionTimestamp>, Serializable {
@@ -87,19 +74,18 @@ public class Mappers {
}
}
- private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
+ private static class UserSessionEntityMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionEntity>, Serializable {
@Override
- public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
- return entry.getKey();
+ public UserSessionEntity apply(Map.Entry<String, SessionEntity> entry) {
+ return (UserSessionEntity) entry.getValue();
}
}
- private static class ClientSessionToUserSessionIdMapper implements Function<Map.Entry<String, SessionEntity>, String>, Serializable {
+ private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
@Override
- public String apply(Map.Entry<String, SessionEntity> entry) {
- SessionEntity e = entry.getValue();
- ClientSessionEntity entity = (ClientSessionEntity) e;
- return entity.getUserSession();
+ public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
+ return entry.getKey();
}
}
+
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
index 77ff572..0cc3fcc 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/UserSessionPredicate.java
@@ -33,6 +33,8 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
private String user;
+ private String client;
+
private Integer expired;
private Integer expiredRefresh;
@@ -53,6 +55,11 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return this;
}
+ public UserSessionPredicate client(String clientUUID) {
+ this.client = clientUUID;
+ return this;
+ }
+
public UserSessionPredicate expired(Integer expired, Integer expiredRefresh) {
this.expired = expired;
this.expiredRefresh = expiredRefresh;
@@ -87,6 +94,10 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return false;
}
+ if (client != null && (entity.getAuthenticatedClientSessions() == null || !entity.getAuthenticatedClientSessions().containsKey(client))) {
+ return false;
+ }
+
if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
return false;
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
index d87612a..8ab15f7 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -19,12 +19,12 @@ package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
-import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@@ -64,15 +64,31 @@ public class UserSessionAdapter implements UserSessionModel {
@Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
- Map<String, ClientLoginSessionEntity> clientSessionEntities = entity.getClientLoginSessions();
+ Map<String, AuthenticatedClientSessionEntity> clientSessionEntities = entity.getAuthenticatedClientSessions();
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
+ List<String> removedClientUUIDS = new LinkedList<>();
+
if (clientSessionEntities != null) {
- clientSessionEntities.forEach((String key, ClientLoginSessionEntity value) -> {
- result.put(key, new AuthenticatedClientSessionAdapter(value, this, provider, cache));
+ clientSessionEntities.forEach((String key, AuthenticatedClientSessionEntity value) -> {
+ // Check if client still exists
+ ClientModel client = realm.getClientById(key);
+ if (client != null) {
+ result.put(key, new AuthenticatedClientSessionAdapter(value, client, this, provider, cache));
+ } else {
+ removedClientUUIDS.add(key);
+ }
});
}
+ // Update user session
+ if (!removedClientUUIDS.isEmpty()) {
+ for (String clientUUID : removedClientUUIDS) {
+ entity.getAuthenticatedClientSessions().remove(clientUUID);
+ }
+ update();
+ }
+
return Collections.unmodifiableMap(result);
}
@@ -101,6 +117,7 @@ public class UserSessionAdapter implements UserSessionModel {
@Override
public void setUser(UserModel user) {
entity.setUser(user.getId());
+ update();
}
@Override
@@ -180,19 +197,14 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
- public List<ClientSessionModel> getClientSessions() {
- if (entity.getClientSessions() != null) {
- List<ClientSessionModel> clientSessions = new LinkedList<>();
- for (String c : entity.getClientSessions()) {
- ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
- if (clientSession != null) {
- clientSessions.add(clientSession);
- }
- }
- return clientSessions;
- } else {
- return Collections.emptyList();
- }
+ public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
+ provider.updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
+
+ entity.setState(null);
+ entity.setNotes(null);
+ entity.setAuthenticatedClientSessions(null);
+
+ update();
}
@Override
@@ -217,4 +229,7 @@ public class UserSessionAdapter implements UserSessionModel {
provider.getTx().replace(cache, entity.getId(), entity);
}
+ Cache<String, SessionEntity> getCache() {
+ return cache;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
index 170654f..64246e8 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProvider.java
@@ -19,7 +19,6 @@ package org.keycloak.models.jpa.session;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
@@ -35,8 +34,9 @@ import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
-import java.util.LinkedList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -69,12 +69,11 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
- public void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline) {
+ public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
PersistentAuthenticatedClientSessionAdapter adapter = new PersistentAuthenticatedClientSessionAdapter(clientSession);
PersistentClientSessionModel model = adapter.getUpdatedModel();
PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
- entity.setClientSessionId(clientSession.getId());
entity.setClientId(clientSession.getClient().getId());
entity.setTimestamp(clientSession.getTimestamp());
String offlineStr = offlineToString(offline);
@@ -122,9 +121,9 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
- public void removeClientSession(String clientSessionId, boolean offline) {
+ public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
String offlineStr = offlineToString(offline);
- PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(clientSessionId, offlineStr));
+ PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(userSessionId, clientUUID, offlineStr));
if (sessionEntity != null) {
em.remove(sessionEntity);
@@ -218,8 +217,6 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
userSessionIds.add(entity.getUserSessionId());
}
- // TODO:mposolda
- /*
if (!userSessionIds.isEmpty()) {
TypedQuery<PersistentClientSessionEntity> query2 = em.createNamedQuery("findClientSessionsByUserSessions", PersistentClientSessionEntity.class);
query2.setParameter("userSessionIds", userSessionIds);
@@ -230,14 +227,14 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
int j = 0;
for (UserSessionModel ss : result) {
PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
- List<ClientSessionModel> currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
+ Map<String, AuthenticatedClientSessionModel> currentClientSessions = userSession.getAuthenticatedClientSessions(); // This is empty now and we want to fill it
boolean next = true;
while (next && j < clientSessions.size()) {
PersistentClientSessionEntity clientSession = clientSessions.get(j);
if (clientSession.getUserSessionId().equals(userSession.getId())) {
- PersistentClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
- currentClientSessions.add(clientSessAdapter);
+ PersistentAuthenticatedClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
+ currentClientSessions.put(clientSession.getClientId(), clientSessAdapter);
j++;
} else {
next = false;
@@ -245,7 +242,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
}
}
- */
+
return result;
}
@@ -256,7 +253,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
model.setLastSessionRefresh(entity.getLastSessionRefresh());
model.setData(entity.getData());
- List<ClientSessionModel> clientSessions = new LinkedList<>();
+ Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
}
@@ -264,7 +261,6 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
ClientModel client = realm.getClientById(entity.getClientId());
PersistentClientSessionModel model = new PersistentClientSessionModel();
- model.setClientSessionId(entity.getClientSessionId());
model.setClientId(entity.getClientId());
model.setUserSessionId(userSession.getId());
model.setUserId(userSession.getUser().getId());
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
index 35265af..e12223d 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/JpaUserSessionPersisterProviderFactory.java
@@ -20,7 +20,6 @@ package org.keycloak.models.jpa.session;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.session.UserSessionPersisterProviderFactory;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
index 7250836..8910bca 100644
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/session/PersistentClientSessionEntity.java
@@ -45,12 +45,10 @@ import java.io.Serializable;
public class PersistentClientSessionEntity {
@Id
- @Column(name="CLIENT_SESSION_ID", length = 36)
- protected String clientSessionId;
-
@Column(name = "USER_SESSION_ID", length = 36)
protected String userSessionId;
+ @Id
@Column(name="CLIENT_ID", length = 36)
protected String clientId;
@@ -64,14 +62,6 @@ public class PersistentClientSessionEntity {
@Column(name="DATA")
protected String data;
- public String getClientSessionId() {
- return clientSessionId;
- }
-
- public void setClientSessionId(String clientSessionId) {
- this.clientSessionId = clientSessionId;
- }
-
public String getUserSessionId() {
return userSessionId;
}
@@ -114,20 +104,27 @@ public class PersistentClientSessionEntity {
public static class Key implements Serializable {
- protected String clientSessionId;
+ protected String userSessionId;
+
+ protected String clientId;
protected String offline;
public Key() {
}
- public Key(String clientSessionId, String offline) {
- this.clientSessionId = clientSessionId;
+ public Key(String userSessionId, String clientId, String offline) {
+ this.userSessionId = userSessionId;
+ this.clientId = clientId;
this.offline = offline;
}
- public String getClientSessionId() {
- return clientSessionId;
+ public String getUserSessionId() {
+ return userSessionId;
+ }
+
+ public String getClientId() {
+ return clientId;
}
public String getOffline() {
@@ -141,7 +138,8 @@ public class PersistentClientSessionEntity {
Key key = (Key) o;
- if (this.clientSessionId != null ? !this.clientSessionId.equals(key.clientSessionId) : key.clientSessionId != null) return false;
+ if (this.userSessionId != null ? !this.userSessionId.equals(key.userSessionId) : key.userSessionId != null) return false;
+ if (this.clientId != null ? !this.clientId.equals(key.clientId) : key.clientId != null) return false;
if (this.offline != null ? !this.offline.equals(key.offline) : key.offline != null) return false;
return true;
@@ -149,7 +147,8 @@ public class PersistentClientSessionEntity {
@Override
public int hashCode() {
- int result = this.clientSessionId != null ? this.clientSessionId.hashCode() : 0;
+ int result = this.userSessionId != null ? this.userSessionId.hashCode() : 0;
+ result = 37 * result + (this.clientId != null ? this.clientId.hashCode() : 0);
result = 31 * result + (this.offline != null ? this.offline.hashCode() : 0);
return result;
}
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
new file mode 100644
index 0000000..c453a2e
--- /dev/null
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-3.2.0.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+ ~ and other contributors as indicated by the @author tags.
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
+
+ <changeSet author="mposolda@redhat.com" id="3.2.0">
+ <dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
+ <dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
+ <addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
+ </changeSet>
+
+</databaseChangeLog>
\ No newline at end of file
diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
index 59855ec..ae7d98b 100755
--- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
+++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml
@@ -47,4 +47,5 @@
<include file="META-INF/jpa-changelog-2.5.0.xml"/>
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
+ <include file="META-INF/jpa-changelog-3.2.0.xml"/>
</databaseChangeLog>
diff --git a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
index 15dc57f..099a39c 100644
--- a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
@@ -30,8 +30,8 @@ public interface AuthenticatedClientSessionModel extends CommonClientSessionMode
void setUserSession(UserSessionModel userSession);
UserSessionModel getUserSession();
- public String getNote(String name);
- public void setNote(String name, String value);
- public void removeNote(String name);
- public Map<String, String> getNotes();
+ String getNote(String name);
+ void setNote(String name, String value);
+ void removeNote(String name);
+ Map<String, String> getNotes();
}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
index 0dc2f5c..28a3145 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -17,7 +17,6 @@
package org.keycloak.models;
-import java.util.List;
import java.util.Map;
/**
@@ -55,9 +54,6 @@ public interface UserSessionModel {
Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions();
- // TODO: Remove
- List<ClientSessionModel> getClientSessions();
-
public String getNote(String name);
public void setNote(String name, String value);
public void removeNote(String name);
@@ -68,8 +64,10 @@ public interface UserSessionModel {
void setUser(UserModel user);
+ // Will completely restart whole state of user session. It will just keep same ID.
+ void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
+
public static enum State {
- LOGGING_IN, // TODO:mposolda Maybe state "LOGGING_IN" is useless now once userSession is attached after requiredActions
LOGGED_IN,
LOGGING_OUT,
LOGGED_OUT
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index 1afbcba..d474e89 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -27,10 +27,7 @@ import java.util.List;
*/
public interface UserSessionProvider extends Provider {
- ClientSessionModel createClientSession(RealmModel realm, ClientModel client);
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
- ClientSessionModel getClientSession(RealmModel realm, String id);
- ClientSessionModel getClientSession(String id);
UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
UserSessionModel getUserSession(RealmModel realm, String id);
@@ -42,14 +39,13 @@ public interface UserSessionProvider extends Provider {
long getActiveUserSessions(RealmModel realm, ClientModel client);
- // This will remove attached ClientLoginSessionModels too
+ /** This will remove attached ClientLoginSessionModels too **/
void removeUserSession(RealmModel realm, UserSessionModel session);
void removeUserSessions(RealmModel realm, UserModel user);
- // Implementation should propagate removal of expired userSessions to userSessionPersister too
+ /** Implementation should propagate removal of expired userSessions to userSessionPersister too **/
void removeExpired(RealmModel realm);
void removeUserSessions(RealmModel realm);
- void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId);
UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId);
@@ -59,25 +55,22 @@ public interface UserSessionProvider extends Provider {
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);
+ /** Newly created userSession won't contain attached AuthenticatedClientSessions **/
UserSessionModel createOfflineUserSession(UserSessionModel userSession);
UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId);
- // Removes the attached clientSessions as well
+ /** Removes the attached clientSessions as well **/
void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession);
- AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession);
- ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId);
+ /** Will automatically attach newly created offline client session to the offlineUserSession **/
+ AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
- // Don't remove userSession even if it's last userSession
- void removeOfflineClientSession(RealmModel realm, String clientSessionId);
-
long getOfflineSessionsCount(RealmModel realm, ClientModel client);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
- // Triggered by persister during pre-load
- UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
- ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
+ /** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
+ UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);
diff --git a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
index 42884cc..f284d93 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
@@ -35,7 +35,6 @@ public interface AuthenticationSessionProvider extends Provider {
void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession);
- // TODO: test and add to scheduler
void removeExpired(RealmModel realm);
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
index 9d71b78..0320299 100755
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java
@@ -17,7 +17,6 @@
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
index 7affd99..ba8276f 100644
--- a/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
+++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/AuthenticationRequest.java
@@ -17,7 +17,6 @@
package org.keycloak.broker.provider;
import org.jboss.resteasy.spi.HttpRequest;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.sessions.AuthenticationSessionModel;
diff --git a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
index f94e588..32195ef 100755
--- a/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/forms/login/LoginFormsProvider.java
@@ -78,8 +78,6 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setClientSessionCode(String accessCode);
- public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
-
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappers);
public LoginFormsProvider setAccessRequest(String message);
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
index 51efc23..10b28a2 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/DisabledUserSessionPersisterProvider.java
@@ -70,7 +70,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
}
@Override
- public void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline) {
+ public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
}
@@ -85,7 +85,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
}
@Override
- public void removeClientSession(String clientSessionId, boolean offline) {
+ public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java
index d410aba..20c3cb6 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentAuthenticatedClientSessionAdapter.java
@@ -20,7 +20,6 @@ package org.keycloak.models.session;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -55,10 +54,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
model = new PersistentClientSessionModel();
model.setClientId(clientSession.getClient().getId());
- model.setClientSessionId(clientSession.getId());
- if (clientSession.getUserSession() != null) {
- model.setUserId(clientSession.getUserSession().getUser().getId());
- }
+ model.setUserId(clientSession.getUserSession().getUser().getId());
model.setUserSessionId(clientSession.getUserSession().getId());
model.setTimestamp(clientSession.getTimestamp());
@@ -101,7 +97,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
@Override
public String getId() {
- return model.getClientSessionId();
+ return null;
}
@Override
@@ -194,7 +190,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
public void setNote(String name, String value) {
PersistentClientSessionData entity = getData();
if (entity.getNotes() == null) {
- entity.setNotes(new HashMap<String, String>());
+ entity.setNotes(new HashMap<>());
}
entity.getNotes().put(name, value);
}
@@ -214,13 +210,12 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
return entity.getNotes();
}
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
- if (o == null || !(o instanceof ClientSessionModel)) return false;
+ if (o == null || !(o instanceof AuthenticatedClientSessionModel)) return false;
- ClientSessionModel that = (ClientSessionModel) o;
+ AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
return that.getId().equals(getId());
}
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
index 5990eea..ee33fed 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentClientSessionModel.java
@@ -22,20 +22,12 @@ package org.keycloak.models.session;
*/
public class PersistentClientSessionModel {
- private String clientSessionId;
private String userSessionId;
private String clientId;
private String userId;
private int timestamp;
private String data;
- public String getClientSessionId() {
- return clientSessionId;
- }
-
- public void setClientSessionId(String clientSessionId) {
- this.clientSessionId = clientSessionId;
- }
public String getUserSessionId() {
return userSessionId;
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
index 7ba4bc4..170d381 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/PersistentUserSessionAdapter.java
@@ -19,7 +19,6 @@ package org.keycloak.models.session;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -28,7 +27,6 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
/**
@@ -39,7 +37,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
private final PersistentUserSessionModel model;
private final UserModel user;
private final RealmModel realm;
- private final List<ClientSessionModel> clientSessions;
+ private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
private PersistentUserSessionData data;
@@ -60,14 +58,14 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
this.user = other.getUser();
this.realm = other.getRealm();
- this.clientSessions = other.getClientSessions();
+ this.authenticatedClientSessions = other.getAuthenticatedClientSessions();
}
- public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List<ClientSessionModel> clientSessions) {
+ public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, Map<String, AuthenticatedClientSessionModel> clientSessions) {
this.model = model;
this.realm = realm;
this.user = user;
- this.clientSessions = clientSessions;
+ this.authenticatedClientSessions = clientSessions;
}
// Lazily init data
@@ -161,14 +159,8 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
}
@Override
- public List<ClientSessionModel> getClientSessions() {
- return clientSessions;
- }
-
- // TODO:mposolda
- @Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
- return null;
+ return authenticatedClientSessions;
}
@Override
@@ -209,6 +201,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
}
@Override
+ public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
+ throw new IllegalStateException("Not supported");
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserSessionModel)) return false;
diff --git a/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
index c5370ed..ba5a595 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/session/UserSessionPersisterProvider.java
@@ -35,7 +35,7 @@ public interface UserSessionPersisterProvider extends Provider {
void createUserSession(UserSessionModel userSession, boolean offline);
// Assuming that corresponding userSession is already persisted
- void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline);
+ void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline);
void updateUserSession(UserSessionModel userSession, boolean offline);
@@ -43,7 +43,7 @@ public interface UserSessionPersisterProvider extends Provider {
void removeUserSession(String userSessionId, boolean offline);
// Called during revoke. It will remove userSession too if this was last clientSession attached to it
- void removeClientSession(String clientSessionId, boolean offline);
+ void removeClientSession(String userSessionId, String clientUUID, boolean offline);
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
index fd87a6e..c6f834b 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/resetcred/ResetCredentialsActionTokenHandler.java
@@ -20,12 +20,14 @@ import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.actiontoken.*;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
+import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
+import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.resources.LoginActionsServiceChecks.IsActionRequired;
import org.keycloak.sessions.CommonClientSessionModel.Action;
import javax.ws.rs.core.Response;
@@ -61,7 +63,7 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
@Override
public Response handleToken(ResetCredentialsActionToken token, ActionTokenContext tokenContext, ProcessFlow processFlow) {
- AuthenticationProcessor authProcessor = new ResetCredsAuthenticationProcessor(tokenContext);
+ AuthenticationProcessor authProcessor = new ResetCredsAuthenticationProcessor();
return processFlow.processFlow(
false,
@@ -87,34 +89,31 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
public static class ResetCredsAuthenticationProcessor extends AuthenticationProcessor {
- private final ActionTokenContext tokenContext;
-
- public ResetCredsAuthenticationProcessor(ActionTokenContext tokenContext) {
- this.tokenContext = tokenContext;
- }
-
@Override
protected Response authenticationComplete() {
- boolean firstBrokerLoginInProgress = (tokenContext.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
+ boolean firstBrokerLoginInProgress = (authenticationSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
if (firstBrokerLoginInProgress) {
- UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, tokenContext.getRealm(), tokenContext.getAuthenticationSession());
- if (!linkingUser.getId().equals(tokenContext.getAuthenticationSession().getAuthenticatedUser().getId())) {
+ UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
+ if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
return ErrorPage.error(session,
Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE,
- tokenContext.getAuthenticationSession().getAuthenticatedUser().getUsername(),
+ authenticationSession.getAuthenticatedUser().getUsername(),
linkingUser.getUsername()
);
}
- logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login.", linkingUser.getUsername());
+ SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
+ authenticationSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
- // TODO:mposolda Isn't this a bug that we redirect to 'afterBrokerLoginEndpoint' without rather continue with firstBrokerLogin and other authenticators like OTP?
- //return redirectToAfterBrokerLoginEndpoint(authSession, true);
- return null;
+ logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login with identity provider '%s'.",
+ linkingUser.getUsername(), serializedCtx.getIdentityProviderId());
+
+ return LoginActionsService.redirectToAfterBrokerLoginEndpoint(session, realm, uriInfo, authenticationSession, true);
} else {
return super.authenticationComplete();
}
}
+
}
}
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index 0daec9a..2427091 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -33,7 +33,6 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -53,11 +52,11 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
-import org.keycloak.services.util.PageExpiredRedirect;
+import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.CommonClientSessionModel;
import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashMap;
@@ -73,7 +72,6 @@ public class AuthenticationProcessor {
public static final String LAST_PROCESSED_EXECUTION = "last.processed.execution";
public static final String CURRENT_FLOW_PATH = "current.flow.path";
public static final String FORKED_FROM = "forked.from";
- public static final String FORWARDED_ERROR_MESSAGE_NOTE = "forwardedErrorMessage";
public static final String BROKER_SESSION_ID = "broker.session.id";
public static final String BROKER_USER_ID = "broker.user.id";
@@ -595,9 +593,9 @@ public class AuthenticationProcessor {
}
public boolean isSuccessful(AuthenticationExecutionModel model) {
- ClientSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
+ AuthenticationSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
if (status == null) return false;
- return status == ClientSessionModel.ExecutionStatus.SUCCESS;
+ return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS;
}
public Response handleBrowserException(Exception failure) {
@@ -629,7 +627,7 @@ public class AuthenticationProcessor {
} else if (e.getError() == AuthenticationFlowError.FORK_FLOW) {
ForkFlowException reset = (ForkFlowException)e;
AuthenticationSessionModel clone = clone(session, authenticationSession);
- clone.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ clone.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
setAuthenticationSession(clone);
AuthenticationProcessor processor = new AuthenticationProcessor();
@@ -726,9 +724,9 @@ public class AuthenticationProcessor {
public Response redirectToFlow() {
- URI redirect = new PageExpiredRedirect(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
+ URI redirect = new AuthenticationFlowURLHelper(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
- logger.info("Redirecting to URL: " + redirect.toString());
+ logger.debug("Redirecting to URL: " + redirect.toString());
return Response.status(302).location(redirect).build();
@@ -750,6 +748,8 @@ public class AuthenticationProcessor {
authSession.clearUserSessionNotes();
authSession.clearAuthNotes();
+ authSession.setAction(CommonClientSessionModel.Action.AUTHENTICATE.name());
+
authSession.setAuthNote(CURRENT_FLOW_PATH, flowPath);
}
@@ -766,7 +766,7 @@ public class AuthenticationProcessor {
clone.setTimestamp(Time.currentTime());
clone.setAuthNote(FORKED_FROM, authSession.getId());
- logger.infof("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
+ logger.debugf("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
return clone;
@@ -777,10 +777,9 @@ public class AuthenticationProcessor {
logger.debug("authenticationAction");
checkClientSession(true);
String current = authenticationSession.getAuthNote(CURRENT_AUTHENTICATION_EXECUTION);
- if (!execution.equals(current)) {
- // TODO:mposolda debug
- logger.info("Current execution does not equal executed execution. Might be a page refresh");
- return new PageExpiredRedirect(session, realm, uriInfo).showPageExpired(authenticationSession);
+ if (execution == null || !execution.equals(current)) {
+ logger.debug("Current execution does not equal executed execution. Might be a page refresh");
+ return new AuthenticationFlowURLHelper(session, realm, uriInfo).showPageExpired(authenticationSession);
}
UserModel authUser = authenticationSession.getAuthenticatedUser();
validateUser(authUser);
@@ -812,7 +811,7 @@ public class AuthenticationProcessor {
ClientSessionCode code = new ClientSessionCode(session, realm, authenticationSession);
if (checkAction) {
- String action = ClientSessionModel.Action.AUTHENTICATE.name();
+ String action = AuthenticationSessionModel.Action.AUTHENTICATE.name();
if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
}
@@ -862,26 +861,29 @@ public class AuthenticationProcessor {
if (attemptedUsername != null) username = attemptedUsername;
String rememberMe = authSession.getAuthNote(Details.REMEMBER_ME);
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
+ String brokerSessionId = authSession.getAuthNote(BROKER_SESSION_ID);
+ String brokerUserId = authSession.getAuthNote(BROKER_USER_ID);
if (userSession == null) { // if no authenticator attached a usersession
userSession = session.sessions().getUserSession(realm, authSession.getId());
if (userSession == null) {
- String brokerSessionId = authSession.getAuthNote(BROKER_SESSION_ID);
- String brokerUserId = authSession.getAuthNote(BROKER_USER_ID);
userSession = session.sessions().createUserSession(authSession.getId(), realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
, remember, brokerSessionId, brokerUserId);
+ } else if (userSession.getUser() == null || !AuthenticationManager.isSessionValid(realm, userSession)) {
+ userSession.restartSession(realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
+ , remember, brokerSessionId, brokerUserId);
} else {
// We have existing userSession even if it wasn't attached to authenticator. Could happen if SSO authentication was ignored (eg. prompt=login) and in some other cases.
- // We need to handle case when different user was used and update that (TODO:mposolda evaluate this again and corner cases like token refresh etc. AND ROLES!!! LIKELY ERROR SHOULD BE SHOWN IF ATTEMPT TO AUTHENTICATE AS DIFFERENT USER)
- logger.info("No SSO login, but found existing userSession with ID '%s' after finished authentication.");
+ // We need to handle case when different user was used
+ logger.debugf("No SSO login, but found existing userSession with ID '%s' after finished authentication.", userSession.getId());
if (!authSession.getAuthenticatedUser().equals(userSession.getUser())) {
event.detail(Details.EXISTING_USER, userSession.getUser().getId());
event.error(Errors.DIFFERENT_USER_AUTHENTICATED);
throw new ErrorPageException(session, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername());
}
}
- userSession.setState(UserSessionModel.State.LOGGING_IN);
+ userSession.setState(UserSessionModel.State.LOGGED_IN);
}
if (remember) {
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
index 5e0851a..af63974 100644
--- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/ScriptBasedAuthenticator.java
@@ -47,7 +47,7 @@ import java.util.Map;
* <li>{@code realm} the {@link RealmModel}</li>
* <li>{@code user} the current {@link UserModel}</li>
* <li>{@code session} the active {@link KeycloakSession}</li>
- * <li>{@code clientSession} the current {@link org.keycloak.models.ClientSessionModel}</li>
+ * <li>{@code clientSession} the current {@link org.keycloak.sessions.AuthenticationSessionModel}</li>
* <li>{@code httpRequest} the current {@link org.jboss.resteasy.spi.HttpRequest}</li>
* <li>{@code LOG} a {@link org.jboss.logging.Logger} scoped to {@link ScriptBasedAuthenticator}/li>
* </ol>
diff --git a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
index dfa1afa..3a9c53c 100755
--- a/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/DefaultAuthenticationFlow.java
@@ -20,9 +20,9 @@ package org.keycloak.authentication;
import org.jboss.logging.Logger;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.ServicesLogger;
+import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Response;
import java.util.Iterator;
@@ -51,11 +51,11 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
protected boolean isProcessed(AuthenticationExecutionModel model) {
if (model.isDisabled()) return true;
- ClientSessionModel.ExecutionStatus status = processor.getAuthenticationSession().getExecutionStatus().get(model.getId());
+ AuthenticationSessionModel.ExecutionStatus status = processor.getAuthenticationSession().getExecutionStatus().get(model.getId());
if (status == null) return false;
- return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
- || status == ClientSessionModel.ExecutionStatus.ATTEMPTED
- || status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
+ return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS || status == AuthenticationSessionModel.ExecutionStatus.SKIPPED
+ || status == AuthenticationSessionModel.ExecutionStatus.ATTEMPTED
+ || status == AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED;
}
@@ -75,7 +75,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
Response flowChallenge = authenticationFlow.processAction(actionExecution);
if (flowChallenge == null) {
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
return processFlow();
} else {
@@ -115,7 +115,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
}
if (model.isAlternative() && alternativeSuccessful) {
logger.debug("Skip alternative execution");
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
if (model.isAuthenticatorFlow()) {
@@ -123,7 +123,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
Response flowChallenge = authenticationFlow.processFlow();
if (flowChallenge == null) {
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
continue;
} else {
@@ -131,13 +131,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
alternativeChallenge = flowChallenge;
challengedAlternativeExecution = model;
} else if (model.isRequired()) {
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return flowChallenge;
} else if (model.isOptional()) {
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
} else {
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
return flowChallenge;
@@ -154,7 +154,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (authenticator.requiresUser() && authUser == null) {
if (alternativeChallenge != null) {
- processor.getAuthenticationSession().setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ processor.getAuthenticationSession().setExecutionStatus(challengedAlternativeExecution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return alternativeChallenge;
}
throw new AuthenticationFlowException("authenticator: " + factory.getId(), AuthenticationFlowError.UNKNOWN_USER);
@@ -166,14 +166,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (model.isRequired()) {
if (factory.isUserSetupAllowed()) {
logger.debugv("authenticator SETUP_REQUIRED: {0}", factory.getId());
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED);
authenticator.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser());
continue;
} else {
throw new AuthenticationFlowException(AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED);
}
} else if (model.isOptional()) {
- processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
@@ -198,13 +198,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
switch (status) {
case SUCCESS:
logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
if (execution.isAlternative()) alternativeSuccessful = true;
return null;
case FAILED:
logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
processor.logFailure();
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.FAILED);
if (result.getChallenge() != null) {
return sendChallenge(result, execution);
}
@@ -214,37 +214,37 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
processor.getAuthenticationSession().setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
case FORCE_CHALLENGE:
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
case CHALLENGE:
logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
if (execution.isRequired()) {
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
UserModel authenticatedUser = processor.getAuthenticationSession().getAuthenticatedUser();
if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
if (execution.isAlternative()) {
alternativeChallenge = result.getChallenge();
challengedAlternativeExecution = execution;
} else {
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
}
return null;
case FAILURE_CHALLENGE:
logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
processor.logFailure();
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
case ATTEMPTED:
logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
}
- processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
+ processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
return null;
case FLOW_RESET:
processor.resetFlow();
diff --git a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
index 0e121dd..955879f 100755
--- a/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
+++ b/services/src/main/java/org/keycloak/authentication/FormAuthenticationFlow.java
@@ -24,7 +24,6 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -167,13 +166,13 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
if (!actionExecution.equals(formExecution.getId())) {
throw new AuthenticationFlowException("action is not current execution", AuthenticationFlowError.INTERNAL_ERROR);
}
- Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
+ Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
List<FormAction> requiredActions = new LinkedList<>();
List<ValidationContextImpl> successes = new LinkedList<>();
List<ValidationContextImpl> errors = new LinkedList<>();
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
if (!formActionExecution.isEnabled()) {
- executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
FormActionFactory factory = (FormActionFactory)processor.getSession().getKeycloakSessionFactory().getProviderFactory(FormAction.class, formActionExecution.getAuthenticator());
@@ -190,14 +189,14 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
if (formActionExecution.isRequired()) {
if (factory.isUserSetupAllowed()) {
AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", formExecution.getAuthenticator());
- executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
+ executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED);
requiredActions.add(action);
continue;
} else {
throw new AuthenticationFlowException(AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED);
}
} else if (formActionExecution.isOptional()) {
- executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
+ executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
@@ -206,10 +205,10 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
ValidationContextImpl result = new ValidationContextImpl(formActionExecution, action);
action.validate(result);
if (result.success) {
- executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
+ executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
successes.add(result);
} else {
- executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
+ executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
errors.add(result);
}
}
@@ -235,14 +234,14 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
context.action.success(context);
}
// set status and required actions only if form is fully successful
- for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
+ for (Map.Entry<String, AuthenticationSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
processor.getAuthenticationSession().setExecutionStatus(entry.getKey(), entry.getValue());
}
for (FormAction action : requiredActions) {
action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser());
}
- processor.getAuthenticationSession().setExecutionStatus(actionExecution, ClientSessionModel.ExecutionStatus.SUCCESS);
+ processor.getAuthenticationSession().setExecutionStatus(actionExecution, AuthenticationSessionModel.ExecutionStatus.SUCCESS);
processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
return null;
}
diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
index fd3ce48..f3ea22f 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -69,17 +69,14 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
return;
}
- LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
- .setClientSessionCode(context.generateCode())
- .setAuthenticationSession(authSession)
- .setUser(context.getUser());
+ LoginFormsProvider loginFormsProvider = context.form();
Response challenge;
// Do not allow resending e-mail by simple page refresh, i.e. when e-mail sent, it should be resent properly via email-verification endpoint
if (! Objects.equals(authSession.getAuthNote(Constants.VERIFY_EMAIL_KEY), email)) {
authSession.setAuthNote(Constants.VERIFY_EMAIL_KEY, email);
context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, email).success();
- challenge = sendVerifyEmail(context.getSession(), context.generateCode(), context.getUser(), context.getAuthenticationSession());
+ challenge = sendVerifyEmail(context.getSession(), loginFormsProvider, context.getUser(), context.getAuthenticationSession());
} else {
challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
}
@@ -87,9 +84,15 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
context.challenge(challenge);
}
+
@Override
public void processAction(RequiredActionContext context) {
- context.failure();
+ logger.infof("Re-sending email requested for user: %s", context.getUser().getUsername());
+
+ // This will allow user to re-send email again
+ context.getAuthenticationSession().removeAuthNote(Constants.VERIFY_EMAIL_KEY);
+
+ requiredActionChallenge(context);
}
@@ -124,15 +127,10 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
return UserModel.RequiredAction.VERIFY_EMAIL.name();
}
- public static Response sendVerifyEmail(KeycloakSession session, String clientCode, UserModel user, AuthenticationSessionModel authSession) throws UriBuilderException, IllegalArgumentException {
+ private Response sendVerifyEmail(KeycloakSession session, LoginFormsProvider forms, UserModel user, AuthenticationSessionModel authSession) throws UriBuilderException, IllegalArgumentException {
RealmModel realm = session.getContext().getRealm();
UriInfo uriInfo = session.getContext().getUri();
- LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class)
- .setClientSessionCode(clientCode)
- .setAuthenticationSession(authSession)
- .setUser(authSession.getAuthenticatedUser());
-
int validityInSecs = realm.getAccessCodeLifespanUserAction();
int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
// ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), absoluteExpirationInSecs, null,
diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
index 6b5b027..9ea53e2 100644
--- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
+++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java
@@ -23,7 +23,6 @@ import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.util.Tokens;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@@ -118,8 +117,8 @@ public class KeycloakIdentity implements Identity {
@Override
public String getId() {
if (isResourceServer()) {
- ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
- return clientSession.getClient().getId();
+ ClientModel client = getTargetClient();
+ return client==null ? null : client.getId();
}
return this.accessToken.getSubject();
@@ -137,20 +136,10 @@ public class KeycloakIdentity implements Identity {
private boolean isResourceServer() {
UserModel clientUser = null;
- if (this.accessToken.getClientSession() != null) {
- ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
+ ClientModel clientModel = getTargetClient();
- if (clientSession != null) {
- clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient());
- }
- }
-
- if (this.accessToken.getIssuedFor() != null) {
- ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm);
-
- if (clientModel != null) {
- clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
- }
+ if (clientModel != null) {
+ clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
}
if (clientUser == null) {
@@ -159,4 +148,17 @@ public class KeycloakIdentity implements Identity {
return this.accessToken.getSubject().equals(clientUser.getId());
}
+
+ private ClientModel getTargetClient() {
+ if (this.accessToken.getIssuedFor() != null) {
+ return realm.getClientByClientId(accessToken.getIssuedFor());
+ }
+
+ if (this.accessToken.getAudience() != null && this.accessToken.getAudience().length > 0) {
+ String audience = this.accessToken.getAudience()[0];
+ return realm.getClientByClientId(audience);
+ }
+
+ return null;
+ }
}
diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
index 4f24373..45183c3 100755
--- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java
@@ -32,7 +32,6 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.keys.loader.PublicKeyStorageManager;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index 5f02b8e..51d6eb8 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -31,7 +31,6 @@ import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder;
import org.keycloak.keys.RsaKeyMetadata;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
index a474f4f..f597d5c 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/SessionsBean.java
@@ -19,7 +19,6 @@ package org.keycloak.forms.account.freemarker.model;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -79,8 +78,8 @@ public class SessionsBean {
public Set<String> getClients() {
Set<String> clients = new HashSet<String>();
- for (ClientSessionModel clientSession : session.getClientSessions()) {
- ClientModel client = clientSession.getClient();
+ for (String clientUUID : session.getAuthenticatedClientSessions().keySet()) {
+ ClientModel client = realm.getClientById(clientUUID);
clients.add(client.getClientId());
}
return clients;
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index e93df33..625406c 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -90,7 +90,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
private UserModel user;
- private AuthenticationSessionModel authenticationSession;
private final Map<String, Object> attributes = new HashMap<String, Object>();
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
@@ -156,6 +155,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
uriBuilder.replaceQuery(null);
}
+ URI baseUri = uriBuilder.build();
+
+ if (accessCode != null) {
+ uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
+ }
+ URI baseUriWithCode = uriBuilder.build();
+
for (String k : queryParameterMap.keySet()) {
Object[] objects = queryParameterMap.get(k).toArray();
@@ -163,13 +169,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
uriBuilder.replaceQueryParam(k, objects);
}
- // TODO:hmlnarik Why was the following removed in https://github.com/hmlnarik/keycloak/commit/6df8f13109d6ea77b455e04d884994e5831ea52b#diff-d795b851c2db89d5198c897aba4c40c9
- if (accessCode != null) {
- uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
- }
-
- URI baseUri = uriBuilder.build();
-
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme;
try {
@@ -221,7 +220,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
- attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUri));
+ attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
@@ -313,9 +312,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (objects.length == 1 && objects[0] == null) continue; //
uriBuilder.replaceQueryParam(k, objects);
}
- if (accessCode != null) {
- uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
- }
+
URI baseUri = uriBuilder.build();
if (accessCode != null) {
@@ -573,12 +570,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
}
@Override
- public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authSession) {
- this.authenticationSession = authSession;
- return this;
- }
-
- @Override
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappersRequested) {
this.realmRolesRequested = realmRolesRequested;
this.resourceRolesRequested = resourceRolesRequested;
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java
index 9b3a9f3..0c574c1 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/UrlBean.java
@@ -85,10 +85,6 @@ public class UrlBean {
return Urls.loginUsernameReminder(baseURI, realm).toString();
}
- public String getLoginEmailVerificationUrl() {
- return Urls.loginActionEmailVerification(baseURI, realm).toString();
- }
-
public String getFirstBrokerLoginUrl() {
return Urls.firstBrokerLoginProcessor(baseURI, realm).toString();
}
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index 89680bb..9c1e5a5 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -21,7 +21,6 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.common.ClientConnection;
-import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationFlowModel;
@@ -35,7 +34,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
-import org.keycloak.services.util.PageExpiredRedirect;
+import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Context;
@@ -159,7 +158,7 @@ public abstract class AuthorizationEndpointBase {
ClientSessionCode<AuthenticationSessionModel> check = new ClientSessionCode<>(session, realm, authSession);
if (!check.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
- logger.infof("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
+ logger.debugf("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
authSession.restartSession(realm, client);
return new AuthorizationEndpointChecks(authSession);
@@ -167,10 +166,10 @@ public abstract class AuthorizationEndpointBase {
// Check if we have lastProcessedExecution and restart the session just if yes. Otherwise update just client information from the AuthorizationEndpoint request.
// This difference is needed, because of logout from JS applications in multiple browser tabs.
if (hasProcessedExecution(authSession)) {
- logger.info("New request from application received, but authentication session already exists. Restart existing authentication session");
+ logger.debug("New request from application received, but authentication session already exists. Restart existing authentication session");
authSession.restartSession(realm, client);
} else {
- logger.info("New request from application received, but authentication session already exists. Update client information in existing authentication session");
+ logger.debug("New request from application received, but authentication session already exists. Update client information in existing authentication session");
authSession.clearClientNotes(); // update client data
authSession.updateClient(client);
}
@@ -178,7 +177,7 @@ public abstract class AuthorizationEndpointBase {
return new AuthorizationEndpointChecks(authSession);
} else {
- logger.info("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
+ logger.debug("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
// See if we have lastProcessedExecution note. If yes, we are expired. Also if we are in different flow than initial one. Otherwise it is browser refresh of initial username/password form
if (!shouldShowExpirePage(authSession)) {
@@ -186,7 +185,7 @@ public abstract class AuthorizationEndpointBase {
} else {
CacheControlUtil.noBackButtonCacheControlHeader();
- Response response = new PageExpiredRedirect(session, realm, uriInfo)
+ Response response = new AuthenticationFlowURLHelper(session, realm, uriInfo)
.showPageExpired(authSession);
return new AuthorizationEndpointChecks(response);
}
@@ -196,11 +195,11 @@ public abstract class AuthorizationEndpointBase {
UserSessionModel userSession = authSessionId==null ? null : session.sessions().getUserSession(realm, authSessionId);
if (userSession != null) {
- logger.infof("Sent request to authz endpoint. We don't have authentication session with ID '%s' but we have userSession. Will re-create authentication session with same ID", authSessionId);
+ logger.debugf("Sent request to authz endpoint. We don't have authentication session with ID '%s' but we have userSession. Will re-create authentication session with same ID", authSessionId);
authSession = session.authenticationSessions().createAuthenticationSession(authSessionId, realm, client);
} else {
authSession = manager.createAuthenticationSession(realm, client, true);
- logger.infof("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
+ logger.debugf("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
}
return new AuthorizationEndpointChecks(authSession);
@@ -224,17 +223,13 @@ public abstract class AuthorizationEndpointBase {
}
String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
- // Check if we transitted between flows (eg. clicking "register" on login screen)
- if (!initialFlow.equals(lastFlow)) {
- logger.infof("Transition between flows! Current flow: %s, Previous flow: %s", initialFlow, lastFlow);
-
- if (lastFlow == null || LoginActionsService.isFlowTransitionAllowed(initialFlow, lastFlow)) {
- authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, initialFlow);
- authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
- return false;
- } else {
- return true;
- }
+ // Check if we transitted between flows (eg. clicking "register" on login screen and then clicking browser 'back', which showed this page)
+ if (!initialFlow.equals(lastFlow) && AuthenticationSessionModel.Action.AUTHENTICATE.toString().equals(authSession.getAction())) {
+ logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", initialFlow, lastFlow);
+
+ authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, initialFlow);
+ authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+ return false;
}
return false;
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 0cd0219..26d012b 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
@@ -28,7 +28,6 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -41,7 +40,6 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
-import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
@@ -387,7 +385,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private void updateAuthenticationSession() {
authenticationSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authenticationSession.setRedirectUri(redirectUri);
- authenticationSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ authenticationSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType());
authenticationSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam());
authenticationSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
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 d564840..83570ef 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -210,12 +210,13 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
}
+ String[] parts = code.split("\\.");
+ if (parts.length == 4) {
+ event.detail(Details.CODE_ID, parts[2]);
+ }
+
ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, AuthenticatedClientSessionModel.class);
if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
- String[] parts = code.split("\\.");
- if (parts.length == 2) {
- event.detail(Details.CODE_ID, parts[1]);
- }
event.error(Errors.INVALID_CODE);
// Attempt to use same code twice should invalidate existing clientSession
@@ -228,17 +229,16 @@ public class TokenEndpoint {
}
AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
- event.detail(Details.CODE_ID, clientSession.getId());
if (!parseResult.getCode().isValid(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
}
- // TODO: This shouldn't be needed to write into the clientLoginSessionModel itself
+ // TODO: This shouldn't be needed to write into the AuthenticatedClientSessionModel itself
parseResult.getCode().setAction(null);
- // TODO: Maybe rather create userSession even at this stage? Not sure...
+ // TODO: Maybe rather create userSession even at this stage?
UserSessionModel userSession = clientSession.getUserSession();
if (userSession == null) {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
index f4ef89d..d239951 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/mappers/AbstractUserRoleMappingMapper.java
@@ -93,9 +93,9 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper
// get a set of all realm roles assigned to the user or its group
Stream<RoleModel> clientUserRoles = getAllUserRolesStream(user).filter(restriction);
- boolean dontLimitScope = userSession.getClientSessions().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
+ boolean dontLimitScope = userSession.getAuthenticatedClientSessions().values().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
if (! dontLimitScope) {
- Set<RoleModel> clientRoles = userSession.getClientSessions().stream()
+ Set<RoleModel> clientRoles = userSession.getAuthenticatedClientSessions().values().stream()
.flatMap(cs -> cs.getClient().getScopeMappings().stream())
.collect(Collectors.toSet());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 93df9b0..e7c147d 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -575,7 +575,6 @@ public class TokenManager {
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, AuthenticatedClientSessionModel clientSession, UriInfo uriInfo) {
AccessToken token = new AccessToken();
- token.clientSession(clientSession.getId());
token.id(KeycloakModelUtils.generateId());
token.type(TokenUtil.TOKEN_TYPE_BEARER);
token.subject(user.getId());
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 10e45a2..a8218c1 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -376,8 +376,12 @@ public class SamlProtocol implements LoginProtocol {
clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
- builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan()).sessionIndex(clientSession.getId())
+ builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan())
.requestIssuer(clientSession.getClient().getClientId()).nameIdentifier(nameIdFormat, nameId).authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
+
+ String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
+ builder.sessionIndex(sessionIndex);
+
if (!samlClient.includeAuthnStatement()) {
builder.disableAuthnStatement(true);
}
@@ -682,8 +686,12 @@ public class SamlProtocol implements LoginProtocol {
protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, AuthenticatedClientSessionModel clientSession, ClientModel client) {
// build userPrincipal with subject used at login
- SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)).sessionIndex(clientSession.getId())
+ SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm))
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl);
+
+ String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
+ logoutBuilder.sessionIndex(sessionIndex);
+
return logoutBuilder;
}
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
index f81d25d..9a6790b 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -37,8 +37,8 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.keys.RsaKeyMetadata;
+import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -55,7 +55,6 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CacheControlUtil;
@@ -99,11 +98,6 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
- @Context
- protected KeycloakSession session;
-
- private String requestRelayState;
-
public SamlService(RealmModel realm, EventBuilder event) {
super(realm, event);
}
@@ -283,7 +277,7 @@ public class SamlService extends AuthorizationEndpointBase {
authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
authSession.setRedirectUri(redirect);
- authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authSession.setClientNote(SamlProtocol.SAML_BINDING, bindingType);
authSession.setClientNote(GeneralConstants.RELAY_STATE, relayState);
authSession.setClientNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
@@ -378,31 +372,22 @@ public class SamlService extends AuthorizationEndpointBase {
userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
// remove client from logout requests
- for (ClientSessionModel clientSession : userSession.getClientSessions()) {
- if (clientSession.getClient().getId().equals(client.getId())) {
- clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
- }
+ AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId());
+ if (clientSession != null) {
+ clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
}
logger.debug("browser Logout");
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
} else if (logoutRequest.getSessionIndex() != null) {
for (String sessionIndex : logoutRequest.getSessionIndex()) {
- ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
+
+ AuthenticatedClientSessionModel clientSession = SamlSessionUtils.getClientSession(session, realm, sessionIndex);
if (clientSession == null)
continue;
UserSessionModel userSession = clientSession.getUserSession();
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
// remove requesting client from logout
- clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
-
- // Remove also other clientSessions of this client as there could be more in this UserSession
- if (userSession != null) {
- for (ClientSessionModel clientSession2 : userSession.getClientSessions()) {
- if (clientSession2.getClient().getId().equals(client.getId())) {
- clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
- }
- }
- }
+ clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
}
try {
@@ -609,6 +594,10 @@ public class SamlService extends AuthorizationEndpointBase {
event.error(Errors.CLIENT_NOT_FOUND);
return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
}
+ if (!client.isEnabled()) {
+ event.error(Errors.CLIENT_DISABLED);
+ return ErrorPage.error(session, Messages.CLIENT_DISABLED);
+ }
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
logger.error("SAML assertion consumer url not set up");
event.error(Errors.INVALID_REDIRECT_URI);
@@ -654,7 +643,7 @@ public class SamlService extends AuthorizationEndpointBase {
AuthenticationSessionModel authSession = checks.authSession;
authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
- authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
+ authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authSession.setClientNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
authSession.setClientNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
authSession.setRedirectUri(redirect);
diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java
new file mode 100644
index 0000000..0083fdc
--- /dev/null
+++ b/services/src/main/java/org/keycloak/protocol/saml/SamlSessionUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.protocol.saml;
+
+import java.util.regex.Pattern;
+
+import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SamlSessionUtils {
+
+ private static final String DELIMITER = "::";
+
+ // Just perf optimization
+ private static final Pattern PATTERN = Pattern.compile(DELIMITER);
+
+
+ public static String getSessionIndex(AuthenticatedClientSessionModel clientSession) {
+ UserSessionModel userSession = clientSession.getUserSession();
+ ClientModel client = clientSession.getClient();
+
+ return userSession.getId() + DELIMITER + client.getId();
+ }
+
+
+ public static AuthenticatedClientSessionModel getClientSession(KeycloakSession session, RealmModel realm, String sessionIndex) {
+ if (sessionIndex == null) {
+ return null;
+ }
+
+ String[] parts = PATTERN.split(sessionIndex);
+ if (parts.length != 2) {
+ return null;
+ }
+
+ UserSessionModel userSession = session.sessions().getUserSession(realm, parts[0]);
+ if (userSession == null) {
+ return null;
+ }
+
+ return userSession.getAuthenticatedClientSessions().get(parts[1]);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java
index 4bcbbc8..51ee9c8 100644
--- a/services/src/main/java/org/keycloak/services/ErrorPageException.java
+++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java
@@ -37,6 +37,8 @@ public class ErrorPageException extends WebApplicationException {
this.parameters = parameters;
}
+
+
@Override
public Response getResponse() {
return ErrorPage.error(session, errorMessage, parameters);
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 0ba5d77..6e7a917 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -537,7 +537,7 @@ public class AuthenticationManager {
.createInfoPage();
return response;
- // TODO:mposolda doublecheck if restart-cookie and authentication session are cleared in this flow
+ // Don't remove authentication session for now, to ensure that browser buttons (back/refresh) will still work fine.
}
RealmModel realm = authSession.getRealm();
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
index 04271f1..0297f13 100644
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
@@ -74,20 +74,17 @@ public class AuthenticationSessionManager {
boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
CookieHelper.addCookie(AUTH_SESSION_ID, authSessionId, cookiePath, null, null, -1, sslRequired, true);
- // TODO trace with isTraceEnabled
- log.infof("Set AUTH_SESSION_ID cookie with value %s", authSessionId);
+ log.debugf("Set AUTH_SESSION_ID cookie with value %s", authSessionId);
}
public String getAuthSessionCookie() {
String cookieVal = CookieHelper.getCookieValue(AUTH_SESSION_ID);
- if (log.isTraceEnabled()) {
- if (cookieVal != null) {
- log.tracef("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
- } else {
- log.tracef("Not found AUTH_SESSION_ID cookie");
- }
+ if (cookieVal != null) {
+ log.debugf("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
+ } else {
+ log.debugf("Not found AUTH_SESSION_ID cookie");
}
return cookieVal;
@@ -95,7 +92,7 @@ public class AuthenticationSessionManager {
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession, boolean expireRestartCookie) {
- log.infof("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
+ log.debugf("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
session.authenticationSessions().removeAuthenticationSession(realm, authSession);
// expire restart cookie
diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
index fec49c9..1bcfaf5 100644
--- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java
@@ -39,6 +39,7 @@ import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.representations.adapters.config.BaseRealmConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.sessions.AuthenticationSessionProvider;
import java.net.URI;
import java.util.Collections;
@@ -104,6 +105,11 @@ public class ClientManager {
sessionsPersister.onClientRemoved(realm, client);
}
+ AuthenticationSessionProvider authSessions = realmManager.getSession().authenticationSessions();
+ if (authSessions != null) {
+ authSessions.onClientRemoved(realm, client);
+ }
+
UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
if (serviceAccountUser != null) {
new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser);
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 ce7a8a1..59158e6 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -112,11 +112,6 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
CommonClientSessionModel clientSessionn = CodeGenerateUtil.getParser(sessionClass).parseSession(code, session, realm);;
CLIENT_SESSION clientSession = sessionClass.cast(clientSessionn);
- // TODO:mposolda Move this to somewhere else? Maybe LoginActionsService.sessionCodeChecks should be somehow even for non-action URLs...
- if (clientSession != null) {
- session.getContext().setClient(clientSession.getClient());
- }
-
return clientSession;
}
@@ -168,8 +163,12 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
public Set<RoleModel> getRequestedRoles() {
+ return getRequestedRoles(commonLoginSession, realm);
+ }
+
+ public static Set<RoleModel> getRequestedRoles(CommonClientSessionModel clientSession, RealmModel realm) {
Set<RoleModel> requestedRoles = new HashSet<>();
- for (String roleId : commonLoginSession.getRoles()) {
+ for (String roleId : clientSession.getRoles()) {
RoleModel role = realm.getRoleById(roleId);
if (role != null) {
requestedRoles.add(role);
diff --git a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
index eac0e64..a975aa5 100644
--- a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
+++ b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
@@ -23,7 +23,6 @@ import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.models.AuthenticatedClientSessionModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@@ -41,7 +40,6 @@ class CodeGenerateUtil {
private static final Map<Class<? extends CommonClientSessionModel>, ClientSessionParser> PARSERS = new HashMap<>();
static {
- PARSERS.put(ClientSessionModel.class, new ClientSessionModelParser());
PARSERS.put(AuthenticationSessionModel.class, new AuthenticationSessionModelParser());
PARSERS.put(AuthenticatedClientSessionModel.class, new AuthenticatedClientSessionModelParser());
}
@@ -78,54 +76,6 @@ class CodeGenerateUtil {
// IMPLEMENTATIONS
- // TODO: remove
- private static class ClientSessionModelParser implements ClientSessionParser<ClientSessionModel> {
-
-
- @Override
- public ClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm) {
- try {
- String[] parts = code.split("\\.");
- String id = parts[2];
- return session.sessions().getClientSession(realm, id);
- } catch (ArrayIndexOutOfBoundsException e) {
- return null;
- }
- }
-
- @Override
- public String generateCode(ClientSessionModel clientSession, String actionId) {
- StringBuilder sb = new StringBuilder();
- sb.append("cls.");
- sb.append(actionId);
- sb.append('.');
- sb.append(clientSession.getId());
-
- return sb.toString();
- }
-
- @Override
- public void removeExpiredSession(KeycloakSession session, ClientSessionModel clientSession) {
- session.sessions().removeClientSession(clientSession.getRealm(), clientSession);
- }
-
- @Override
- public String getNote(ClientSessionModel clientSession, String name) {
- return clientSession.getNote(name);
- }
-
- @Override
- public void removeNote(ClientSessionModel clientSession, String name) {
- clientSession.removeNote(name);
- }
-
- @Override
- public void setNote(ClientSessionModel clientSession, String name, String value) {
- clientSession.setNote(name, value);
- }
- }
-
-
private static class AuthenticationSessionModelParser implements ClientSessionParser<AuthenticationSessionModel> {
@Override
@@ -193,20 +143,6 @@ class CodeGenerateUtil {
sb.append('.');
sb.append(clientUUID);
- // TODO:mposolda codeChallengeMethod is not used anywhere. Not sure if it's bug of PKCE contribution. Doublecheck the PKCE specification what should be done regarding code
- // https://tools.ietf.org/html/rfc7636#section-4
- String codeChallenge = clientSession.getNote(OAuth2Constants.CODE_CHALLENGE);
- String codeChallengeMethod = clientSession.getNote(OAuth2Constants.CODE_CHALLENGE_METHOD);
- if (codeChallenge != null) {
- logger.debugf("PKCE received codeChallenge = %s", codeChallenge);
- if (codeChallengeMethod == null) {
- logger.debug("PKCE not received codeChallengeMethod, treating plain");
- codeChallengeMethod = OAuth2Constants.PKCE_METHOD_PLAIN;
- } else {
- logger.debugf("PKCE received codeChallengeMethod = %s", codeChallengeMethod);
- }
- }
-
return sb.toString();
}
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index 3306bcd..e94ff3c 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -47,6 +47,7 @@ import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.services.clientregistration.policy.DefaultClientRegistrationPolicies;
@@ -248,6 +249,11 @@ public class RealmManager {
sessionsPersister.onRealmRemoved(realm);
}
+ AuthenticationSessionProvider authSessions = session.authenticationSessions();
+ if (authSessions != null) {
+ authSessions.onRealmRemoved(realm);
+ }
+
// Refresh periodic sync tasks for configured storageProviders
List<UserStorageProviderModel> storageProviders = realm.getUserStorageProviders();
UserStorageSyncManager storageSync = new UserStorageSyncManager();
diff --git a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
index 4528575..f347d4c 100644
--- a/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/UserSessionManager.java
@@ -104,8 +104,8 @@ public class UserSessionManager {
user.getUsername(), client.getClientId());
}
- userSession.getAuthenticatedClientSessions().remove(client.getClientId());
- persister.removeClientSession(clientSession.getId(), true);
+ clientSession.setUserSession(null);
+ persister.removeClientSession(userSession.getId(), client.getId(), true);
checkOfflineUserSessionHasClientSessions(realm, user, userSession);
anyRemoved = true;
}
@@ -148,9 +148,8 @@ public class UserSessionManager {
clientSession.getId(), offlineUserSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
}
- AuthenticatedClientSessionModel offlineClientSession = kcSession.sessions().createOfflineClientSession(clientSession);
- offlineUserSession.getAuthenticatedClientSessions().put(clientSession.getClient().getId(), offlineClientSession);
- persister.createClientSession(offlineUserSession, clientSession, true);
+ kcSession.sessions().createOfflineClientSession(clientSession, offlineUserSession);
+ persister.createClientSession(clientSession, true);
}
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
index 89b0a33..a92729e 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java
@@ -25,8 +25,8 @@ import org.keycloak.common.Profile;
import org.keycloak.common.util.Time;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
+import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@@ -492,8 +492,11 @@ public class ClientResource {
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
// Update lastSessionRefresh with the timestamp from clientSession
- for (ClientSessionModel clientSession : userSession.getClientSessions()) {
- if (client.getId().equals(clientSession.getClient().getId())) {
+ for (Map.Entry<String, AuthenticatedClientSessionModel> csEntry : userSession.getAuthenticatedClientSessions().entrySet()) {
+ String clientUuid = csEntry.getKey();
+ AuthenticatedClientSessionModel clientSession = csEntry.getValue();
+
+ if (client.getId().equals(clientUuid)) {
rep.setLastAccess(Time.toMillis(clientSession.getTimestamp()));
break;
}
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 ded189d..1810ed0 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -75,6 +75,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
+import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.AuthenticationSessionModel;
@@ -210,6 +211,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
}
+ event.detail(Details.REDIRECT_URI, redirectUri);
+
if (nonce == null || hash == null) {
event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
@@ -239,7 +242,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return Response.status(302).location(builder.build()).build();
}
-
+ cookieResult.getSession();
+ event.session(cookieResult.getSession());
+ event.user(cookieResult.getUser());
+ event.detail(Details.USERNAME, cookieResult.getUser().getUsername());
AuthenticatedClientSessionModel clientSession = null;
for (AuthenticatedClientSessionModel cs : cookieResult.getSession().getAuthenticatedClientSessions().values()) {
@@ -264,7 +270,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
}
-
+ event.detail(Details.IDENTITY_PROVIDER, providerId);
ClientModel accountService = this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
if (!accountService.getId().equals(client.getId())) {
@@ -307,6 +313,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
authSession.setAuthNote(LINKING_IDENTITY_PROVIDER, cookieResult.getSession().getId() + clientId + providerId);
+ event.detail(Details.CODE_ID, userSession.getId());
event.success();
try {
@@ -508,6 +515,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
.detail(Details.REDIRECT_URI, authenticationSession.getRedirectUri())
+ .detail(Details.IDENTITY_PROVIDER, providerId)
.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
@@ -786,6 +794,9 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
authSession.setUserSessionNote(Details.IDENTITY_PROVIDER, providerId);
authSession.setUserSessionNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+ event.detail(Details.IDENTITY_PROVIDER, providerId)
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
+
if (isDebugEnabled()) {
logger.debugf("Performing local authentication for user [%s].", federatedUser);
}
@@ -961,43 +972,39 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
private ParsedCodeContext parseClientSessionCode(String code) {
- ClientSessionCode.ParseResult<AuthenticationSessionModel> parseResult = ClientSessionCode.parseResult(code, this.session, this.realmModel, AuthenticationSessionModel.class);
- ClientSessionCode<AuthenticationSessionModel> clientCode = parseResult.getCode();
-
- if (clientCode != null) {
- AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
-
- ClientModel client = authenticationSession.getClient();
-
- if (client != null) {
-
- logger.debugf("Got authorization code from client [%s].", client.getClientId());
- this.event.client(client);
- this.session.getContext().setClient(client);
-
- if (!clientCode.isValid(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
- logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", authenticationSession.getId(), authenticationSession.getAction());
-
- // Check if error happened during login or during linking from account management
- Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.STALE_CODE_ACCOUNT);
- Response staleCodeError = (accountManagementFailedLinking != null) ? accountManagementFailedLinking : redirectToErrorPage(Messages.STALE_CODE);
-
-
- return ParsedCodeContext.response(staleCodeError);
- }
+ if (code == null) {
+ logger.debugf("Invalid request. Authorization code was null");
+ Response staleCodeError = redirectToErrorPage(Messages.INVALID_REQUEST);
+ return ParsedCodeContext.response(staleCodeError);
+ }
+
+ SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, LoginActionsService.AUTHENTICATE_PATH);
+ checks.initialVerify();
+ if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+
+ AuthenticationSessionModel authSession = checks.getAuthenticationSession();
+ if (authSession != null) {
+ // Check if error happened during login or during linking from account management
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(authSession, Messages.STALE_CODE_ACCOUNT);
+ if (accountManagementFailedLinking != null) {
+ return ParsedCodeContext.response(accountManagementFailedLinking);
+ } else {
+ Response errorResponse = checks.getResponse();
- if (isDebugEnabled()) {
- logger.debugf("Authorization code is valid.");
+ // Remove "code" from browser history
+ errorResponse = BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, errorResponse, true);
+ return ParsedCodeContext.response(errorResponse);
}
-
- return ParsedCodeContext.clientSessionCode(clientCode);
+ } else {
+ return ParsedCodeContext.response(checks.getResponse());
+ }
+ } else {
+ if (isDebugEnabled()) {
+ logger.debugf("Authorization code is valid.");
}
- }
- // TODO:mposolda rather some different page? Maybe "PageExpired" page?
- logger.debugf("Authorization code is not valid. Code: %s", code);
- Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
- return ParsedCodeContext.response(staleCodeError);
+ return ParsedCodeContext.clientSessionCode(checks.getClientCode());
+ }
}
/**
@@ -1022,6 +1029,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
SamlService samlService = new SamlService(realmModel, event);
+ ResteasyProviderFactory.getInstance().injectProperties(samlService);
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
return ParsedCodeContext.clientSessionCode(new ClientSessionCode<>(session, this.realmModel, authSession));
@@ -1091,16 +1099,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return Response.status(302).location(UriBuilder.fromUri(authSession.getRedirectUri()).build()).build();
}
- private Response redirectToLoginPage(Throwable t, ClientSessionCode<AuthenticationSessionModel> clientCode) {
- String message = t.getMessage();
-
- if (message == null) {
- message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
- }
-
- fireErrorEvent(message);
- return browserAuthentication(clientCode.getClientSession(), message);
- }
protected Response browserAuthentication(AuthenticationSessionModel authSession, String errorMessage) {
this.event.event(EventType.LOGIN);
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 0338964..5dcb621 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -28,22 +28,20 @@ import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.TokenVerifier;
import org.keycloak.authentication.actiontoken.*;
+import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
import org.keycloak.authentication.authenticators.broker.util.PostBrokerLoginConstants;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
-import org.keycloak.authentication.requiredactions.VerifyEmail;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.VerificationException;
-import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.exceptions.TokenNotActiveException;
-import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
@@ -55,13 +53,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
-import org.keycloak.models.UserModel.RequiredAction;
-import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocol.Error;
-import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@@ -74,10 +69,9 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsServiceChecks.RestartFlowException;
import org.keycloak.services.util.CacheControlUtil;
-import org.keycloak.services.util.PageExpiredRedirect;
+import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
-import org.keycloak.sessions.CommonClientSessionModel;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -94,6 +88,8 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
import java.net.URI;
+import java.util.Map;
+
import javax.ws.rs.core.*;
import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS;
@@ -185,333 +181,14 @@ public class LoginActionsService {
}
private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
- SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
- res.initialVerify();
- return res;
- }
-
- private SessionCodeChecks checksForCodeRefreshNotAllowed(String code, String execution, String flowPath) {
- SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
- res.setAllowRefresh(false);
+ SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, flowPath);
res.initialVerify();
return res;
}
-
- private class SessionCodeChecks {
- ClientSessionCode<AuthenticationSessionModel> clientCode;
- Response response;
- ClientSessionCode.ParseResult<AuthenticationSessionModel> result;
- private boolean actionRequest;
- private boolean allowRefresh = true;
-
- private final String code;
- private final String execution;
- private final String flowPath;
-
- public SessionCodeChecks(String code, String execution, String flowPath) {
- this.code = code;
- this.execution = execution;
- this.flowPath = flowPath;
- }
-
- public AuthenticationSessionModel getAuthenticationSession() {
- return clientCode == null ? null : clientCode.getClientSession();
- }
-
- public boolean passed() {
- return response == null;
- }
-
- public boolean failed() {
- return response != null;
- }
-
- public boolean isAllowRefresh() {
- return allowRefresh;
- }
-
- public void setAllowRefresh(boolean allowRefresh) {
- this.allowRefresh = allowRefresh;
- }
-
-
- boolean verifyCode(String expectedAction, ClientSessionCode.ActionType actionType) {
- if (failed()) {
- return false;
- }
-
- if (!isActionActive(actionType)) {
- return false;
- }
-
- if (!clientCode.isValidAction(expectedAction)) {
- AuthenticationSessionModel authSession = getAuthenticationSession();
- if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
- // TODO:mposolda debug or trace
- logger.info("Incorrect flow '%s' . User authenticated already. Redirecting to requiredActions now.");
- response = redirectToRequiredActions(null);
- return false;
- } else {
- // TODO:mposolda could this happen? Doublecheck if we use other AuthenticationSession.Action besides AUTHENTICATE and REQUIRED_ACTIONS
- logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
- response = ErrorPage.error(session, Messages.EXPIRED_CODE);
- return false;
- }
- }
-
- return true;
- }
-
-
- private boolean isActionActive(ClientSessionCode.ActionType actionType) {
- if (!clientCode.isActionActive(actionType)) {
- event.client(getAuthenticationSession().getClient());
- event.clone().error(Errors.EXPIRED_CODE);
-
- AuthenticationSessionModel authSession = getAuthenticationSession();
- AuthenticationProcessor.resetFlow(authSession, AUTHENTICATE_PATH);
- response = processAuthentication(false, null, authSession, Messages.LOGIN_TIMEOUT);
- return false;
- }
- return true;
- }
-
-
- private AuthenticationSessionModel initialVerifyAuthSession() {
- // Basic realm checks
- if (!checkSsl()) {
- event.error(Errors.SSL_REQUIRED);
- response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
- return null;
- }
- if (!realm.isEnabled()) {
- event.error(Errors.REALM_DISABLED);
- response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
- return null;
- }
-
- // object retrieve
- AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, AuthenticationSessionModel.class);
- if (authSession != null) {
- return authSession;
- }
-
- // See if we are already authenticated and userSession with same ID exists.
- String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
- if (sessionId != null) {
- UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
- if (userSession != null) {
-
- LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
- .setSuccess(Messages.ALREADY_LOGGED_IN);
-
- ClientModel client = null;
- String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
- if (lastClientUuid != null) {
- client = realm.getClientById(lastClientUuid);
- }
-
- if (client != null) {
- session.getContext().setClient(client);
- } else {
- loginForm.setAttribute("skipLink", true);
- }
-
- response = loginForm.createInfoPage();
- return null;
- }
- }
-
- // Otherwise just try to restart from the cookie
- response = restartAuthenticationSessionFromCookie();
- return null;
- }
-
-
- private boolean initialVerify() {
- // Basic realm checks and authenticationSession retrieve
- AuthenticationSessionModel authSession = initialVerifyAuthSession();
- if (authSession == null) {
- return false;
- }
-
- // Check cached response from previous action request
- response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
- if (response != null) {
- return false;
- }
-
- // Client checks
- event.detail(Details.CODE_ID, authSession.getId());
- ClientModel client = authSession.getClient();
- if (client == null) {
- event.error(Errors.CLIENT_NOT_FOUND);
- response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
- clientCode.removeExpiredClientSession();
- return false;
- }
- if (!client.isEnabled()) {
- event.error(Errors.CLIENT_DISABLED);
- response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
- clientCode.removeExpiredClientSession();
- return false;
- }
- session.getContext().setClient(client);
-
-
- // Check if it's action or not
- if (code == null) {
- String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
- String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
-
- // Check if we transitted between flows (eg. clicking "register" on login screen)
- if (execution==null && !flowPath.equals(lastFlow)) {
- logger.infof("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
-
- if (lastFlow == null || isFlowTransitionAllowed(flowPath, lastFlow)) {
- authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
- authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
- lastExecFromSession = null;
- }
- }
-
- if (ObjectUtil.isEqualOrBothNull(execution, lastExecFromSession)) {
- // Allow refresh of previous page
- clientCode = new ClientSessionCode<>(session, realm, authSession);
- actionRequest = false;
- return true;
- } else {
- response = showPageExpired(authSession);
- return false;
- }
- } else {
- result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
- clientCode = result.getCode();
- if (clientCode == null) {
-
- // In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
- if (allowRefresh && ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
- String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
- URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
-
- logger.infof("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
- authSession.setAuthNote(FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
- response = Response.status(Response.Status.FOUND).location(redirectUri).build();
- } else {
- response = showPageExpired(authSession);
- }
- return false;
- }
-
-
- actionRequest = execution != null;
- authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
- return true;
- }
- }
-
-
- public boolean verifyRequiredAction(String executedAction) {
- if (failed()) {
- return false;
- }
-
- if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
- // TODO:mposolda debug or trace
- logger.infof("Expected required action, but session action is '%s' . Showing expired page now.", getAuthenticationSession().getAction());
- event.client(getAuthenticationSession().getClient());
- event.error(Errors.INVALID_CODE);
-
- response = showPageExpired(getAuthenticationSession());
-
- return false;
- }
-
- if (!isActionActive(ClientSessionCode.ActionType.USER)) return false;
-
- final AuthenticationSessionModel authSession = getAuthenticationSession();
-
- if (actionRequest) {
- String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
- if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
- logger.debug("required action doesn't match current required action");
- response = redirectToRequiredActions(currentRequiredAction);
- return false;
- }
- }
- return true;
- }
- }
-
-
- public static boolean isFlowTransitionAllowed(String currentFlow, String previousFlow) {
- if (currentFlow.equals(AUTHENTICATE_PATH) && (previousFlow.equals(REGISTRATION_PATH) || previousFlow.equals(RESET_CREDENTIALS_PATH))) {
- return true;
- }
-
- if (currentFlow.equals(REGISTRATION_PATH) && (previousFlow.equals(AUTHENTICATE_PATH))) {
- return true;
- }
-
- if (currentFlow.equals(RESET_CREDENTIALS_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(FIRST_BROKER_LOGIN_PATH))) {
- return true;
- }
-
- if (currentFlow.equals(FIRST_BROKER_LOGIN_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(POST_BROKER_LOGIN_PATH))) {
- return true;
- }
-
- if (currentFlow.equals(POST_BROKER_LOGIN_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(FIRST_BROKER_LOGIN_PATH))) {
- return true;
- }
-
- return false;
- }
-
-
- protected Response restartAuthenticationSessionFromCookie() {
- logger.info("Authentication session not found. Trying to restart from cookie.");
- AuthenticationSessionModel authSession = null;
- try {
- authSession = RestartLoginCookie.restartSession(session, realm);
- } catch (Exception e) {
- ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
- }
-
- if (authSession != null) {
-
- event.clone();
- event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
- event.error(Errors.EXPIRED_CODE);
-
- String warningMessage = Messages.LOGIN_TIMEOUT;
- authSession.setAuthNote(FORWARDED_ERROR_MESSAGE_NOTE, warningMessage);
-
- String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
- if (flowPath == null) {
- flowPath = AUTHENTICATE_PATH;
- }
-
- URI redirectUri = getLastExecutionUrl(flowPath, null);
- logger.infof("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
- return Response.status(Response.Status.FOUND).location(redirectUri).build();
- } else {
- event.error(Errors.INVALID_CODE);
- return ErrorPage.error(session, Messages.INVALID_CODE);
- }
- }
-
-
- protected Response showPageExpired(AuthenticationSessionModel authSession) {
- return new PageExpiredRedirect(session, realm, uriInfo)
- .showPageExpired(authSession);
- }
-
-
protected URI getLastExecutionUrl(String flowPath, String executionId) {
- return new PageExpiredRedirect(session, realm, uriInfo)
+ return new AuthenticationFlowURLHelper(session, realm, uriInfo)
.getLastExecutionUrl(flowPath, executionId);
}
@@ -525,11 +202,11 @@ public class LoginActionsService {
@GET
public Response restartSession() {
event.event(EventType.RESTART_AUTHENTICATION);
- SessionCodeChecks checks = new SessionCodeChecks(null, null, null);
+ SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, null);
AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
if (authSession == null) {
- return checks.response;
+ return checks.getResponse();
}
String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
@@ -539,11 +216,8 @@ public class LoginActionsService {
AuthenticationProcessor.resetFlow(authSession, flowPath);
- // TODO:mposolda Check it's better to put it to AuthenticationProcessor.resetFlow (with consider of brokering)
- authSession.setAction(CommonClientSessionModel.Action.AUTHENTICATE.name());
-
URI redirectUri = getLastExecutionUrl(flowPath, null);
- logger.infof("Flow restart requested. Redirecting to %s", redirectUri);
+ logger.debugf("Flow restart requested. Redirecting to %s", redirectUri);
return Response.status(Response.Status.FOUND).location(redirectUri).build();
}
@@ -561,12 +235,12 @@ public class LoginActionsService {
event.event(EventType.LOGIN);
SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
- if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
- return checks.response;
+ if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ return checks.getResponse();
}
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
- boolean actionRequest = checks.actionRequest;
+ boolean actionRequest = checks.isActionRequest();
return processAuthentication(actionRequest, execution, authSession, null);
}
@@ -605,6 +279,9 @@ public class LoginActionsService {
} else {
response = processor.authenticate();
}
+ } catch (WebApplicationException e) {
+ response = e.getResponse();
+ authSession = processor.getAuthenticationSession();
} catch (Exception e) {
response = processor.handleBrowserException(e);
authSession = processor.getAuthenticationSession(); // Could be changed (eg. Forked flow)
@@ -696,30 +373,26 @@ public class LoginActionsService {
*/
protected Response resetCredentials(String code, String execution) {
SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
- if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
- return checks.response;
+ if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
+ return checks.getResponse();
}
final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
if (!realm.isResetPasswordAllowed()) {
- if (authSession != null) {
- event.client(authSession.getClient());
- }
event.error(Errors.NOT_ALLOWED);
return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
}
- return processResetCredentials(checks.actionRequest, execution, authSession);
+ return processResetCredentials(checks.isActionRequest(), execution, authSession);
}
/**
* Handles a given token using the given token handler. If there is any {@link VerificationException} thrown
* in the handler, it is handled automatically here to reduce boilerplate code.
*
- * @param tokenString Original token string
- * @param eventError
- * @param defaultErrorMessage
+ * @param key
+ * @param execution
* @return
*/
@Path("action-token")
@@ -888,30 +561,7 @@ public class LoginActionsService {
}
protected Response processResetCredentials(boolean actionRequest, String execution, AuthenticationSessionModel authSession) {
- AuthenticationProcessor authProcessor = new AuthenticationProcessor() {
-
- @Override
- protected Response authenticationComplete() {
- boolean firstBrokerLoginInProgress = (authenticationSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
- if (firstBrokerLoginInProgress) {
-
- UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
- if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
- return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, authenticationSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
- }
-
- SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
- authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
-
- logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login with identity provider '%s'.",
- linkingUser.getUsername(), serializedCtx.getIdentityProviderId());
-
- return redirectToAfterBrokerLoginEndpoint(authSession, true);
- } else {
- return super.authenticationComplete();
- }
- }
- };
+ AuthenticationProcessor authProcessor = new ResetCredentialsActionTokenHandler.ResetCredsAuthenticationProcessor();
return processFlow(actionRequest, execution, authSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), null, authProcessor);
}
@@ -958,19 +608,15 @@ public class LoginActionsService {
}
SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
- if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
- return checks.response;
+ if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ return checks.getResponse();
}
- ClientSessionCode<AuthenticationSessionModel> clientSessionCode = checks.clientCode;
- AuthenticationSessionModel clientSession = clientSessionCode.getClientSession();
+ AuthenticationSessionModel authSession = checks.getAuthenticationSession();
- // TODO:mposolda any consequences to do this for POST request too?
- if (!isPostRequest) {
- AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
- }
+ AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
- return processRegistration(checks.actionRequest, execution, clientSession, null);
+ return processRegistration(checks.isActionRequest(), execution, authSession, null);
}
@@ -1010,8 +656,8 @@ public class LoginActionsService {
event.event(eventType);
SessionCodeChecks checks = checksForCode(code, execution, flowPath);
- if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
- return checks.response;
+ if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ return checks.getResponse();
}
event.detail(Details.CODE_ID, code);
final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
@@ -1056,10 +702,14 @@ public class LoginActionsService {
};
- return processFlow(checks.actionRequest, execution, authSession, flowPath, brokerLoginFlow, null, processor);
+ return processFlow(checks.isActionRequest(), execution, authSession, flowPath, brokerLoginFlow, null, processor);
}
private Response redirectToAfterBrokerLoginEndpoint(AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
+ return redirectToAfterBrokerLoginEndpoint(session, realm, uriInfo, authSession, firstBrokerLogin);
+ }
+
+ public static Response redirectToAfterBrokerLoginEndpoint(KeycloakSession session, RealmModel realm, UriInfo uriInfo, AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
authSession.setTimestamp(Time.currentTime());
@@ -1085,11 +735,10 @@ public class LoginActionsService {
String code = formData.getFirst("code");
SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
- return checks.response;
+ return checks.getResponse();
}
- ClientSessionCode<AuthenticationSessionModel> accessCode = checks.clientCode;
- AuthenticationSessionModel authSession = accessCode.getClientSession();
+ AuthenticationSessionModel authSession = checks.getAuthenticationSession();
initLoginEvent(authSession);
@@ -1113,10 +762,10 @@ public class LoginActionsService {
grantedConsent = new UserConsentModel(client);
session.users().addConsent(realm, user.getId(), grantedConsent);
}
- for (RoleModel role : accessCode.getRequestedRoles()) {
+ for (RoleModel role : ClientSessionCode.getRequestedRoles(authSession, realm)) {
grantedConsent.addGrantedRole(role);
}
- for (ProtocolMapperModel protocolMapper : accessCode.getRequestedProtocolMappers()) {
+ for (ProtocolMapperModel protocolMapper : ClientSessionCode.getRequestedProtocolMappers(authSession.getProtocolMappers(), client)) {
if (protocolMapper.isConsentRequired() && protocolMapper.getConsentText() != null) {
grantedConsent.addGrantedProtocolMapper(protocolMapper);
}
@@ -1130,23 +779,6 @@ public class LoginActionsService {
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSession.getUserSession(), clientSession, request, uriInfo, clientConnection, event, authSession.getProtocol());
}
- @Path("email-verification")
- @GET
- public Response emailVerification(@QueryParam("code") String code, @QueryParam("execution") String execution) {
- event.event(EventType.SEND_VERIFY_EMAIL);
-
- SessionCodeChecks checks = checksForCodeRefreshNotAllowed(code, execution, REQUIRED_ACTION);
- if (!checks.verifyCode(ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
- return checks.response;
- }
- ClientSessionCode accessCode = checks.clientCode;
- AuthenticationSessionModel authSession = checks.getAuthenticationSession();
- initLoginEvent(authSession);
-
- event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, authSession.getAuthenticatedUser().getEmail()).success();
-
- return VerifyEmail.sendVerifyEmail(session, accessCode.getCode(), authSession.getAuthenticatedUser(), authSession);
- }
/**
* Initiated by admin, not the user on login
@@ -1210,11 +842,12 @@ public class LoginActionsService {
}
event.detail(Details.REMEMBER_ME, rememberMe);
- // TODO:mposolda Fix if this is called at firstBroker or postBroker login
- /*
- .detail(Details.IDENTITY_PROVIDER, userSession.getNote(Details.IDENTITY_PROVIDER))
- .detail(Details.IDENTITY_PROVIDER_USERNAME, userSession.getNote(Details.IDENTITY_PROVIDER_USERNAME));
- */
+ Map<String, String> userSessionNotes = authSession.getUserSessionNotes();
+ String identityProvider = userSessionNotes.get(Details.IDENTITY_PROVIDER);
+ if (identityProvider != null) {
+ event.detail(Details.IDENTITY_PROVIDER, identityProvider)
+ .detail(Details.IDENTITY_PROVIDER_USERNAME, userSessionNotes.get(Details.IDENTITY_PROVIDER_USERNAME));
+ }
}
@Path(REQUIRED_ACTION)
@@ -1236,11 +869,11 @@ public class LoginActionsService {
SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
if (!checks.verifyRequiredAction(action)) {
- return checks.response;
+ return checks.getResponse();
}
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
- if (!checks.actionRequest) {
+ if (!checks.isActionRequest()) {
initLoginEvent(authSession);
event.event(EventType.CUSTOM_REQUIRED_ACTION);
return AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, uriInfo, event);
@@ -1268,7 +901,9 @@ public class LoginActionsService {
Response response;
provider.processAction(context);
- authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
+ if (action != null) {
+ authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
+ }
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
event.clone().success();
@@ -1298,15 +933,4 @@ public class LoginActionsService {
return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, true);
}
- private Response redirectToRequiredActions(String action) {
- UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
- .path(LoginActionsService.REQUIRED_ACTION);
-
- if (action != null) {
- uriBuilder.queryParam("execution", action);
- }
- URI redirect = uriBuilder.build(realm.getName());
- return Response.status(302).location(redirect).build();
- }
-
}
diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
new file mode 100644
index 0000000..978ad6f
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.services.resources;
+
+import java.net.URI;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.logging.Logger;
+import org.keycloak.authentication.AuthenticationProcessor;
+import org.keycloak.common.ClientConnection;
+import org.keycloak.common.util.ObjectUtil;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.protocol.AuthorizationEndpointBase;
+import org.keycloak.protocol.RestartLoginCookie;
+import org.keycloak.services.ErrorPage;
+import org.keycloak.services.ServicesLogger;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
+import org.keycloak.services.managers.ClientSessionCode;
+import org.keycloak.services.messages.Messages;
+import org.keycloak.services.util.BrowserHistoryHelper;
+import org.keycloak.services.util.AuthenticationFlowURLHelper;
+import org.keycloak.sessions.AuthenticationSessionModel;
+
+
+public class SessionCodeChecks {
+
+ private static final Logger logger = Logger.getLogger(SessionCodeChecks.class);
+
+ private AuthenticationSessionModel authSession;
+ private ClientSessionCode<AuthenticationSessionModel> clientCode;
+ private Response response;
+ private boolean actionRequest;
+
+ private final RealmModel realm;
+ private final UriInfo uriInfo;
+ private final ClientConnection clientConnection;
+ private final KeycloakSession session;
+ private final EventBuilder event;
+
+ private final String code;
+ private final String execution;
+ private final String flowPath;
+
+
+ public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String flowPath) {
+ this.realm = realm;
+ this.uriInfo = uriInfo;
+ this.clientConnection = clientConnection;
+ this.session = session;
+ this.event = event;
+
+ this.code = code;
+ this.execution = execution;
+ this.flowPath = flowPath;
+ }
+
+
+ public AuthenticationSessionModel getAuthenticationSession() {
+ return authSession;
+ }
+
+
+ private boolean failed() {
+ return response != null;
+ }
+
+
+ public Response getResponse() {
+ return response;
+ }
+
+
+ public ClientSessionCode<AuthenticationSessionModel> getClientCode() {
+ return clientCode;
+ }
+
+ public boolean isActionRequest() {
+ return actionRequest;
+ }
+
+
+ private boolean checkSsl() {
+ if (uriInfo.getBaseUri().getScheme().equals("https")) {
+ return true;
+ } else {
+ return !realm.getSslRequired().isRequired(clientConnection);
+ }
+ }
+
+
+ public AuthenticationSessionModel initialVerifyAuthSession() {
+ // Basic realm checks
+ if (!checkSsl()) {
+ event.error(Errors.SSL_REQUIRED);
+ response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
+ return null;
+ }
+ if (!realm.isEnabled()) {
+ event.error(Errors.REALM_DISABLED);
+ response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
+ return null;
+ }
+
+ // object retrieve
+ AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, AuthenticationSessionModel.class);
+ if (authSession != null) {
+ return authSession;
+ }
+
+ // See if we are already authenticated and userSession with same ID exists.
+ String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
+ if (sessionId != null) {
+ UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
+ if (userSession != null) {
+
+ LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
+ .setSuccess(Messages.ALREADY_LOGGED_IN);
+
+ ClientModel client = null;
+ String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
+ if (lastClientUuid != null) {
+ client = realm.getClientById(lastClientUuid);
+ }
+
+ if (client != null) {
+ session.getContext().setClient(client);
+ } else {
+ loginForm.setAttribute("skipLink", true);
+ }
+
+ response = loginForm.createInfoPage();
+ return null;
+ }
+ }
+
+ // Otherwise just try to restart from the cookie
+ response = restartAuthenticationSessionFromCookie();
+ return null;
+ }
+
+
+ public boolean initialVerify() {
+ // Basic realm checks and authenticationSession retrieve
+ authSession = initialVerifyAuthSession();
+ if (authSession == null) {
+ return false;
+ }
+
+ // Check cached response from previous action request
+ response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
+ if (response != null) {
+ return false;
+ }
+
+ // Client checks
+ event.detail(Details.CODE_ID, authSession.getId());
+ ClientModel client = authSession.getClient();
+ if (client == null) {
+ event.error(Errors.CLIENT_NOT_FOUND);
+ response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
+ clientCode.removeExpiredClientSession();
+ return false;
+ }
+
+ event.client(client);
+ session.getContext().setClient(client);
+
+ if (!client.isEnabled()) {
+ event.error(Errors.CLIENT_DISABLED);
+ response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
+ clientCode.removeExpiredClientSession();
+ return false;
+ }
+
+
+ // Check if it's action or not
+ if (code == null) {
+ String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+ String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
+
+ // Check if we transitted between flows (eg. clicking "register" on login screen)
+ if (execution==null && !flowPath.equals(lastFlow)) {
+ logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
+
+ // Don't allow moving to different flow if I am on requiredActions already
+ if (ClientSessionModel.Action.AUTHENTICATE.name().equals(authSession.getAction())) {
+ authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
+ authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+ lastExecFromSession = null;
+ }
+ }
+
+ if (ObjectUtil.isEqualOrBothNull(execution, lastExecFromSession)) {
+ // Allow refresh of previous page
+ clientCode = new ClientSessionCode<>(session, realm, authSession);
+ actionRequest = false;
+ return true;
+ } else {
+ response = showPageExpired(authSession);
+ return false;
+ }
+ } else {
+ ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
+ clientCode = result.getCode();
+ if (clientCode == null) {
+
+ // In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
+ if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
+ String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
+ URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
+
+ logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
+ authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
+ response = Response.status(Response.Status.FOUND).location(redirectUri).build();
+ } else {
+ response = showPageExpired(authSession);
+ }
+ return false;
+ }
+
+
+ actionRequest = true;
+ if (execution != null) {
+ authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
+ }
+ return true;
+ }
+ }
+
+
+ public boolean verifyActiveAndValidAction(String expectedAction, ClientSessionCode.ActionType actionType) {
+ if (failed()) {
+ return false;
+ }
+
+ if (!isActionActive(actionType)) {
+ return false;
+ }
+
+ if (!clientCode.isValidAction(expectedAction)) {
+ AuthenticationSessionModel authSession = getAuthenticationSession();
+ if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
+ logger.debugf("Incorrect action '%s' . User authenticated already.", authSession.getAction());
+ response = showPageExpired(authSession);
+ return false;
+ } else {
+ logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
+ response = ErrorPage.error(session, Messages.EXPIRED_CODE);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ private boolean isActionActive(ClientSessionCode.ActionType actionType) {
+ if (!clientCode.isActionActive(actionType)) {
+ event.clone().error(Errors.EXPIRED_CODE);
+
+ AuthenticationProcessor.resetFlow(authSession, LoginActionsService.AUTHENTICATE_PATH);
+
+ authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.LOGIN_TIMEOUT);
+
+ URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null);
+ logger.debugf("Flow restart after timeout. Redirecting to %s", redirectUri);
+ response = Response.status(Response.Status.FOUND).location(redirectUri).build();
+ return false;
+ }
+ return true;
+ }
+
+
+ public boolean verifyRequiredAction(String executedAction) {
+ if (failed()) {
+ return false;
+ }
+
+ if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
+ logger.debugf("Expected required action, but session action is '%s' . Showing expired page now.", authSession.getAction());
+ event.error(Errors.INVALID_CODE);
+
+ response = showPageExpired(authSession);
+
+ return false;
+ }
+
+ if (!isActionActive(ClientSessionCode.ActionType.USER)) {
+ return false;
+ }
+
+ if (actionRequest) {
+ String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
+ if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
+ logger.debug("required action doesn't match current required action");
+ response = redirectToRequiredActions(currentRequiredAction);
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ private Response restartAuthenticationSessionFromCookie() {
+ logger.debug("Authentication session not found. Trying to restart from cookie.");
+ AuthenticationSessionModel authSession = null;
+ try {
+ authSession = RestartLoginCookie.restartSession(session, realm);
+ } catch (Exception e) {
+ ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
+ }
+
+ if (authSession != null) {
+
+ event.clone();
+ event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
+ event.error(Errors.EXPIRED_CODE);
+
+ String warningMessage = Messages.LOGIN_TIMEOUT;
+ authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, warningMessage);
+
+ String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
+ if (flowPath == null) {
+ flowPath = LoginActionsService.AUTHENTICATE_PATH;
+ }
+
+ URI redirectUri = getLastExecutionUrl(flowPath, null);
+ logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
+ return Response.status(Response.Status.FOUND).location(redirectUri).build();
+ } else {
+ // Finally need to show error as all the fallbacks failed
+ event.error(Errors.INVALID_CODE);
+ return ErrorPage.error(session, Messages.INVALID_CODE);
+ }
+ }
+
+
+ private Response redirectToRequiredActions(String action) {
+ UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
+ .path(LoginActionsService.REQUIRED_ACTION);
+
+ if (action != null) {
+ uriBuilder.queryParam("execution", action);
+ }
+ URI redirect = uriBuilder.build(realm.getName());
+ return Response.status(302).location(redirect).build();
+ }
+
+
+ private URI getLastExecutionUrl(String flowPath, String executionId) {
+ return new AuthenticationFlowURLHelper(session, realm, uriInfo)
+ .getLastExecutionUrl(flowPath, executionId);
+ }
+
+
+ private Response showPageExpired(AuthenticationSessionModel authSession) {
+ return new AuthenticationFlowURLHelper(session, realm, uriInfo)
+ .showPageExpired(authSession);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
index 5935eb0..5315be4 100755
--- a/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
+++ b/services/src/main/java/org/keycloak/services/scheduled/ClearExpiredUserSessions.java
@@ -32,6 +32,7 @@ public class ClearExpiredUserSessions implements ScheduledTask {
UserSessionProvider sessions = session.sessions();
for (RealmModel realm : session.realms().getRealms()) {
sessions.removeExpired(realm);
+ session.authenticationSessions().removeExpired(realm);
}
}
diff --git a/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java b/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
index 89cda2c..890d21c 100644
--- a/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
+++ b/services/src/main/java/org/keycloak/services/util/BrowserHistoryHelper.java
@@ -77,7 +77,7 @@ public abstract class BrowserHistoryHelper {
if (entity instanceof String) {
String responseString = (String) entity;
- URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
+ URI lastExecutionURL = new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
// Inject javascript for history "replaceState"
String responseWithJavascript = responseWithJavascript(responseString, lastExecutionURL.toString());
@@ -124,6 +124,7 @@ public abstract class BrowserHistoryHelper {
}
+ // This impl is limited ATM. Saved request doesn't save response HTTP headers, so they may not be fully restored..
private static class RedirectAfterPostHelper extends BrowserHistoryHelper {
private static final String CACHED_RESPONSE = "cached.response";
@@ -141,10 +142,11 @@ public abstract class BrowserHistoryHelper {
String responseString = (String) entity;
authSession.setAuthNote(CACHED_RESPONSE, responseString);
- URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
+ URI lastExecutionURL = new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
- // TODO:mposolda trace
- logger.infof("Saved response challenge and redirect to %s", lastExecutionURL);
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Saved response challenge and redirect to %s", lastExecutionURL);
+ }
return Response.status(302).location(lastExecutionURL).build();
}
@@ -160,11 +162,12 @@ public abstract class BrowserHistoryHelper {
if (savedResponse != null) {
authSession.removeAuthNote(CACHED_RESPONSE);
- // TODO:mposolda trace
- logger.infof("Restored previously saved request");
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Restored previously saved request");
+ }
Response.ResponseBuilder builder = Response.status(200).type(MediaType.TEXT_HTML_UTF_8).entity(savedResponse);
- BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO:mposolda cRather all the headers from the saved response should be added here.
+ BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO rather all the headers from the saved response should be added here.
return builder.build();
}
diff --git a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
index a8c42b1..00be27c 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -119,6 +119,8 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
return callback.cancelled(state);
}
+ Response errorResponse = null;
+
try {
Twitter twitter = new TwitterFactory().getInstance();
@@ -155,17 +157,21 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
return callback.authenticated(identity);
} catch (WebApplicationException e) {
+ sendErrorEvent();
return e.getResponse();
} catch (Exception e) {
logger.error("Could get user profile from twitter.", e);
+ sendErrorEvent();
+ return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
}
+ }
+
+ private void sendErrorEvent() {
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGIN);
event.error("twitter_login_failed");
- return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
}
-
}
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
index 474417c..f2dffaf 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java
@@ -33,12 +33,16 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
import org.keycloak.testsuite.pages.IdpLinkEmailPage;
+import org.keycloak.testsuite.pages.InfoPage;
+import org.keycloak.testsuite.pages.LoginExpiredPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
+import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
+import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import javax.mail.internet.MimeMessage;
@@ -71,6 +75,9 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
@WebResource
protected LoginPasswordUpdatePage passwordUpdatePage;
+ @WebResource
+ protected LoginExpiredPage loginExpiredPage;
+
/**
@@ -361,6 +368,101 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
/**
+ * Variation of previous test, which uses browser buttons (back, refresh etc)
+ */
+ @Test
+ public void testLinkAccountByReauthenticationWithPassword_browserButtons() throws Exception {
+ // Remove smtp config. The reauthentication by username+password screen will be automatically used then
+ final Map<String, String> smtpConfig = new HashMap<>();
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+ smtpConfig.putAll(realmWithBroker.getSmtpConfig());
+ realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
+ }
+
+ }, APP_REALM_ID);
+
+
+ // Use invalid username for the first time
+ loginIDP("foo");
+ assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("pedroigor", "password");
+
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+
+ // Click browser 'back' and then 'forward' and then continue
+ driver.navigate().back();
+ Assert.assertTrue(driver.getPageSource().contains("You are already logged in."));
+ driver.navigate().forward();
+ this.loginExpiredPage.assertCurrent();
+ this.loginExpiredPage.clickLoginContinueLink();
+ this.idpConfirmLinkPage.assertCurrent();
+
+ // Click browser 'back' on review profile page
+ this.idpConfirmLinkPage.clickReviewProfile();
+ this.updateProfilePage.assertCurrent();
+ driver.navigate().back();
+ this.loginExpiredPage.assertCurrent();
+ this.loginExpiredPage.clickLoginContinueLink();
+ this.updateProfilePage.assertCurrent();
+ this.updateProfilePage.update("Pedro", "Igor", "psilva@redhat.com");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ // Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ Assert.assertEquals("pedroigor", this.loginPage.getUsername());
+ Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
+
+ Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getInfoMessage());
+
+ try {
+ this.loginPage.findSocialButton(getProviderId());
+ Assert.fail("Not expected to see social button with " + getProviderId());
+ } catch (NoSuchElementException expected) {
+ }
+
+ try {
+ this.loginPage.clickRegister();
+ Assert.fail("Not expected to see register link");
+ } catch (NoSuchElementException expected) {
+ }
+
+ // Use bad password first
+ this.loginPage.login("password1");
+ Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
+
+ // Click browser 'back' and then continue
+ this.driver.navigate().back();
+ this.loginExpiredPage.assertCurrent();
+ this.loginExpiredPage.clickLoginContinueLink();
+
+ // Use correct password now
+ this.loginPage.login("password");
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+
+ // Restore smtp config
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ realmWithBroker.setSmtpConfig(smtpConfig);
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
+ /**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
* and additionally he goes through "forget password"
*/
@@ -418,6 +520,93 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
}
+ /**
+ * Same like above, but "forget password" link is opened in different browser
+ */
+ @Test
+ public void testLinkAccountByReauthentication_forgetPassword_differentBrowser() throws Throwable {
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+ IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
+
+ setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
+ }
+
+ }, APP_REALM_ID);
+
+ loginIDP("pedroigor");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ // Click "Forget password" on login page. Email sent directly because username is known
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ this.loginPage.resetPassword();
+
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
+
+ // Click on link from email
+ Assert.assertEquals(1, greenMail.getReceivedMessages().length);
+ MimeMessage message = greenMail.getReceivedMessages()[0];
+ String linkFromMail = getVerificationEmailLink(message);
+
+ // Simulate 2nd browser
+ WebRule webRule2 = new WebRule(this);
+ webRule2.before();
+
+ WebDriver driver2 = webRule2.getDriver();
+ LoginPasswordUpdatePage passwordUpdatePage2 = webRule2.getPage(LoginPasswordUpdatePage.class);
+ InfoPage infoPage2 = webRule2.getPage(InfoPage.class);
+
+ driver2.navigate().to(linkFromMail.trim());
+
+ // Need to update password now
+ passwordUpdatePage2.assertCurrent();
+ passwordUpdatePage2.changePassword("password", "password");
+
+ // authenticated, but not redirected to app. Just seeing info page.
+ infoPage2.assertCurrent();
+ Assert.assertEquals("Your account has been updated.", infoPage2.getInfo());
+
+ // User is not yet linked with identity provider. He needs to authenticate again in 1st browser
+ RealmModel realmWithBroker = getRealm();
+ Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(this.session.users().getUserByUsername("pedroigor", realmWithBroker), realmWithBroker);
+ assertEquals(0, federatedIdentities.size());
+
+ // Continue with 1st browser
+ loginIDP("pedroigor");
+
+ this.idpConfirmLinkPage.assertCurrent();
+ Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
+ this.idpConfirmLinkPage.clickLinkAccount();
+
+ Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
+ this.loginPage.login("password");
+
+ // authenticated and redirected to app. User is linked with identity provider
+ assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
+
+ // Revert everything
+ webRule2.after();
+
+ brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
+ setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
+ IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
+
+ }
+
+ }, APP_REALM_ID);
+ }
+
+
protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) {
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
UserModel federatedUser = getFederatedUser();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
index 14c98dd..781714a 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -74,8 +74,6 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
brokerServerRule.stopSession(session, true);
- Thread.sleep(10000000);
-
driver.navigate().to("http://localhost:8081/test-app");
loginPage.clickSocial(getProviderId());
loginPage.login("test-user", "password");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
index b576cbf..b2ecd41 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
@@ -117,11 +117,9 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
grantPage.assertCurrent();
grantPage.cancel();
- // Assert error page with backToApplication link displayed
- errorPage.assertCurrent();
- errorPage.clickBackToApplication();
-
- assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+ // Assert login page with "You took too long to login..." message
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/login-actions/authenticate"));
+ Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
} finally {
Time.setOffset(0);
@@ -152,14 +150,9 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
grantPage.assertCurrent();
grantPage.cancel();
- // Assert error page without backToApplication link (clientSession expired and was removed on the server)
- errorPage.assertCurrent();
- try {
- errorPage.clickBackToApplication();
- fail("Not expected to have link backToApplication available");
- } catch (NoSuchElementException nsee) {
- // Expected;
- }
+ // Assert login page with "You took too long to login..." message
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/login-actions/authenticate"));
+ Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
} finally {
Time.setOffset(0);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
index 8ee397a..db08e81 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
@@ -22,15 +22,21 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
+import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
+import org.keycloak.services.managers.ClientManager;
+import org.keycloak.services.managers.RealmManager;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.testsuite.rule.KeycloakRule;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -53,7 +59,6 @@ public class AuthenticationSessionProviderTest {
@After
public void after() {
resetSession();
- session.sessions().removeUserSessions(realm);
UserModel user1 = session.users().getUserByUsername("user1", realm);
UserModel user2 = session.users().getUserByUsername("user2", realm);
@@ -87,7 +92,7 @@ public class AuthenticationSessionProviderTest {
// Ensure session is here
authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
- testLoginSession(authSession, client1.getId(), null, "foo");
+ testAuthenticationSession(authSession, client1.getId(), null, "foo");
Assert.assertEquals(100, authSession.getTimestamp());
// Update and commit
@@ -99,7 +104,7 @@ public class AuthenticationSessionProviderTest {
// Ensure session was updated
authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
- testLoginSession(authSession, client1.getId(), user1.getId(), "foo-updated");
+ testAuthenticationSession(authSession, client1.getId(), user1.getId(), "foo-updated");
Assert.assertEquals(200, authSession.getTimestamp());
// Remove and commit
@@ -113,7 +118,7 @@ public class AuthenticationSessionProviderTest {
}
@Test
- public void testLoginSessionRestart() {
+ public void testAuthenticationSessionRestart() {
ClientModel client1 = realm.getClientByClientId("test-app");
UserModel user1 = session.users().getUserByUsername("user1", realm);
@@ -136,7 +141,7 @@ public class AuthenticationSessionProviderTest {
resetSession();
authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
- testLoginSession(authSession, client1.getId(), null, null);
+ testAuthenticationSession(authSession, client1.getId(), null, null);
Assert.assertTrue(authSession.getTimestamp() > 0);
Assert.assertTrue(authSession.getClientNotes().isEmpty());
@@ -145,7 +150,120 @@ public class AuthenticationSessionProviderTest {
}
- private void testLoginSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
+
+ @Test
+ public void testExpiredAuthSessions() {
+ try {
+ realm.setAccessCodeLifespan(10);
+ realm.setAccessCodeLifespanUserAction(10);
+ realm.setAccessCodeLifespanLogin(30);
+
+ // Login lifespan is largest
+ String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ resetSession();
+
+ Time.setOffset(25);
+ session.authenticationSessions().removeExpired(realm);
+ resetSession();
+
+ assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+ Time.setOffset(35);
+ session.authenticationSessions().removeExpired(realm);
+ resetSession();
+
+ assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+ // User action is largest
+ realm.setAccessCodeLifespanUserAction(40);
+
+ Time.setOffset(0);
+ authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ resetSession();
+
+ Time.setOffset(35);
+ session.authenticationSessions().removeExpired(realm);
+ resetSession();
+
+ assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+ Time.setOffset(45);
+ session.authenticationSessions().removeExpired(realm);
+ resetSession();
+
+ assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+ // Access code is largest
+ realm.setAccessCodeLifespan(50);
+
+ Time.setOffset(0);
+ authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ resetSession();
+
+ Time.setOffset(45);
+ session.authenticationSessions().removeExpired(realm);
+ resetSession();
+
+ assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+
+ Time.setOffset(55);
+ session.authenticationSessions().removeExpired(realm);
+ resetSession();
+
+ assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ } finally {
+ Time.setOffset(0);
+
+ realm.setAccessCodeLifespan(60);
+ realm.setAccessCodeLifespanUserAction(300);
+ realm.setAccessCodeLifespanLogin(1800);
+
+ }
+ }
+
+
+ @Test
+ public void testOnRealmRemoved() {
+ RealmModel fooRealm = session.realms().createRealm("foo-realm");
+ ClientModel fooClient = fooRealm.addClient("foo-client");
+
+ String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ String authSessionId2 = session.authenticationSessions().createAuthenticationSession(fooRealm, fooClient).getId();
+
+ resetSession();
+
+ new RealmManager(session).removeRealm(session.realms().getRealmByName("foo-realm"));
+
+ resetSession();
+
+ AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+ testAuthenticationSession(authSession, realm.getClientByClientId("test-app").getId(), null, null);
+ Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
+ }
+
+ @Test
+ public void testOnClientRemoved() {
+ String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ String authSessionId2 = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("third-party")).getId();
+
+ String testAppClientUUID = realm.getClientByClientId("test-app").getId();
+
+ resetSession();
+
+ new ClientManager(new RealmManager(session)).removeClient(realm, realm.getClientByClientId("third-party"));
+
+ resetSession();
+
+ AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+ testAuthenticationSession(authSession, testAppClientUUID, null, null);
+ Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
+
+ // Revert client
+ realm.addClient("third-party");
+ }
+
+
+ private void testAuthenticationSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
Assert.assertEquals(expectedClientId, authSession.getClient().getId());
if (expectedUserId == null) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index bc71a09..8c01e2c 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -25,14 +25,15 @@ import org.junit.Test;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-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.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.models.UserManager;
import org.keycloak.services.managers.UserSessionManager;
@@ -47,129 +48,129 @@ import java.util.Set;
*/
public class UserSessionInitializerTest {
- // TODO:mposolda
-// @ClassRule
-// public static KeycloakRule kc = new KeycloakRule();
-//
-// private KeycloakSession session;
-// private RealmModel realm;
-// private UserSessionManager sessionManager;
-//
-// @Before
-// public void before() {
-// session = kc.startSession();
-// realm = session.realms().getRealm("test");
-// session.users().addUser(realm, "user1").setEmail("user1@localhost");
-// session.users().addUser(realm, "user2").setEmail("user2@localhost");
-// sessionManager = new UserSessionManager(session);
-// }
-//
-// @After
-// public void after() {
-// resetSession();
-// session.sessions().removeUserSessions(realm);
-// UserModel user1 = session.users().getUserByUsername("user1", realm);
-// UserModel user2 = session.users().getUserByUsername("user2", realm);
-//
-// UserManager um = new UserManager(session);
-// um.removeUser(realm, user1);
-// um.removeUser(realm, user2);
-// kc.stopSession(session, true);
-// }
-//
-// @Test
-// public void testUserSessionInitializer() {
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// // Create and persist offline sessions
-// int started = Time.currentTime();
-// int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
-//
-// for (UserSessionModel origSession : origSessions) {
-// UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
-// for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-// sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
-// }
-// }
-//
-// resetSession();
-//
-// // Delete cache (persisted sessions are still kept)
-// session.sessions().onRealmRemoved(realm);
-//
-// // Clear ispn cache to ensure initializerState is removed as well
-// InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
-// infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
-//
-// resetSession();
-//
-// ClientModel testApp = realm.getClientByClientId("test-app");
-// ClientModel thirdparty = realm.getClientByClientId("third-party");
-// Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
-// Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-// // Load sessions from persister into infinispan/memory
-// UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
-// userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
-//
-// resetSession();
-//
-// // Assert sessions are in
-// testApp = realm.getClientByClientId("test-app");
-// Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
-// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-// List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
-// UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
-//
-// UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
-// UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
-// UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
-// }
-//
-// private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-// ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-// if (userSession != null) clientSession.setUserSession(userSession);
-// clientSession.setRedirectUri(redirect);
-// if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-// if (roles != null) clientSession.setRoles(roles);
-// if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
-// return clientSession;
-// }
-//
-// private UserSessionModel[] createSessions() {
-// UserSessionModel[] sessions = new UserSessionModel[3];
-// sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
-//
-// Set<String> roles = new HashSet<String>();
-// roles.add("one");
-// roles.add("two");
-//
-// Set<String> protocolMappers = new HashSet<String>();
-// protocolMappers.add("mapper-one");
-// protocolMappers.add("mapper-two");
-//
-// createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
-// createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
-// createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
-// createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// resetSession();
-//
-// return sessions;
-// }
-//
-// private void resetSession() {
-// kc.stopSession(session, true);
-// session = kc.startSession();
-// realm = session.realms().getRealm("test");
-// sessionManager = new UserSessionManager(session);
-// }
+
+ @ClassRule
+ public static KeycloakRule kc = new KeycloakRule();
+
+ private KeycloakSession session;
+ private RealmModel realm;
+ private UserSessionManager sessionManager;
+
+ @Before
+ public void before() {
+ session = kc.startSession();
+ realm = session.realms().getRealm("test");
+ session.users().addUser(realm, "user1").setEmail("user1@localhost");
+ session.users().addUser(realm, "user2").setEmail("user2@localhost");
+ sessionManager = new UserSessionManager(session);
+ }
+
+ @After
+ public void after() {
+ resetSession();
+ session.sessions().removeUserSessions(realm);
+ UserModel user1 = session.users().getUserByUsername("user1", realm);
+ UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+ UserManager um = new UserManager(session);
+ um.removeUser(realm, user1);
+ um.removeUser(realm, user2);
+ kc.stopSession(session, true);
+ }
+
+ @Test
+ public void testUserSessionInitializer() {
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Create and persist offline sessions
+ int started = Time.currentTime();
+ int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
+
+ for (UserSessionModel origSession : origSessions) {
+ UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
+ for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+ sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
+ }
+ }
+
+ resetSession();
+
+ // Delete cache (persisted sessions are still kept)
+ session.sessions().onRealmRemoved(realm);
+
+ // Clear ispn cache to ensure initializerState is removed as well
+ InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
+ infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
+
+ resetSession();
+
+ ClientModel testApp = realm.getClientByClientId("test-app");
+ ClientModel thirdparty = realm.getClientByClientId("third-party");
+ Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
+ Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+ // Load sessions from persister into infinispan/memory
+ UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
+ userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
+
+ resetSession();
+
+ // Assert sessions are in
+ testApp = realm.getClientByClientId("test-app");
+ Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
+ Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+ List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+ UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
+
+ UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
+ UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
+ UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
+ }
+
+ private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+ if (userSession != null) clientSession.setUserSession(userSession);
+ clientSession.setRedirectUri(redirect);
+ if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+ if (roles != null) clientSession.setRoles(roles);
+ if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
+ return clientSession;
+ }
+
+ private UserSessionModel[] createSessions() {
+ UserSessionModel[] sessions = new UserSessionModel[3];
+ sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+
+ Set<String> roles = new HashSet<String>();
+ roles.add("one");
+ roles.add("two");
+
+ Set<String> protocolMappers = new HashSet<String>();
+ protocolMappers.add("mapper-one");
+ protocolMappers.add("mapper-two");
+
+ createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
+ createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+ createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
+ createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ resetSession();
+
+ return sessions;
+ }
+
+ private void resetSession() {
+ kc.stopSession(session, true);
+ session = kc.startSession();
+ realm = session.realms().getRealm("test");
+ sessionManager = new UserSessionManager(session);
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
index 9e46ec6..8c89046 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionPersisterProviderTest.java
@@ -23,13 +23,14 @@ import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Time;
+import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-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.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
@@ -45,398 +46,398 @@ import java.util.Set;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserSessionPersisterProviderTest {
-// TODO:mposolda
-
-// @ClassRule
-// public static KeycloakRule kc = new KeycloakRule();
-//
-// private KeycloakSession session;
-// private RealmModel realm;
-// private UserSessionPersisterProvider persister;
-//
-// @Before
-// public void before() {
-// session = kc.startSession();
-// realm = session.realms().getRealm("test");
-// session.users().addUser(realm, "user1").setEmail("user1@localhost");
-// session.users().addUser(realm, "user2").setEmail("user2@localhost");
-// persister = session.getProvider(UserSessionPersisterProvider.class);
-// }
-//
-// @After
-// public void after() {
-// resetSession();
-// session.sessions().removeUserSessions(realm);
-// UserModel user1 = session.users().getUserByUsername("user1", realm);
-// UserModel user2 = session.users().getUserByUsername("user2", realm);
-//
-// UserManager um = new UserManager(session);
-// if (user1 != null) {
-// um.removeUser(realm, user1);
-// }
-// if (user2 != null) {
-// um.removeUser(realm, user2);
-// }
-// kc.stopSession(session, true);
-// }
-//
-// @Test
-// public void testPersistenceWithLoad() {
-// // Create some sessions in infinispan
-// int started = Time.currentTime();
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// // Persist 3 created userSessions and clientSessions as offline
-// ClientModel testApp = realm.getClientByClientId("test-app");
-// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-// for (UserSessionModel userSession : userSessions) {
-// persistUserSession(userSession, true);
-// }
-//
-// // Persist 1 online session
-// UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
-// persistUserSession(userSession, false);
-//
-// resetSession();
-//
-// // Assert online session
-// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
-// UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
-//
-// // Assert offline sessions
-// loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
-// UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
-//
-// assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
-// assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
-// assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
-// }
-//
-// @Test
-// public void testUpdateTimestamps() {
-// // Create some sessions in infinispan
-// int started = Time.currentTime();
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// // Persist 3 created userSessions and clientSessions as offline
-// ClientModel testApp = realm.getClientByClientId("test-app");
-// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-// for (UserSessionModel userSession : userSessions) {
-// persistUserSession(userSession, true);
-// }
-//
-// // Persist 1 online session
-// UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
-// persistUserSession(userSession, false);
-//
-// resetSession();
-//
-// // update timestamps
-// int newTime = started + 50;
-// persister.updateAllTimestamps(newTime);
-//
-// // Assert online session
-// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
-// Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
-//
-// // Assert offline sessions
-// loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
-// Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
-// }
-//
-// private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
-// int clientSessionsCount = 0;
-// for (UserSessionModel loadedSession : loadedSessions) {
-// Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
-// for (ClientSessionModel clientSession : loadedSession.getClientSessions()) {
-// Assert.assertEquals(expectedTime, clientSession.getTimestamp());
-// clientSessionsCount++;
-// }
-// }
-// return clientSessionsCount;
-// }
-//
-// @Test
-// public void testUpdateAndRemove() {
-// // Create some sessions in infinispan
-// int started = Time.currentTime();
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// // Persist 1 offline session
-// UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
-// persistUserSession(userSession, true);
-//
-// resetSession();
-//
-// // Load offline session
-// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-// UserSessionModel persistedSession = loadedSessions.get(0);
-// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
-//
-// // Update userSession
-// Time.setOffset(10);
-// try {
-// persistedSession.setLastSessionRefresh(Time.currentTime());
-// persistedSession.setNote("foo", "bar");
-// persistedSession.setState(UserSessionModel.State.LOGGING_IN);
-// persister.updateUserSession(persistedSession, true);
-//
-// // create new clientSession
-// ClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
-// "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-// persister.createClientSession(clientSession, true);
-//
-// resetSession();
-//
-// // Assert session updated
-// loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-// persistedSession = loadedSessions.get(0);
-// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
-// Assert.assertEquals("bar", persistedSession.getNote("foo"));
-// Assert.assertEquals(UserSessionModel.State.LOGGING_IN, persistedSession.getState());
-//
-// // Remove clientSession
-// persister.removeClientSession(clientSession.getId(), true);
-//
-// resetSession();
-//
-// // Assert clientSession removed
-// loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-// persistedSession = loadedSessions.get(0);
-// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
-//
-// // Remove userSession
-// persister.removeUserSession(persistedSession.getId(), true);
-//
-// resetSession();
-//
-// // Assert nothing found
-// loadPersistedSessionsPaginated(true, 10, 0, 0);
-// } finally {
-// Time.setOffset(0);
-// }
-// }
-//
-// @Test
-// public void testOnRealmRemoved() {
-// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-// fooRealm.addClient("foo-app");
-// session.users().addUser(fooRealm, "user3");
-//
-// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-// createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// resetSession();
-//
-// // Persist offline session
-// fooRealm = session.realms().getRealm("foo");
-// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-// persistUserSession(userSession, true);
-//
-// resetSession();
-//
-// // Assert session was persisted
-// loadPersistedSessionsPaginated(true, 10, 1, 1);
-//
-// // Remove realm
-// RealmManager realmMgr = new RealmManager(session);
-// realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//
-// resetSession();
-//
-// // Assert nothing loaded
-// loadPersistedSessionsPaginated(true, 10, 0, 0);
-// }
-//
-// @Test
-// public void testOnClientRemoved() {
-// int started = Time.currentTime();
-//
-// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-// fooRealm.addClient("foo-app");
-// fooRealm.addClient("bar-app");
-// session.users().addUser(fooRealm, "user3");
-//
-// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-// createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-// createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// resetSession();
-//
-// // Persist offline session
-// fooRealm = session.realms().getRealm("foo");
-// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-// persistUserSession(userSession, true);
-//
-// resetSession();
-//
-// RealmManager realmMgr = new RealmManager(session);
-// ClientManager clientMgr = new ClientManager(realmMgr);
-// fooRealm = realmMgr.getRealm("foo");
-//
-// // Assert session was persisted with both clientSessions
-// UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
-// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
-//
-// // Remove foo-app client
-// ClientModel client = fooRealm.getClientByClientId("foo-app");
-// clientMgr.removeClient(fooRealm, client);
-//
-// resetSession();
-//
-// realmMgr = new RealmManager(session);
-// clientMgr = new ClientManager(realmMgr);
-// fooRealm = realmMgr.getRealm("foo");
-//
-// // Assert just one bar-app clientSession persisted now
-// persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
-// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
-//
-// // Remove bar-app client
-// client = fooRealm.getClientByClientId("bar-app");
-// clientMgr.removeClient(fooRealm, client);
-//
-// resetSession();
-//
-// // Assert nothing loaded - userSession was removed as well because it was last userSession
-// loadPersistedSessionsPaginated(true, 10, 0, 0);
-//
-// // Cleanup
-// realmMgr = new RealmManager(session);
-// realmMgr.removeRealm(realmMgr.getRealm("foo"));
-// }
-//
-// @Test
-// public void testOnUserRemoved() {
-// // Create some sessions in infinispan
-// int started = Time.currentTime();
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// // Persist 2 offline sessions of 2 users
-// UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
-// UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
-// persistUserSession(userSession1, true);
-// persistUserSession(userSession2, true);
-//
-// resetSession();
-//
-// // Load offline sessions
-// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
-//
-// // Properly delete user and assert his offlineSession removed
-// UserModel user1 = session.users().getUserByUsername("user1", realm);
-// new UserManager(session).removeUser(realm, user1);
-//
-// resetSession();
-//
-// Assert.assertEquals(1, persister.getUserSessionsCount(true));
-// loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
-// UserSessionModel persistedSession = loadedSessions.get(0);
-// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
-//
-// // KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
-// UserModel user2 = session.users().getUserByUsername("user2", realm);
-// session.users().removeUser(realm, user2);
-//
-// loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
-//
-// }
-//
-// // KEYCLOAK-1999
-// @Test
-// public void testNoSessions() {
-// UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
-// List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
-// Assert.assertEquals(0, sessions.size());
-// }
-//
-//
-// private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-// ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
-// if (userSession != null) clientSession.setUserSession(userSession);
-// clientSession.setRedirectUri(redirect);
-// if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-// if (roles != null) clientSession.setRoles(roles);
-// if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
-// return clientSession;
-// }
-//
-// private UserSessionModel[] createSessions() {
-// UserSessionModel[] sessions = new UserSessionModel[3];
-// sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
-//
-// Set<String> roles = new HashSet<String>();
-// roles.add("one");
-// roles.add("two");
-//
-// Set<String> protocolMappers = new HashSet<String>();
-// protocolMappers.add("mapper-one");
-// protocolMappers.add("mapper-two");
-//
-// createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
-// createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
-// createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
-// createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// return sessions;
-// }
-//
-// private void persistUserSession(UserSessionModel userSession, boolean offline) {
-// persister.createUserSession(userSession, offline);
-// for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-// persister.createClientSession(clientSession, offline);
-// }
-// }
-//
-// private void resetSession() {
-// kc.stopSession(session, true);
-// session = kc.startSession();
-// realm = session.realms().getRealm("test");
-// persister = session.getProvider(UserSessionPersisterProvider.class);
-// }
-//
-// public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
-// for (UserSessionModel session : sessions) {
-// if (session.getId().equals(id)) {
-// UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
-// return;
-// }
-// }
-// Assert.fail("Session with ID " + id + " not found in the list");
-// }
-//
-// private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
-// int count = persister.getUserSessionsCount(offline);
-//
-// int start = 0;
-// int pageCount = 0;
-// boolean next = true;
-// List<UserSessionModel> result = new ArrayList<>();
-// while (next && start < count) {
-// List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
-// if (sess.size() == 0) {
-// next = false;
-// } else {
-// pageCount++;
-// start += sess.size();
-// result.addAll(sess);
-// }
-// }
-//
-// Assert.assertEquals(pageCount, expectedPageCount);
-// Assert.assertEquals(result.size(), expectedSessionsCount);
-// return result;
-// }
+
+
+ @ClassRule
+ public static KeycloakRule kc = new KeycloakRule();
+
+ private KeycloakSession session;
+ private RealmModel realm;
+ private UserSessionPersisterProvider persister;
+
+ @Before
+ public void before() {
+ session = kc.startSession();
+ realm = session.realms().getRealm("test");
+ session.users().addUser(realm, "user1").setEmail("user1@localhost");
+ session.users().addUser(realm, "user2").setEmail("user2@localhost");
+ persister = session.getProvider(UserSessionPersisterProvider.class);
+ }
+
+ @After
+ public void after() {
+ resetSession();
+ session.sessions().removeUserSessions(realm);
+ UserModel user1 = session.users().getUserByUsername("user1", realm);
+ UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+ UserManager um = new UserManager(session);
+ if (user1 != null) {
+ um.removeUser(realm, user1);
+ }
+ if (user2 != null) {
+ um.removeUser(realm, user2);
+ }
+ kc.stopSession(session, true);
+ }
+
+ @Test
+ public void testPersistenceWithLoad() {
+ // Create some sessions in infinispan
+ int started = Time.currentTime();
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Persist 3 created userSessions and clientSessions as offline
+ ClientModel testApp = realm.getClientByClientId("test-app");
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+ for (UserSessionModel userSession : userSessions) {
+ persistUserSession(userSession, true);
+ }
+
+ // Persist 1 online session
+ UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
+ persistUserSession(userSession, false);
+
+ resetSession();
+
+ // Assert online session
+ List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
+ UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+
+ // Assert offline sessions
+ loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
+ UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
+
+ assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+ assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+ assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+ }
+
+ @Test
+ public void testUpdateTimestamps() {
+ // Create some sessions in infinispan
+ int started = Time.currentTime();
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Persist 3 created userSessions and clientSessions as offline
+ ClientModel testApp = realm.getClientByClientId("test-app");
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+ for (UserSessionModel userSession : userSessions) {
+ persistUserSession(userSession, true);
+ }
+
+ // Persist 1 online session
+ UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
+ persistUserSession(userSession, false);
+
+ resetSession();
+
+ // update timestamps
+ int newTime = started + 50;
+ persister.updateAllTimestamps(newTime);
+
+ // Assert online session
+ List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
+ Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
+
+ // Assert offline sessions
+ loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
+ Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
+ }
+
+ private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
+ int clientSessionsCount = 0;
+ for (UserSessionModel loadedSession : loadedSessions) {
+ Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
+ for (AuthenticatedClientSessionModel clientSession : loadedSession.getAuthenticatedClientSessions().values()) {
+ Assert.assertEquals(expectedTime, clientSession.getTimestamp());
+ clientSessionsCount++;
+ }
+ }
+ return clientSessionsCount;
+ }
+
+ @Test
+ public void testUpdateAndRemove() {
+ // Create some sessions in infinispan
+ int started = Time.currentTime();
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Persist 1 offline session
+ UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
+ persistUserSession(userSession, true);
+
+ resetSession();
+
+ // Load offline session
+ List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+ UserSessionModel persistedSession = loadedSessions.get(0);
+ UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
+
+ // Update userSession
+ Time.setOffset(10);
+ try {
+ persistedSession.setLastSessionRefresh(Time.currentTime());
+ persistedSession.setNote("foo", "bar");
+ persistedSession.setState(UserSessionModel.State.LOGGED_IN);
+ persister.updateUserSession(persistedSession, true);
+
+ // create new clientSession
+ AuthenticatedClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
+ "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+ persister.createClientSession(clientSession, true);
+
+ resetSession();
+
+ // Assert session updated
+ loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+ persistedSession = loadedSessions.get(0);
+ UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
+ Assert.assertEquals("bar", persistedSession.getNote("foo"));
+ Assert.assertEquals(UserSessionModel.State.LOGGED_IN, persistedSession.getState());
+
+ // Remove clientSession
+ persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
+
+ resetSession();
+
+ // Assert clientSession removed
+ loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+ persistedSession = loadedSessions.get(0);
+ UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
+
+ // Remove userSession
+ persister.removeUserSession(persistedSession.getId(), true);
+
+ resetSession();
+
+ // Assert nothing found
+ loadPersistedSessionsPaginated(true, 10, 0, 0);
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+ @Test
+ public void testOnRealmRemoved() {
+ RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+ fooRealm.addClient("foo-app");
+ session.users().addUser(fooRealm, "user3");
+
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+ createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ resetSession();
+
+ // Persist offline session
+ fooRealm = session.realms().getRealm("foo");
+ userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+ persistUserSession(userSession, true);
+
+ resetSession();
+
+ // Assert session was persisted
+ loadPersistedSessionsPaginated(true, 10, 1, 1);
+
+ // Remove realm
+ RealmManager realmMgr = new RealmManager(session);
+ realmMgr.removeRealm(realmMgr.getRealm("foo"));
+
+ resetSession();
+
+ // Assert nothing loaded
+ loadPersistedSessionsPaginated(true, 10, 0, 0);
+ }
+
+ @Test
+ public void testOnClientRemoved() {
+ int started = Time.currentTime();
+
+ RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+ fooRealm.addClient("foo-app");
+ fooRealm.addClient("bar-app");
+ session.users().addUser(fooRealm, "user3");
+
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+ createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+ createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ resetSession();
+
+ // Persist offline session
+ fooRealm = session.realms().getRealm("foo");
+ userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+ persistUserSession(userSession, true);
+
+ resetSession();
+
+ RealmManager realmMgr = new RealmManager(session);
+ ClientManager clientMgr = new ClientManager(realmMgr);
+ fooRealm = realmMgr.getRealm("foo");
+
+ // Assert session was persisted with both clientSessions
+ UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
+ UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
+
+ // Remove foo-app client
+ ClientModel client = fooRealm.getClientByClientId("foo-app");
+ clientMgr.removeClient(fooRealm, client);
+
+ resetSession();
+
+ realmMgr = new RealmManager(session);
+ clientMgr = new ClientManager(realmMgr);
+ fooRealm = realmMgr.getRealm("foo");
+
+ // Assert just one bar-app clientSession persisted now
+ persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
+ UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
+
+ // Remove bar-app client
+ client = fooRealm.getClientByClientId("bar-app");
+ clientMgr.removeClient(fooRealm, client);
+
+ resetSession();
+
+ // Assert nothing loaded - userSession was removed as well because it was last userSession
+ loadPersistedSessionsPaginated(true, 10, 0, 0);
+
+ // Cleanup
+ realmMgr = new RealmManager(session);
+ realmMgr.removeRealm(realmMgr.getRealm("foo"));
+ }
+
+ @Test
+ public void testOnUserRemoved() {
+ // Create some sessions in infinispan
+ int started = Time.currentTime();
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Persist 2 offline sessions of 2 users
+ UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
+ UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
+ persistUserSession(userSession1, true);
+ persistUserSession(userSession2, true);
+
+ resetSession();
+
+ // Load offline sessions
+ List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
+
+ // Properly delete user and assert his offlineSession removed
+ UserModel user1 = session.users().getUserByUsername("user1", realm);
+ new UserManager(session).removeUser(realm, user1);
+
+ resetSession();
+
+ Assert.assertEquals(1, persister.getUserSessionsCount(true));
+ loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
+ UserSessionModel persistedSession = loadedSessions.get(0);
+ UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
+
+ // KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
+ UserModel user2 = session.users().getUserByUsername("user2", realm);
+ session.users().removeUser(realm, user2);
+
+ loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
+
+ }
+
+ // KEYCLOAK-1999
+ @Test
+ public void testNoSessions() {
+ UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
+ List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
+ Assert.assertEquals(0, sessions.size());
+ }
+
+
+ private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+ if (userSession != null) clientSession.setUserSession(userSession);
+ clientSession.setRedirectUri(redirect);
+ if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+ if (roles != null) clientSession.setRoles(roles);
+ if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
+ return clientSession;
+ }
+
+ private UserSessionModel[] createSessions() {
+ UserSessionModel[] sessions = new UserSessionModel[3];
+ sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+
+ Set<String> roles = new HashSet<String>();
+ roles.add("one");
+ roles.add("two");
+
+ Set<String> protocolMappers = new HashSet<String>();
+ protocolMappers.add("mapper-one");
+ protocolMappers.add("mapper-two");
+
+ createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
+ createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+ createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
+ createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ return sessions;
+ }
+
+ private void persistUserSession(UserSessionModel userSession, boolean offline) {
+ persister.createUserSession(userSession, offline);
+ for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+ persister.createClientSession(clientSession, offline);
+ }
+ }
+
+ private void resetSession() {
+ kc.stopSession(session, true);
+ session = kc.startSession();
+ realm = session.realms().getRealm("test");
+ persister = session.getProvider(UserSessionPersisterProvider.class);
+ }
+
+ public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
+ for (UserSessionModel session : sessions) {
+ if (session.getId().equals(id)) {
+ UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
+ return;
+ }
+ }
+ Assert.fail("Session with ID " + id + " not found in the list");
+ }
+
+ private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
+ int count = persister.getUserSessionsCount(offline);
+
+ int start = 0;
+ int pageCount = 0;
+ boolean next = true;
+ List<UserSessionModel> result = new ArrayList<>();
+ while (next && start < count) {
+ List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
+ if (sess.size() == 0) {
+ next = false;
+ } else {
+ pageCount++;
+ start += sess.size();
+ result.addAll(sess);
+ }
+ }
+
+ Assert.assertEquals(pageCount, expectedPageCount);
+ Assert.assertEquals(result.size(), expectedSessionsCount);
+ return result;
+ }
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
index c69f8f9..106f525 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionProviderOfflineTest.java
@@ -24,13 +24,14 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.Time;
+import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-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.models.session.UserSessionPersisterProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
@@ -41,7 +42,6 @@ import org.keycloak.testsuite.rule.LoggingRule;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -51,410 +51,380 @@ import java.util.Set;
*/
public class UserSessionProviderOfflineTest {
- // TODO:mposolda
-// @ClassRule
-// public static KeycloakRule kc = new KeycloakRule();
-//
-// @Rule
-// public LoggingRule loggingRule = new LoggingRule(this);
-//
-// private KeycloakSession session;
-// private RealmModel realm;
-// private UserSessionManager sessionManager;
-// private UserSessionPersisterProvider persister;
-//
-// @Before
-// public void before() {
-// session = kc.startSession();
-// realm = session.realms().getRealm("test");
-// session.users().addUser(realm, "user1").setEmail("user1@localhost");
-// session.users().addUser(realm, "user2").setEmail("user2@localhost");
-// sessionManager = new UserSessionManager(session);
-// persister = session.getProvider(UserSessionPersisterProvider.class);
-// }
-//
-// @After
-// public void after() {
-// resetSession();
-// session.sessions().removeUserSessions(realm);
-// UserModel user1 = session.users().getUserByUsername("user1", realm);
-// UserModel user2 = session.users().getUserByUsername("user2", realm);
-//
-// UserManager um = new UserManager(session);
-// um.removeUser(realm, user1);
-// um.removeUser(realm, user2);
-// kc.stopSession(session, true);
-// }
-//
-//
-// @Test
-// public void testOfflineSessionsCrud() {
-// // Create some online sessions in infinispan
-// int started = Time.currentTime();
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// Map<String, String> offlineSessions = new HashMap<>();
-//
-// // Persist 3 created userSessions and clientSessions as offline
-// ClientModel testApp = realm.getClientByClientId("test-app");
-// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-// for (UserSessionModel userSession : userSessions) {
-// offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
-// }
-//
-// resetSession();
-//
-// // Assert all previously saved offline sessions found
-// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-//
-// UserSessionModel offlineSession = session.sessions().getUserSession(realm, entry.getValue());
-// boolean found = false;
-// for (ClientSessionModel clientSession : offlineSession.getClientSessions()) {
-// if (clientSession.getId().equals(entry.getKey())) {
-// found = true;
-// }
-// }
-// Assert.assertTrue(found);
-// }
-//
-// // Find clients with offline token
-// UserModel user1 = session.users().getUserByUsername("user1", realm);
-// Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
-// Assert.assertEquals(clients.size(), 2);
-// for (ClientModel client : clients) {
-// Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
-// }
-//
-// UserModel user2 = session.users().getUserByUsername("user2", realm);
-// clients = sessionManager.findClientsWithOfflineToken(realm, user2);
-// Assert.assertEquals(clients.size(), 1);
-// Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app"));
-//
-// // Test count
-// testApp = realm.getClientByClientId("test-app");
-// ClientModel thirdparty = realm.getClientByClientId("third-party");
-// Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
-// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-// // Revoke "test-app" for user1
-// sessionManager.revokeOfflineToken(user1, testApp);
-//
-// resetSession();
-//
-// // Assert userSession revoked
-// testApp = realm.getClientByClientId("test-app");
-// thirdparty = realm.getClientByClientId("third-party");
-// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
-// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
-//
-// List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
-// List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
-// Assert.assertEquals(1, testAppSessions.size());
-// Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
-// Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
-// Assert.assertEquals(1, thirdpartySessions.size());
-// Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
-// Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
-//
-// user1 = session.users().getUserByUsername("user1", realm);
-// user2 = session.users().getUserByUsername("user2", realm);
-// clients = sessionManager.findClientsWithOfflineToken(realm, user1);
-// Assert.assertEquals(1, clients.size());
-// Assert.assertEquals("third-party", clients.iterator().next().getClientId());
-// clients = sessionManager.findClientsWithOfflineToken(realm, user2);
-// Assert.assertEquals(1, clients.size());
-// Assert.assertEquals("test-app", clients.iterator().next().getClientId());
-// }
-//
-// @Test
-// public void testOnRealmRemoved() {
-// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-// fooRealm.addClient("foo-app");
-// session.users().addUser(fooRealm, "user3");
-//
-// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-// ClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// resetSession();
-//
-// // Persist offline session
-// fooRealm = session.realms().getRealm("foo");
-// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-// clientSession = session.sessions().getClientSession(fooRealm, clientSession.getId());
-// sessionManager.createOrUpdateOfflineSession(userSession.getClientSessions().get(0), userSession);
-//
-// resetSession();
-//
-// ClientSessionModel offlineClientSession = sessionManager.findOfflineClientSession(fooRealm, clientSession.getId());
-// Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
-// Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
-// Assert.assertEquals(offlineClientSession.getId(), offlineClientSession.getUserSession().getClientSessions().get(0).getId());
-//
-// // Remove realm
-// RealmManager realmMgr = new RealmManager(session);
-// realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//
-// resetSession();
-//
-// fooRealm = session.realms().createRealm("foo", "foo");
-// fooRealm.addClient("foo-app");
-// session.users().addUser(fooRealm, "user3");
-//
-// resetSession();
-//
-// // Assert nothing loaded
-// fooRealm = session.realms().getRealm("foo");
-// Assert.assertNull(sessionManager.findOfflineClientSession(fooRealm, clientSession.getId()));
-// Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
-//
-// // Cleanup
-// realmMgr = new RealmManager(session);
-// realmMgr.removeRealm(realmMgr.getRealm("foo"));
-// }
-//
-// @Test
-// public void testOnClientRemoved() {
-// int started = Time.currentTime();
-//
-// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-// fooRealm.addClient("foo-app");
-// fooRealm.addClient("bar-app");
-// session.users().addUser(fooRealm, "user3");
-//
-// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-// createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-// createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// resetSession();
-//
-// // Create offline session
-// fooRealm = session.realms().getRealm("foo");
-// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-// createOfflineSessionIncludeClientSessions(userSession);
-//
-// resetSession();
-//
-// RealmManager realmMgr = new RealmManager(session);
-// ClientManager clientMgr = new ClientManager(realmMgr);
-// fooRealm = realmMgr.getRealm("foo");
-//
-// // Assert session was persisted with both clientSessions
-// UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-// UserSessionProviderTest.assertSession(offlineSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
-//
-// // Remove foo-app client
-// ClientModel client = fooRealm.getClientByClientId("foo-app");
-// clientMgr.removeClient(fooRealm, client);
-//
-// resetSession();
-//
-// realmMgr = new RealmManager(session);
-// clientMgr = new ClientManager(realmMgr);
-// fooRealm = realmMgr.getRealm("foo");
-//
-// // Assert just one bar-app clientSession persisted now
-// offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-// Assert.assertEquals(1, offlineSession.getClientSessions().size());
-// Assert.assertEquals("bar-app", offlineSession.getClientSessions().get(0).getClient().getClientId());
-//
-// // Remove bar-app client
-// client = fooRealm.getClientByClientId("bar-app");
-// clientMgr.removeClient(fooRealm, client);
-//
-// resetSession();
-//
-// // Assert nothing loaded - userSession was removed as well because it was last userSession
-// offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-// Assert.assertEquals(0, offlineSession.getClientSessions().size());
-//
-// // Cleanup
-// realmMgr = new RealmManager(session);
-// realmMgr.removeRealm(realmMgr.getRealm("foo"));
-// }
-//
-// @Test
-// public void testOnUserRemoved() {
-// int started = Time.currentTime();
-//
-// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
-// fooRealm.addClient("foo-app");
-// session.users().addUser(fooRealm, "user3");
-//
-// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
-// ClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// resetSession();
-//
-// // Create offline session
-// fooRealm = session.realms().getRealm("foo");
-// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
-// createOfflineSessionIncludeClientSessions(userSession);
-//
-// resetSession();
-//
-// RealmManager realmMgr = new RealmManager(session);
-// fooRealm = realmMgr.getRealm("foo");
-// UserModel user3 = session.users().getUserByUsername("user3", fooRealm);
-//
-// // Assert session was persisted with both clientSessions
-// UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
-// UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
-//
-// // Remove user3
-// new UserManager(session).removeUser(fooRealm, user3);
-//
-// resetSession();
-//
-// // Assert userSession removed as well
-// Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId()));
-// Assert.assertNull(session.sessions().getOfflineClientSession(fooRealm, clientSession.getId()));
-//
-// // Cleanup
-// realmMgr = new RealmManager(session);
-// realmMgr.removeRealm(realmMgr.getRealm("foo"));
-//
-// }
-//
-// @Test
-// public void testExpired() {
-// // Create some online sessions in infinispan
-// int started = Time.currentTime();
-// UserSessionModel[] origSessions = createSessions();
-//
-// resetSession();
-//
-// Map<String, String> offlineSessions = new HashMap<>();
-//
-// // Persist 3 created userSessions and clientSessions as offline
-// ClientModel testApp = realm.getClientByClientId("test-app");
-// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
-// for (UserSessionModel userSession : userSessions) {
-// offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
-// }
-//
-// resetSession();
-//
-// // Assert all previously saved offline sessions found
-// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-// }
-//
-// UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
-// Assert.assertNotNull(session0);
-// List<String> clientSessions = new LinkedList<>();
-// for (ClientSessionModel clientSession : session0.getClientSessions()) {
-// clientSessions.add(clientSession.getId());
-// Assert.assertNotNull(session.sessions().getOfflineClientSession(realm, clientSession.getId()));
-// }
-//
-// UserSessionModel session1 = session.sessions().getOfflineUserSession(realm, origSessions[1].getId());
-// Assert.assertEquals(1, session1.getClientSessions().size());
-// ClientSessionModel cls1 = session1.getClientSessions().get(0);
-//
-// // sessions are in persister too
-// Assert.assertEquals(3, persister.getUserSessionsCount(true));
-//
-// // Set lastSessionRefresh to session[0] to 0
-// session0.setLastSessionRefresh(0);
-//
-// // Set timestamp to cls1 to 0
-// cls1.setTimestamp(0);
-//
-// resetSession();
-//
-// session.sessions().removeExpired(realm);
-//
-// resetSession();
-//
-// // assert session0 not found now
-// Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
-// for (String clientSession : clientSessions) {
-// Assert.assertNull(session.sessions().getOfflineClientSession(realm, origSessions[0].getId()));
-// offlineSessions.remove(clientSession);
-// }
-//
-// // Assert cls1 not found too
-// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-// String userSessionId = entry.getValue();
-// if (userSessionId.equals(session1.getId())) {
-// Assert.assertFalse(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-// } else {
-// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
-// }
-// }
-// Assert.assertEquals(1, persister.getUserSessionsCount(true));
-//
-// // Expire everything and assert nothing found
-// Time.setOffset(3000000);
-// try {
-// session.sessions().removeExpired(realm);
-//
-// resetSession();
-//
-// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
-// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) == null);
-// }
-// Assert.assertEquals(0, persister.getUserSessionsCount(true));
-//
-// } finally {
-// Time.setOffset(0);
-// }
-// }
-//
-// private Map<String, String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
-// Map<String, String> offlineSessions = new HashMap<>();
-//
-// for (ClientSessionModel clientSession : userSession.getClientSessions()) {
-// sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
-// offlineSessions.put(clientSession.getId(), userSession.getId());
-// }
-// return offlineSessions;
-// }
-//
-//
-//
-// private void resetSession() {
-// kc.stopSession(session, true);
-// session = kc.startSession();
-// realm = session.realms().getRealm("test");
-// sessionManager = new UserSessionManager(session);
-// persister = session.getProvider(UserSessionPersisterProvider.class);
-// }
-//
-// private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
-// ClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client);
-// if (userSession != null) clientSession.setUserSession(userSession);
-// clientSession.setRedirectUri(redirect);
-// if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
-// if (roles != null) clientSession.setRoles(roles);
-// if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
-// return clientSession;
-// }
-//
-// private UserSessionModel[] createSessions() {
-// UserSessionModel[] sessions = new UserSessionModel[3];
-// sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
-//
-// Set<String> roles = new HashSet<String>();
-// roles.add("one");
-// roles.add("two");
-//
-// Set<String> protocolMappers = new HashSet<String>();
-// protocolMappers.add("mapper-one");
-// protocolMappers.add("mapper-two");
-//
-// createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
-// createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
-// createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
-// createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
-//
-// return sessions;
-// }
+ @ClassRule
+ public static KeycloakRule kc = new KeycloakRule();
+
+ @Rule
+ public LoggingRule loggingRule = new LoggingRule(this);
+
+ private KeycloakSession session;
+ private RealmModel realm;
+ private UserSessionManager sessionManager;
+ private UserSessionPersisterProvider persister;
+
+ @Before
+ public void before() {
+ session = kc.startSession();
+ realm = session.realms().getRealm("test");
+ session.users().addUser(realm, "user1").setEmail("user1@localhost");
+ session.users().addUser(realm, "user2").setEmail("user2@localhost");
+ sessionManager = new UserSessionManager(session);
+ persister = session.getProvider(UserSessionPersisterProvider.class);
+ }
+
+ @After
+ public void after() {
+ resetSession();
+ session.sessions().removeUserSessions(realm);
+ UserModel user1 = session.users().getUserByUsername("user1", realm);
+ UserModel user2 = session.users().getUserByUsername("user2", realm);
+
+ UserManager um = new UserManager(session);
+ um.removeUser(realm, user1);
+ um.removeUser(realm, user2);
+ kc.stopSession(session, true);
+ }
+
+
+ @Test
+ public void testOfflineSessionsCrud() {
+ // Create some online sessions in infinispan
+ int started = Time.currentTime();
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Key is userSession ID, values are client UUIDS
+ Map<String, Set<String>> offlineSessions = new HashMap<>();
+
+ // Persist 3 created userSessions and clientSessions as offline
+ ClientModel testApp = realm.getClientByClientId("test-app");
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+ for (UserSessionModel userSession : userSessions) {
+ offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession));
+ }
+
+ resetSession();
+
+ // Assert all previously saved offline sessions found
+ for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
+ UserSessionModel offlineSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
+ Assert.assertNotNull(offlineSession);
+ Assert.assertEquals(offlineSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
+ }
+
+ // Find clients with offline token
+ UserModel user1 = session.users().getUserByUsername("user1", realm);
+ Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+ Assert.assertEquals(clients.size(), 2);
+ for (ClientModel client : clients) {
+ Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
+ }
+
+ UserModel user2 = session.users().getUserByUsername("user2", realm);
+ clients = sessionManager.findClientsWithOfflineToken(realm, user2);
+ Assert.assertEquals(clients.size(), 1);
+ Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app"));
+
+ // Test count
+ testApp = realm.getClientByClientId("test-app");
+ ClientModel thirdparty = realm.getClientByClientId("third-party");
+ Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
+ Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+ // Revoke "test-app" for user1
+ sessionManager.revokeOfflineToken(user1, testApp);
+
+ resetSession();
+
+ // Assert userSession revoked
+ testApp = realm.getClientByClientId("test-app");
+ thirdparty = realm.getClientByClientId("third-party");
+ Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
+ Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
+
+ List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
+ List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
+ Assert.assertEquals(1, testAppSessions.size());
+ Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
+ Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
+ Assert.assertEquals(1, thirdpartySessions.size());
+ Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
+ Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
+
+ user1 = session.users().getUserByUsername("user1", realm);
+ user2 = session.users().getUserByUsername("user2", realm);
+ clients = sessionManager.findClientsWithOfflineToken(realm, user1);
+ Assert.assertEquals(1, clients.size());
+ Assert.assertEquals("third-party", clients.iterator().next().getClientId());
+ clients = sessionManager.findClientsWithOfflineToken(realm, user2);
+ Assert.assertEquals(1, clients.size());
+ Assert.assertEquals("test-app", clients.iterator().next().getClientId());
+ }
+
+ @Test
+ public void testOnRealmRemoved() {
+ RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+ fooRealm.addClient("foo-app");
+ session.users().addUser(fooRealm, "user3");
+
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+ AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ resetSession();
+
+ // Persist offline session
+ fooRealm = session.realms().getRealm("foo");
+ userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+ createOfflineSessionIncludeClientSessions(userSession);
+
+ resetSession();
+
+ UserSessionModel offlineUserSession = sessionManager.findOfflineUserSession(fooRealm, userSession.getId());
+ Assert.assertEquals(offlineUserSession.getAuthenticatedClientSessions().size(), 1);
+ AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().values().iterator().next();
+ Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
+ Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
+
+ // Remove realm
+ RealmManager realmMgr = new RealmManager(session);
+ realmMgr.removeRealm(realmMgr.getRealm("foo"));
+
+ resetSession();
+
+ fooRealm = session.realms().createRealm("foo", "foo");
+ fooRealm.addClient("foo-app");
+ session.users().addUser(fooRealm, "user3");
+
+ resetSession();
+
+ // Assert nothing loaded
+ fooRealm = session.realms().getRealm("foo");
+ Assert.assertNull(sessionManager.findOfflineUserSession(fooRealm, userSession.getId()));
+ Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
+
+ // Cleanup
+ realmMgr = new RealmManager(session);
+ realmMgr.removeRealm(realmMgr.getRealm("foo"));
+ }
+
+ @Test
+ public void testOnClientRemoved() {
+ int started = Time.currentTime();
+
+ RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+ fooRealm.addClient("foo-app");
+ fooRealm.addClient("bar-app");
+ session.users().addUser(fooRealm, "user3");
+
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+ createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+ createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ resetSession();
+
+ // Create offline session
+ fooRealm = session.realms().getRealm("foo");
+ userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+ createOfflineSessionIncludeClientSessions(userSession);
+
+ resetSession();
+
+ RealmManager realmMgr = new RealmManager(session);
+ ClientManager clientMgr = new ClientManager(realmMgr);
+ fooRealm = realmMgr.getRealm("foo");
+
+ // Assert session was persisted with both clientSessions
+ UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+ UserSessionProviderTest.assertSession(offlineSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
+
+ // Remove foo-app client
+ ClientModel client = fooRealm.getClientByClientId("foo-app");
+ clientMgr.removeClient(fooRealm, client);
+
+ resetSession();
+
+ realmMgr = new RealmManager(session);
+ clientMgr = new ClientManager(realmMgr);
+ fooRealm = realmMgr.getRealm("foo");
+
+ // Assert just one bar-app clientSession persisted now
+ offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+ Assert.assertEquals(1, offlineSession.getAuthenticatedClientSessions().size());
+ Assert.assertEquals("bar-app", offlineSession.getAuthenticatedClientSessions().values().iterator().next().getClient().getClientId());
+
+ // Remove bar-app client
+ client = fooRealm.getClientByClientId("bar-app");
+ clientMgr.removeClient(fooRealm, client);
+
+ resetSession();
+
+ // Assert nothing loaded - userSession was removed as well because it was last userSession
+ realmMgr = new RealmManager(session);
+ fooRealm = realmMgr.getRealm("foo");
+ offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+ Assert.assertEquals(0, offlineSession.getAuthenticatedClientSessions().size());
+
+ // Cleanup
+ realmMgr = new RealmManager(session);
+ realmMgr.removeRealm(realmMgr.getRealm("foo"));
+ }
+
+ @Test
+ public void testOnUserRemoved() {
+ int started = Time.currentTime();
+
+ RealmModel fooRealm = session.realms().createRealm("foo", "foo");
+ fooRealm.addClient("foo-app");
+ session.users().addUser(fooRealm, "user3");
+
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
+ AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ resetSession();
+
+ // Create offline session
+ fooRealm = session.realms().getRealm("foo");
+ userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
+ createOfflineSessionIncludeClientSessions(userSession);
+
+ resetSession();
+
+ RealmManager realmMgr = new RealmManager(session);
+ fooRealm = realmMgr.getRealm("foo");
+ UserModel user3 = session.users().getUserByUsername("user3", fooRealm);
+
+ // Assert session was persisted with both clientSessions
+ UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
+ UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
+
+ // Remove user3
+ new UserManager(session).removeUser(fooRealm, user3);
+
+ resetSession();
+
+ // Assert userSession removed as well
+ Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId()));
+
+ // Cleanup
+ realmMgr = new RealmManager(session);
+ realmMgr.removeRealm(realmMgr.getRealm("foo"));
+
+ }
+
+ @Test
+ public void testExpired() {
+ // Create some online sessions in infinispan
+ int started = Time.currentTime();
+ UserSessionModel[] origSessions = createSessions();
+
+ resetSession();
+
+ // Key is userSessionId, value is set of client UUIDS
+ Map<String, Set<String>> offlineSessions = new HashMap<>();
+
+ // Persist 3 created userSessions and clientSessions as offline
+ ClientModel testApp = realm.getClientByClientId("test-app");
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
+ for (UserSessionModel userSession : userSessions) {
+ offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession));
+ }
+
+ resetSession();
+
+ // Assert all previously saved offline sessions found
+ for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
+ UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
+ Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
+ }
+
+ UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
+ Assert.assertNotNull(session0);
+
+ // sessions are in persister too
+ Assert.assertEquals(3, persister.getUserSessionsCount(true));
+
+ // Set lastSessionRefresh to session[0] to 0
+ session0.setLastSessionRefresh(0);
+
+ resetSession();
+
+ session.sessions().removeExpired(realm);
+
+ resetSession();
+
+ // assert session0 not found now
+ Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
+
+ Assert.assertEquals(2, persister.getUserSessionsCount(true));
+
+ // Expire everything and assert nothing found
+ Time.setOffset(3000000);
+ try {
+ session.sessions().removeExpired(realm);
+
+ resetSession();
+
+ for (String userSessionId : offlineSessions.keySet()) {
+ Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId));
+ }
+ Assert.assertEquals(0, persister.getUserSessionsCount(true));
+
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+ private Set<String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
+ Set<String> offlineSessions = new HashSet<>();
+
+ for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
+ sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
+ offlineSessions.add(clientSession.getClient().getId());
+ }
+ return offlineSessions;
+ }
+
+
+ private void resetSession() {
+ kc.stopSession(session, true);
+ session = kc.startSession();
+ realm = session.realms().getRealm("test");
+ sessionManager = new UserSessionManager(session);
+ persister = session.getProvider(UserSessionPersisterProvider.class);
+ }
+
+ private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client, userSession);
+ if (userSession != null) clientSession.setUserSession(userSession);
+ clientSession.setRedirectUri(redirect);
+ if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
+ if (roles != null) clientSession.setRoles(roles);
+ if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
+ return clientSession;
+ }
+
+ private UserSessionModel[] createSessions() {
+ UserSessionModel[] sessions = new UserSessionModel[3];
+ sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+
+ Set<String> roles = new HashSet<String>();
+ roles.add("one");
+ roles.add("two");
+
+ Set<String> protocolMappers = new HashSet<String>();
+ protocolMappers.add("mapper-one");
+ protocolMappers.add("mapper-two");
+
+ createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
+ createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
+ createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
+ createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+
+ return sessions;
+ }
}
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 683a490..7d6745e 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
@@ -25,7 +25,6 @@ import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserLoginFailureModel;
@@ -37,8 +36,8 @@ import org.keycloak.models.UserManager;
import org.keycloak.testsuite.rule.KeycloakRule;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -107,21 +106,35 @@ public class UserSessionProviderTest {
}
@Test
+ public void testRestartSession() {
+ int started = Time.currentTime();
+ UserSessionModel[] sessions = createSessions();
+
+ Time.setOffset(100);
+
+ UserSessionModel userSession = session.sessions().getUserSession(realm, sessions[0].getId());
+ assertSession(userSession, session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
+
+ userSession.restartSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.6", "form", true, null, null);
+
+ resetSession();
+
+ userSession = session.sessions().getUserSession(realm, sessions[0].getId());
+ assertSession(userSession, session.users().getUserByUsername("user2", realm), "127.0.0.6", started + 100, started + 100);
+
+ Time.setOffset(0);
+ }
+
+ @Test
public void testCreateClientSession() {
UserSessionModel[] sessions = createSessions();
- List<ClientSessionModel> clientSessions = session.sessions().getUserSession(realm, sessions[0].getId()).getClientSessions();
+ Map<String, AuthenticatedClientSessionModel> clientSessions = session.sessions().getUserSession(realm, sessions[0].getId()).getAuthenticatedClientSessions();
assertEquals(2, clientSessions.size());
- String client1 = realm.getClientByClientId("test-app").getId();
-
- ClientSessionModel session1;
+ String clientUUID = realm.getClientByClientId("test-app").getId();
- if (clientSessions.get(0).getClient().getId().equals(client1)) {
- session1 = clientSessions.get(0);
- } else {
- session1 = clientSessions.get(1);
- }
+ AuthenticatedClientSessionModel session1 = clientSessions.get(clientUUID);
assertEquals(null, session1.getAction());
assertEquals(realm.getClientByClientId("test-app").getClientId(), session1.getClient().getClientId());
@@ -140,21 +153,22 @@ public class UserSessionProviderTest {
public void testUpdateClientSession() {
UserSessionModel[] sessions = createSessions();
- String id = sessions[0].getClientSessions().get(0).getId();
+ String userSessionId = sessions[0].getId();
+ String clientUUID = realm.getClientByClientId("test-app").getId();
- ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
+ AuthenticatedClientSessionModel clientSession = sessions[0].getAuthenticatedClientSessions().get(clientUUID);
int time = clientSession.getTimestamp();
assertEquals(null, clientSession.getAction());
- clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
+ clientSession.setAction(AuthenticatedClientSessionModel.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.name(), updated.getAction());
+ AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessions().get(clientUUID);
+ assertEquals(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
assertEquals(time + 10, updated.getTimestamp());
}
@@ -170,17 +184,12 @@ public class UserSessionProviderTest {
public void testRemoveUserSessionsByUser() {
UserSessionModel[] sessions = createSessions();
- List<String> clientSessionsRemoved = new LinkedList<String>();
- List<String> clientSessionsKept = new LinkedList<String>();
+ Map<String, Integer> clientSessionsKept = new HashMap<>();
for (UserSessionModel s : sessions) {
s = session.sessions().getUserSession(realm, s.getId());
- for (ClientSessionModel c : s.getClientSessions()) {
- if (c.getUserSession().getUser().getUsername().equals("user1")) {
- clientSessionsRemoved.add(c.getId());
- } else {
- clientSessionsKept.add(c.getId());
- }
+ if (!s.getUser().getUsername().equals("user1")) {
+ clientSessionsKept.put(s.getId(), s.getAuthenticatedClientSessions().keySet().size());
}
}
@@ -188,13 +197,12 @@ public class UserSessionProviderTest {
resetSession();
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
- assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
+ List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm));
+ assertFalse(userSessions.isEmpty());
- for (String c : clientSessionsRemoved) {
- assertNull(session.sessions().getClientSession(realm, c));
- }
- for (String c : clientSessionsKept) {
- assertNotNull(session.sessions().getClientSession(realm, c));
+ Assert.assertEquals(userSessions.size(), clientSessionsKept.size());
+ for (UserSessionModel userSession : userSessions) {
+ Assert.assertEquals((int) clientSessionsKept.get(userSession.getId()), userSession.getAuthenticatedClientSessions().size());
}
}
@@ -202,76 +210,47 @@ public class UserSessionProviderTest {
public void testRemoveUserSession() {
UserSessionModel userSession = createSessions()[0];
- List<String> clientSessionsRemoved = new LinkedList<String>();
- for (ClientSessionModel c : userSession.getClientSessions()) {
- clientSessionsRemoved.add(c.getId());
- }
-
session.sessions().removeUserSession(realm, userSession);
resetSession();
assertNull(session.sessions().getUserSession(realm, userSession.getId()));
- for (String c : clientSessionsRemoved) {
- assertNull(session.sessions().getClientSession(realm, c));
- }
}
@Test
public void testRemoveUserSessionsByRealm() {
UserSessionModel[] sessions = createSessions();
- List<ClientSessionModel> clientSessions = new LinkedList<ClientSessionModel>();
- for (UserSessionModel s : sessions) {
- clientSessions.addAll(s.getClientSessions());
- }
-
session.sessions().removeUserSessions(realm);
resetSession();
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
-
- for (ClientSessionModel c : clientSessions) {
- assertNull(session.sessions().getClientSession(realm, c.getId()));
- }
}
@Test
public void testOnClientRemoved() {
UserSessionModel[] sessions = createSessions();
- List<String> clientSessionsRemoved = new LinkedList<String>();
- List<String> clientSessionsKept = new LinkedList<String>();
+ String thirdPartyClientUUID = realm.getClientByClientId("third-party").getId();
+
+ Map<String, Set<String>> clientSessionsKept = new HashMap<>();
for (UserSessionModel s : sessions) {
- s = session.sessions().getUserSession(realm, s.getId());
- for (ClientSessionModel c : s.getClientSessions()) {
- if (c.getClient().getClientId().equals("third-party")) {
- clientSessionsRemoved.add(c.getId());
- } else {
- clientSessionsKept.add(c.getId());
- }
- }
+ Set<String> clientUUIDS = new HashSet<>(s.getAuthenticatedClientSessions().keySet());
+ clientUUIDS.remove(thirdPartyClientUUID); // This client will be later removed, hence his clientSessions too
+ clientSessionsKept.put(s.getId(), clientUUIDS);
}
- session.sessions().onClientRemoved(realm, realm.getClientByClientId("third-party"));
+ realm.removeClient(thirdPartyClientUUID);
resetSession();
- for (String c : clientSessionsRemoved) {
- assertNull(session.sessions().getClientSession(realm, c));
- }
- for (String c : clientSessionsKept) {
- assertNotNull(session.sessions().getClientSession(realm, c));
+ for (UserSessionModel s : sessions) {
+ s = session.sessions().getUserSession(realm, s.getId());
+ Set<String> clientUUIDS = s.getAuthenticatedClientSessions().keySet();
+ assertEquals(clientUUIDS, clientSessionsKept.get(s.getId()));
}
- session.sessions().onClientRemoved(realm, realm.getClientByClientId("test-app"));
- resetSession();
-
- for (String c : clientSessionsRemoved) {
- assertNull(session.sessions().getClientSession(realm, c));
- }
- for (String c : clientSessionsKept) {
- assertNull(session.sessions().getClientSession(realm, c));
- }
+ // Revert client
+ realm.addClient("third-party");
}
@Test
@@ -281,11 +260,12 @@ public class UserSessionProviderTest {
try {
Set<String> expired = new HashSet<String>();
- Set<String> expiredClientSessions = new HashSet<String>();
Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
- expired.add(session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
- expiredClientSessions.add(session.sessions().createClientSession(realm, client).getId());
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+ expired.add(userSession.getId());
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
+ Assert.assertEquals(userSession, clientSession.getUserSession());
Time.setOffset(0);
UserSessionModel s = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true, null, null);
@@ -293,15 +273,12 @@ public class UserSessionProviderTest {
s.setLastSessionRefresh(0);
expired.add(s.getId());
- ClientSessionModel clSession = session.sessions().createClientSession(realm, client);
- clSession.setUserSession(s);
- expiredClientSessions.add(clSession.getId());
-
Set<String> valid = new HashSet<String>();
Set<String> validClientSessions = new HashSet<String>();
- valid.add(session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
- validClientSessions.add(session.sessions().createClientSession(realm, client).getId());
+ userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
+ valid.add(userSession.getId());
+ validClientSessions.add(session.sessions().createClientSession(realm, client, userSession).getId());
resetSession();
@@ -311,91 +288,18 @@ public class UserSessionProviderTest {
for (String e : expired) {
assertNull(session.sessions().getUserSession(realm, e));
}
- for (String e : expiredClientSessions) {
- assertNull(session.sessions().getClientSession(realm, e));
- }
for (String v : valid) {
- assertNotNull(session.sessions().getUserSession(realm, v));
- }
- for (String e : validClientSessions) {
- assertNotNull(session.sessions().getClientSession(realm, e));
+ UserSessionModel userSessionLoaded = session.sessions().getUserSession(realm, v);
+ assertNotNull(userSessionLoaded);
+ Assert.assertEquals(1, userSessionLoaded.getAuthenticatedClientSessions().size());
+ Assert.assertNotNull(userSessionLoaded.getAuthenticatedClientSessions().get(client.getId()));
}
} finally {
Time.setOffset(0);
}
}
- @Test
- public void testExpireDetachedClientSessions() {
- try {
- realm.setAccessCodeLifespan(10);
- realm.setAccessCodeLifespanUserAction(10);
- realm.setAccessCodeLifespanLogin(30);
-
- // Login lifespan is largest
- String clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
- resetSession();
-
- Time.setOffset(25);
- session.sessions().removeExpired(realm);
- resetSession();
-
- assertNotNull(session.sessions().getClientSession(clientSessionId));
-
- Time.setOffset(35);
- session.sessions().removeExpired(realm);
- resetSession();
-
- assertNull(session.sessions().getClientSession(clientSessionId));
-
- // User action is largest
- realm.setAccessCodeLifespanUserAction(40);
-
- Time.setOffset(0);
- clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
- resetSession();
-
- Time.setOffset(35);
- session.sessions().removeExpired(realm);
- resetSession();
-
- assertNotNull(session.sessions().getClientSession(clientSessionId));
-
- Time.setOffset(45);
- session.sessions().removeExpired(realm);
- resetSession();
-
- assertNull(session.sessions().getClientSession(clientSessionId));
-
- // Access code is largest
- realm.setAccessCodeLifespan(50);
-
- Time.setOffset(0);
- clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
- resetSession();
-
- Time.setOffset(45);
- session.sessions().removeExpired(realm);
- resetSession();
-
- assertNotNull(session.sessions().getClientSession(clientSessionId));
-
- Time.setOffset(55);
- session.sessions().removeExpired(realm);
- resetSession();
-
- assertNull(session.sessions().getClientSession(clientSessionId));
- } finally {
- Time.setOffset(0);
-
- realm.setAccessCodeLifespan(60);
- realm.setAccessCodeLifespanUserAction(300);
- realm.setAccessCodeLifespanLogin(1800);
-
- }
- }
-
// KEYCLOAK-2508
@Test
public void testRemovingExpiredSession() {
@@ -429,12 +333,13 @@ public class UserSessionProviderTest {
for (int i = 0; i < 25; i++) {
Time.setOffset(i);
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null);
- ClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"));
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession);
clientSession.setUserSession(userSession);
clientSession.setRedirectUri("http://redirect");
clientSession.setRoles(new HashSet<String>());
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
clientSession.setTimestamp(userSession.getStarted());
+ userSession.setLastSessionRefresh(userSession.getStarted());
}
} finally {
Time.setOffset(0);
@@ -451,19 +356,21 @@ public class UserSessionProviderTest {
@Test
public void testCreateAndGetInSameTransaction() {
+ ClientModel client = realm.getClientByClientId("test-app");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
- ClientSessionModel clientSession = createClientSession(realm.getClientByClientId("test-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
+ AuthenticatedClientSessionModel clientSession = createClientSession(client, userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
- Assert.assertNotNull(session.sessions().getUserSession(realm, userSession.getId()));
- Assert.assertNotNull(session.sessions().getClientSession(realm, clientSession.getId()));
+ UserSessionModel userSessionLoaded = session.sessions().getUserSession(realm, userSession.getId());
+ AuthenticatedClientSessionModel clientSessionLoaded = userSessionLoaded.getAuthenticatedClientSessions().get(client.getId());
+ Assert.assertNotNull(userSessionLoaded);
+ Assert.assertNotNull(clientSessionLoaded);
- Assert.assertEquals(userSession.getId(), clientSession.getUserSession().getId());
- Assert.assertEquals(1, userSession.getClientSessions().size());
- Assert.assertEquals(clientSession.getId(), userSession.getClientSessions().get(0).getId());
+ Assert.assertEquals(userSession.getId(), clientSessionLoaded.getUserSession().getId());
+ Assert.assertEquals(1, userSessionLoaded.getAuthenticatedClientSessions().size());
}
@Test
- public void testClientLoginSessions() {
+ public void testAuthenticatedClientSessions() {
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
ClientModel client1 = realm.getClientByClientId("test-app");
@@ -486,8 +393,8 @@ public class UserSessionProviderTest {
userSession = session.sessions().getUserSession(realm, userSession.getId());
Map<String, AuthenticatedClientSessionModel> clientSessions = userSession.getAuthenticatedClientSessions();
Assert.assertEquals(2, clientSessions.size());
- testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
- testClientLoginSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
+ testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
+ testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
// Update session1
clientSessions.get(client1.getId()).setAction("foo1-updated");
@@ -498,7 +405,7 @@ public class UserSessionProviderTest {
// Ensure updated
userSession = session.sessions().getUserSession(realm, userSession.getId());
clientSessions = userSession.getAuthenticatedClientSessions();
- testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
+ testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
// Rewrite session2
clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
@@ -512,8 +419,8 @@ public class UserSessionProviderTest {
userSession = session.sessions().getUserSession(realm, userSession.getId());
clientSessions = userSession.getAuthenticatedClientSessions();
Assert.assertEquals(2, clientSessions.size());
- testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
- testClientLoginSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
+ testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
+ testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
// remove session
clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId());
@@ -549,11 +456,11 @@ public class UserSessionProviderTest {
}
- private void testClientLoginSession(AuthenticatedClientSessionModel clientLoginSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) {
- Assert.assertEquals(expectedClientId, clientLoginSession.getClient().getClientId());
- Assert.assertEquals(expectedUserSessionId, clientLoginSession.getUserSession().getId());
- Assert.assertEquals(expectedAction, clientLoginSession.getAction());
- Assert.assertEquals(expectedTimestamp, clientLoginSession.getTimestamp());
+ private void testAuthenticatedClientSession(AuthenticatedClientSessionModel clientSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) {
+ Assert.assertEquals(expectedClientId, clientSession.getClient().getClientId());
+ Assert.assertEquals(expectedUserSessionId, clientSession.getUserSession().getId());
+ Assert.assertEquals(expectedAction, clientSession.getAction());
+ Assert.assertEquals(expectedTimestamp, clientSession.getTimestamp());
}
private void assertPaginatedSession(RealmModel realm, ClientModel client, int start, int max, int expectedSize) {
@@ -642,9 +549,8 @@ public class UserSessionProviderTest {
assertNotNull(session.sessions().getUserLoginFailure(realm, "user2"));
}
- private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
- ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
- if (userSession != null) clientSession.setUserSession(userSession);
+ private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
@@ -710,9 +616,14 @@ public class UserSessionProviderTest {
assertTrue(session.getStarted() >= started - 1 && session.getStarted() <= started + 1);
assertTrue(session.getLastSessionRefresh() >= lastRefresh - 1 && session.getLastSessionRefresh() <= lastRefresh + 1);
- String[] actualClients = new String[session.getClientSessions().size()];
- for (int i = 0; i < actualClients.length; i++) {
- actualClients[i] = session.getClientSessions().get(i).getClient().getClientId();
+ String[] actualClients = new String[session.getAuthenticatedClientSessions().size()];
+ int i = 0;
+ for (Map.Entry<String, AuthenticatedClientSessionModel> entry : session.getAuthenticatedClientSessions().entrySet()) {
+ String clientUUID = entry.getKey();
+ AuthenticatedClientSessionModel clientSession = entry.getValue();
+ Assert.assertEquals(clientUUID, clientSession.getClient().getId());
+ actualClients[i] = clientSession.getClient().getClientId();
+ i++;
}
Arrays.sort(clients);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
index 46d62b8..2da679e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/OAuthClient.java
@@ -35,6 +35,7 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
@@ -69,7 +70,9 @@ public class OAuthClient {
private String redirectUri = "http://localhost:8081/app/auth";
- private String state = "mystate";
+ private StateParamProvider state = () -> {
+ return KeycloakModelUtils.generateId();
+ };
private String scope;
@@ -438,7 +441,7 @@ public class OAuthClient {
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
}
if (state != null) {
- b.queryParam(OAuth2Constants.STATE, state);
+ b.queryParam(OAuth2Constants.STATE, state.getState());
}
if(uiLocales != null){
b.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
@@ -509,8 +512,17 @@ public class OAuthClient {
return this;
}
- public OAuthClient state(String state) {
- this.state = state;
+ public OAuthClient stateParamHardcoded(String value) {
+ this.state = () -> {
+ return value;
+ };
+ return this;
+ }
+
+ public OAuthClient stateParamRandom() {
+ this.state = () -> {
+ return KeycloakModelUtils.generateId();
+ };
return this;
}
@@ -639,4 +651,10 @@ public class OAuthClient {
}
}
+ private interface StateParamProvider {
+
+ String getState();
+
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginExpiredPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginExpiredPage.java
new file mode 100644
index 0000000..e3ff938
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginExpiredPage.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.pages;
+
+import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.FindBy;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class LoginExpiredPage extends AbstractPage {
+
+ @FindBy(id = "loginRestartLink")
+ private WebElement loginRestartLink;
+
+ @FindBy(id = "loginContinueLink")
+ private WebElement loginContinueLink;
+
+
+ public void clickLoginRestartLink() {
+ loginRestartLink.click();
+ }
+
+ public void clickLoginContinueLink() {
+ loginContinueLink.click();
+ }
+
+
+ public boolean isCurrent() {
+ return driver.getTitle().equals("Page has expired");
+ }
+
+ public void open() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
index f94cea0..7aea03e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/AbstractOfflineCacheCommand.java
@@ -47,9 +47,9 @@ public abstract class AbstractOfflineCacheCommand extends AbstractCommand {
}
protected String toString(UserSessionEntity userSession) {
- int clientSessionsSize = userSession.getClientSessions()==null ? 0 : userSession.getClientSessions().size();
+ int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
- ", clientSessions: " + clientSessionsSize;
+ ", authenticatedClientSessions: " + clientSessionsSize;
}
protected abstract void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java
index 39b4d48..c8b6771 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/PersistSessionsCommand.java
@@ -17,8 +17,11 @@
package org.keycloak.testsuite.util.cli;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.RealmModel;
@@ -27,8 +30,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
-import java.util.LinkedList;
-import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -65,10 +66,9 @@ public class PersistSessionsCommand extends AbstractCommand {
});
}
- // TODO:mposolda
+
private void createSessionsBatch(final int countInThisBatch) {
- /*final List<String> userSessionIds = new LinkedList<>();
- final List<String> clientSessionIds = new LinkedList<>();
+ final List<String> userSessionIds = new LinkedList<>();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@@ -80,13 +80,11 @@ public class PersistSessionsCommand extends AbstractCommand {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
for (int i = 0; i < countInThisBatch; i++) {
- UserSessionModel userSession = session.sessions().createUserSession(realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
- ClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp);
- clientSession.setUserSession(userSession);
+ UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, john, "john-doh@localhost", "127.0.0.2", "form", true, null, null);
+ AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, testApp, userSession);
clientSession.setRedirectUri("http://redirect");
clientSession.setNote("foo", "bar-" + i);
userSessionIds.add(userSession.getId());
- clientSessionIds.add(clientSession.getId());
}
}
@@ -101,6 +99,7 @@ public class PersistSessionsCommand extends AbstractCommand {
@Override
public void run(KeycloakSession session) {
RealmModel realm = session.realms().getRealmByName("master");
+ ClientModel testApp = realm.getClientByClientId("security-admin-console");
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int counter = 0;
@@ -108,20 +107,15 @@ public class PersistSessionsCommand extends AbstractCommand {
counter++;
UserSessionModel userSession = session.sessions().getUserSession(realm, userSessionId);
persister.createUserSession(userSession, true);
- }
-
- log.infof("%d user sessions persisted. Continue", counter);
- counter = 0;
- for (String clientSessionId : clientSessionIds) {
- counter++;
- ClientSessionModel clientSession = session.sessions().getClientSession(realm, clientSessionId);
+ AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(testApp.getId());
persister.createClientSession(clientSession, true);
}
- log.infof("%d client sessions persisted. Continue", counter);
+
+ log.infof("%d user sessions persisted. Continue", counter);
}
- });*/
+ });
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
index 7489ee2..4c89eaa 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/OAuthClient.java
@@ -163,7 +163,9 @@ public class OAuthClient {
realm = "test";
clientId = "test-app";
redirectUri = APP_ROOT + "/auth";
- state = KeycloakModelUtils::generateId;
+ state = () -> {
+ return KeycloakModelUtils.generateId();
+ };
scope = null;
uiLocales = null;
clientSessionState = null;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
index 433b0b1..124620a 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionMultipleActionsTest.java
@@ -63,46 +63,50 @@ public class RequiredActionMultipleActionsTest extends AbstractTestRealmKeycloak
loginPage.open();
loginPage.login("test-user@localhost", "password");
- String sessionId = null;
+ String codeId = null;
if (changePasswordPage.isCurrent()) {
- sessionId = updatePassword(sessionId);
+ codeId = updatePassword(codeId);
updateProfilePage.assertCurrent();
- updateProfile(sessionId);
+ updateProfile(codeId);
} else if (updateProfilePage.isCurrent()) {
- sessionId = updateProfile(sessionId);
+ codeId = updateProfile(codeId);
changePasswordPage.assertCurrent();
- updatePassword(sessionId);
+ updatePassword(codeId);
} else {
Assert.fail("Expected to update password and profile before login");
}
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().session(sessionId).assertEvent();
+ events.expectLogin().session(codeId).assertEvent();
}
- public String updatePassword(String sessionId) {
+ public String updatePassword(String codeId) {
changePasswordPage.changePassword("new-password", "new-password");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PASSWORD);
- if (sessionId != null) {
- expectedEvent.session(sessionId);
+ if (codeId != null) {
+ expectedEvent.detail(Details.CODE_ID, codeId);
}
- return expectedEvent.assertEvent().getSessionId();
+ return expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
}
- public String updateProfile(String sessionId) {
+ public String updateProfile(String codeId) {
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
- AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com");
- if (sessionId != null) {
- expectedEvent.session(sessionId);
+ AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL)
+ .detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
+ .detail(Details.UPDATED_EMAIL, "new@email.com");
+ if (codeId != null) {
+ expectedEvent.detail(Details.CODE_ID, codeId);
}
- sessionId = expectedEvent.assertEvent().getSessionId();
- events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
- return sessionId;
+ codeId = expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
+ events.expectRequiredAction(EventType.UPDATE_PROFILE)
+ .detail(Details.CODE_ID, codeId)
+ .assertEvent();
+ return codeId;
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
index f613087..a06079c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionTotpSetupTest.java
@@ -127,11 +127,12 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent().getSessionId();
+ String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
+ .getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
+ events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
}
@Test
@@ -145,15 +146,16 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.configure(totp.generateTOTP(totpSecret));
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+ String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
+ .getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
+ EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
oauth.openLogout();
- events.expectLogout(loginEvent.getSessionId()).assertEvent();
+ events.expectLogout(authSessionId).assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "password");
@@ -229,7 +231,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.assertCurrent();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
+ String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent()
+ .getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@@ -260,7 +263,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
TimeBasedOTP timeBased = new TimeBasedOTP(HmacOTP.HMAC_SHA1, 8, 30, 1);
totpPage.configure(timeBased.generateTOTP(totpSecret));
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+ String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
+ .getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@@ -311,7 +315,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
HmacOTP otpgen = new HmacOTP(6, HmacOTP.HMAC_SHA1, 1);
totpPage.configure(otpgen.generateHOTP(totpSecret, 0));
String uri = driver.getCurrentUrl();
- String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
+ String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
+ .getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index 80ee0fe..9c18070 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.actions;
+import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
@@ -89,12 +90,12 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
- String sessionId = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent().getSessionId();
- events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
+ events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
+ events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().session(sessionId).assertEvent();
+ events.expectLogin().assertEvent();
// assert user is really updated in persistent store
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
@@ -116,19 +117,17 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
updateProfilePage.update("New first", "New last", "john-doh@localhost", "new");
- String sessionId = events
- .expectLogin()
+ events.expectLogin()
.event(EventType.UPDATE_PROFILE)
.detail(Details.USERNAME, "john-doh@localhost")
.user(userId)
- .session(AssertEvents.isUUID())
+ .session(Matchers.nullValue(String.class))
.removeDetail(Details.CONSENT)
- .assertEvent()
- .getSessionId();
+ .assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).session(sessionId).assertEvent();
+ events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).assertEvent();
// assert user is really updated in persistent store
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "new");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
index ab52294..86066e8 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
@@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.actions;
+import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
@@ -86,11 +87,11 @@ public class TermsAndConditionsTest extends AbstractTestRealmKeycloakTest {
termsPage.acceptTerms();
- String sessionId = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent().getSessionId();
+ events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
- events.expectLogin().session(sessionId).assertEvent();
+ events.expectLogin().assertEvent();
// assert user attribute is properly set
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
@@ -123,6 +124,7 @@ public class TermsAndConditionsTest extends AbstractTestRealmKeycloakTest {
events.expectLogin().event(EventType.CUSTOM_REQUIRED_ACTION_ERROR).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID)
.error(Errors.REJECTED_BY_USER)
.removeDetail(Details.CONSENT)
+ .session(Matchers.nullValue(String.class))
.assertEvent();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
index 38662d1..098c05a 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -122,9 +122,9 @@ public class AssertEvents implements TestRule {
.session(isUUID());
}
- // TODO:mposolda codeId is not needed anymore
public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
return expect(EventType.CODE_TO_TOKEN)
+ .detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
index abda821..37b9d72 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/AbstractKerberosTest.java
@@ -40,6 +40,7 @@ import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.utils.URLEncodedUtils;
+import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.ietf.jgss.GSSCredential;
import org.jboss.arquillian.graphene.page.Page;
@@ -347,7 +348,10 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
cleanupApacheHttpClient();
}
- DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
+ DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder()
+ .disableCookieCache(false)
+ .build();
+
httpClient.getAuthSchemes().register(AuthPolicy.SPNEGO, spnegoSchemeFactory);
if (useSpnego) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
index 65e5a3e..2d6d3af 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/kerberos/KerberosStandaloneTest.java
@@ -17,18 +17,23 @@
package org.keycloak.testsuite.federation.kerberos;
+import java.net.URI;
+import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.constants.KerberosConstants;
+import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.KerberosConfig;
@@ -148,15 +153,24 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
Response spnegoResponse = spnegoLogin("hnelson", "secret");
String context = spnegoResponse.readEntity(String.class);
spnegoResponse.close();
+
+ Assert.assertTrue(context.contains("Log in to test"));
+
Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
Matcher m = pattern.matcher(context);
Assert.assertTrue(m.find());
String url = m.group(1);
- driver.navigate().to(url);
- Assert.assertTrue(loginPage.isCurrent());
- loginPage.login("test-user@localhost", "password");
- String pageSource = driver.getPageSource();
- assertAuthenticationSuccess(driver.getCurrentUrl());
+
+
+ // Follow login with HttpClient. Improve if needed
+ MultivaluedMap<String, String> params = new javax.ws.rs.core.MultivaluedHashMap<>();
+ params.putSingle("username", "test-user@localhost");
+ params.putSingle("password", "password");
+ Response response = client.target(url).request()
+ .post(Entity.form(params));
+
+ URI redirectUri = response.getLocation();
+ assertAuthenticationSuccess(redirectUri.toString());
events.clear();
testRealmResource().components().add(kerberosProvider);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java
index 6cbe92f..08d8265 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BrowserButtonsTest.java
@@ -254,7 +254,7 @@ public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
// KEYCLOAK-4670 - Flow 5
@Test
- public void clickBackButtonAfterReturnFromRegister() {
+ public void clickBackButtonAfterReturnFromRegister() throws Exception {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
index cec079d..24a70fd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java
@@ -21,6 +21,8 @@ import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.keycloak.events.Details;
+import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@@ -28,6 +30,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
+import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.InfoPage;
@@ -43,7 +46,7 @@ import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.UserBuilder;
/**
- * Tries to test multiple browser tabs
+ * Tries to simulate testing with multiple browser tabs
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@@ -245,4 +248,43 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
}
+ @Test
+ public void loginActionWithoutExecution() throws Exception {
+ oauth.openLoginForm();
+
+ // Manually remove execution from the URL and try to simulate the request just with "code" parameter
+ String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
+ actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+
+ driver.navigate().to(actionUrl);
+
+ loginExpiredPage.assertCurrent();
+ }
+
+
+ // Same like "loginActionWithoutExecution", but AuthenticationSession is in REQUIRED_ACTIONS action
+ @Test
+ public void loginActionWithoutExecutionInRequiredActions() throws Exception {
+ oauth.openLoginForm();
+ loginPage.assertCurrent();
+
+ loginPage.login("login-test", "password");
+ updatePasswordPage.assertCurrent();
+
+ // Manually remove execution from the URL and try to simulate the request just with "code" parameter
+ String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
+ actionUrl = actionUrl.replaceFirst("&execution=.*", "");
+
+ driver.navigate().to(actionUrl);
+
+ // Back on updatePasswordPage now
+ updatePasswordPage.assertCurrent();
+
+ updatePasswordPage.changePassword("password", "password");
+ updateProfilePage.update("John", "Doe3", "john@doe3.com");
+ appPage.assertCurrent();
+ }
+
+
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
index 92e68cb..ecf540c 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java
@@ -210,12 +210,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
oauth.openLoginForm();
- String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&", "&");
- actionUrl = actionUrl.replaceFirst("&execution=.*", "");
-
- String loginPageCode = actionUrl.split("code=")[1].split("&")[0];
-
- driver.navigate().to(actionUrl);
+ String loginPageCode = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
oauth.fillLoginForm("test-user@localhost", "password");
@@ -452,7 +447,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
Assert.assertEquals(400, response.getStatusCode());
EventRepresentation event = events.poll();
- assertNotNull(event.getDetails().get(Details.CODE_ID));
+ assertNull(event.getDetails().get(Details.CODE_ID));
UserManager.realm(adminClient.realm("test")).user(user).removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE.toString());
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
index 0ca7785..7577990 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/AuthorizationCodeTest.java
@@ -155,7 +155,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
}
private void assertCode(String expectedCodeId, String actualCode) {
- assertEquals(expectedCodeId, actualCode.split("\\.")[1]);
+ assertEquals(expectedCodeId, actualCode.split("\\.")[2]);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index e244f9a..c5304ff 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -16,8 +16,8 @@
*/
package org.keycloak.testsuite.oauth;
+import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
-import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@@ -155,6 +155,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
.client(THIRD_PARTY_APP)
.error("rejected_by_user")
.removeDetail(Details.CONSENT)
+ .session(Matchers.nullValue(String.class))
.assertEvent();
}
@@ -309,6 +310,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
.client(THIRD_PARTY_APP)
.error("rejected_by_user")
.removeDetail(Details.CONSENT)
+ .session(Matchers.nullValue(String.class))
.assertEvent();
oauth.scope("foo-role third-party/bar-role");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
index ac8e359..03522d6 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCAdvancedRequestParamsTest.java
@@ -304,6 +304,31 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
}
+
+ @Test
+ public void promptLoginDifferentUser() throws Exception {
+ String sss = oauth.getLoginFormUrl();
+ System.out.println(sss);
+
+ // Login user
+ loginPage.open();
+ loginPage.login("test-user@localhost", "password");
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+
+ EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
+ IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
+
+ // Assert need to re-authenticate with prompt=login
+ driver.navigate().to(oauth.getLoginFormUrl() + "&prompt=login");
+
+ // Authenticate as different user
+ loginPage.assertCurrent();
+ loginPage.login("john-doh@localhost", "password");
+
+ errorPage.assertCurrent();
+ Assert.assertTrue(errorPage.getError().startsWith("You are already authenticated as different user"));
+ }
+
// DISPLAY & OTHERS
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js b/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
index 07a07a1..0fd70d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/scripts/client-session-test.js
@@ -12,7 +12,7 @@ function authenticate(context) {
return;
}
- if (clientSession.getAuthMethod() != "${authMethod}") {
+ if (clientSession.getProtocol() != "${authMethod}") {
context.failure(AuthenticationFlowError.INVALID_CLIENT_SESSION);
return;
}
diff --git a/themes/src/main/resources/theme/base/login/login-verify-email.ftl b/themes/src/main/resources/theme/base/login/login-verify-email.ftl
index 1396351..53caaa3 100755
--- a/themes/src/main/resources/theme/base/login/login-verify-email.ftl
+++ b/themes/src/main/resources/theme/base/login/login-verify-email.ftl
@@ -9,7 +9,7 @@
${msg("emailVerifyInstruction1")}
</p>
<p class="instruction">
- ${msg("emailVerifyInstruction2")} <a href="${url.loginEmailVerificationUrl}">${msg("doClickHere")}</a> ${msg("emailVerifyInstruction3")}
+ ${msg("emailVerifyInstruction2")} <a href="${url.loginAction}">${msg("doClickHere")}</a> ${msg("emailVerifyInstruction3")}
</p>
</#if>
</@layout.registrationLayout>
\ No newline at end of file