keycloak-uncached
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java 14(+12 -2)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java 59(+15 -44)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java 60(+6 -54)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/RootAuthenticationSessionEntity.java 77(+77 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProvider.java 89(+42 -47)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java 16(+12 -4)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java 112(+112 -0)
model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/stream/RootAuthenticationSessionPredicate.java 74(+14 -60)
services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java 4(+2 -2)
services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java 15(+14 -1)
services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java 15(+9 -6)
services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java 4(+2 -2)
services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java 7(+3 -4)
services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java 5(+3 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java 20(+1 -19)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/LastSessionRefreshCrossDCTest.java 4(+0 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java 19(+0 -19)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/MultipleTabsLoginTest.java 32(+32 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/MailAssert.java 4(+4 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java
index 34e22e3..b29922b 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/events/AuthenticationSessionAuthNoteUpdateEvent.java
@@ -36,6 +36,8 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
private String authSessionId;
+ private String clientUUID;
+
private Map<String, String> authNotesFragment;
/**
@@ -44,9 +46,10 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
* @param authNotesFragment
* @return Event. Note that {@code authNotesFragment} property is not thread safe which is fine for now.
*/
- public static AuthenticationSessionAuthNoteUpdateEvent create(String authSessionId, Map<String, String> authNotesFragment) {
+ public static AuthenticationSessionAuthNoteUpdateEvent create(String authSessionId, String clientUUID, Map<String, String> authNotesFragment) {
AuthenticationSessionAuthNoteUpdateEvent event = new AuthenticationSessionAuthNoteUpdateEvent();
event.authSessionId = authSessionId;
+ event.clientUUID = clientUUID;
event.authNotesFragment = new LinkedHashMap<>(authNotesFragment);
return event;
}
@@ -55,13 +58,18 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
return authSessionId;
}
+ public String getClientUUID() {
+ return clientUUID;
+ }
+
public Map<String, String> getAuthNotesFragment() {
return authNotesFragment;
}
@Override
public String toString() {
- return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, authNotesFragment=%s ]", authSessionId, authNotesFragment);
+ return String.format("AuthenticationSessionAuthNoteUpdateEvent [ authSessionId=%s, clientUUID=%s, authNotesFragment=%s ]",
+ authSessionId, clientUUID, authNotesFragment);
}
public static class ExternalizerImpl implements Externalizer<AuthenticationSessionAuthNoteUpdateEvent> {
@@ -73,6 +81,7 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
output.writeByte(VERSION_1);
MarshallUtil.marshallString(value.authSessionId, output);
+ MarshallUtil.marshallString(value.clientUUID, output);
MarshallUtil.marshallMap(value.authNotesFragment, output);
}
@@ -89,6 +98,7 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {
public AuthenticationSessionAuthNoteUpdateEvent readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
return create(
MarshallUtil.unmarshallString(input),
+ MarshallUtil.unmarshallString(input),
MarshallUtil.unmarshallMap(input, HashMap::new)
);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java
index 5736188..9ab5945 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/AuthenticationSessionAdapter.java
@@ -23,14 +23,13 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import org.infinispan.Cache;
-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.UserModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
/**
* NOTE: Calling setter doesn't automatically enlist for update
@@ -39,39 +38,37 @@ import org.keycloak.sessions.AuthenticationSessionModel;
*/
public class AuthenticationSessionAdapter implements AuthenticationSessionModel {
- private KeycloakSession session;
- private InfinispanAuthenticationSessionProvider provider;
- private Cache<String, AuthenticationSessionEntity> cache;
- private RealmModel realm;
+ private final KeycloakSession session;
+ private final RootAuthenticationSessionAdapter parent;
+ private final String clientUUID;
private AuthenticationSessionEntity entity;
- public AuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider, Cache<String, AuthenticationSessionEntity> cache, RealmModel realm,
- AuthenticationSessionEntity entity) {
+ public AuthenticationSessionAdapter(KeycloakSession session, RootAuthenticationSessionAdapter parent, String clientUUID, AuthenticationSessionEntity entity) {
this.session = session;
- this.provider = provider;
- this.cache = cache;
- this.realm = realm;
+ this.parent = parent;
+ this.clientUUID = clientUUID;
this.entity = entity;
}
- void update() {
- provider.tx.replace(cache, entity.getId(), entity);
+ private void update() {
+ parent.update();
}
@Override
- public String getId() {
- return entity.getId();
+ public RootAuthenticationSessionModel getParentSession() {
+ return parent;
}
+
@Override
public RealmModel getRealm() {
- return realm;
+ return parent.getRealm();
}
@Override
public ClientModel getClient() {
- return realm.getClientById(entity.getClientUuid());
+ return getRealm().getClientById(clientUUID);
}
@Override
@@ -85,16 +82,6 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
update();
}
- @Override
- public int getTimestamp() {
- return entity.getTimestamp();
- }
-
- @Override
- public void setTimestamp(int timestamp) {
- entity.setTimestamp(timestamp);
- update();
- }
@Override
public String getAction() {
@@ -303,7 +290,7 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
@Override
public UserModel getAuthenticatedUser() {
- return entity.getAuthUserId() == null ? null : session.users().getUserById(entity.getAuthUserId(), realm); }
+ return entity.getAuthUserId() == null ? null : session.users().getUserById(entity.getAuthUserId(), getRealm()); }
@Override
public void setAuthenticatedUser(UserModel user) {
@@ -312,20 +299,4 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
update();
}
- @Override
- public void updateClient(ClientModel client) {
- entity.setClientUuid(client.getId());
- update();
- }
-
- @Override
- public void restartSession(RealmModel realm, ClientModel client) {
- String id = entity.getId();
- entity = new AuthenticationSessionEntity();
- entity.setId(id);
- entity.setRealmId(realm.getId());
- entity.setClientUuid(client.getId());
- entity.setTimestamp(Time.currentTime());
- update();
- }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java
index 3d7d9af..710f17a 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/AuthenticationSessionEntity.java
@@ -17,53 +17,34 @@
package org.keycloak.models.sessions.infinispan.entities;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.io.Serializable;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import org.infinispan.util.concurrent.ConcurrentHashSet;
import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
-public class AuthenticationSessionEntity extends SessionEntity {
+public class AuthenticationSessionEntity implements Serializable {
- private String id;
-
- private String clientUuid;
private String authUserId;
private String redirectUri;
- private int timestamp;
private String action;
private Set<String> roles;
private Set<String> protocolMappers;
- private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
+ private Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new ConcurrentHashMap<>();
private String protocol;
private Map<String, String> clientNotes;
private Map<String, String> authNotes;
- private Set<String> requiredActions = new HashSet<>();
+ private Set<String> requiredActions = new ConcurrentHashSet<>();
private Map<String, String> userSessionNotes;
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- public String getClientUuid() {
- return clientUuid;
- }
-
- public void setClientUuid(String clientUuid) {
- this.clientUuid = clientUuid;
- }
-
public String getAuthUserId() {
return authUserId;
}
@@ -80,14 +61,6 @@ public class AuthenticationSessionEntity extends SessionEntity {
this.redirectUri = redirectUri;
}
- public int getTimestamp() {
- return timestamp;
- }
-
- public void setTimestamp(int timestamp) {
- this.timestamp = timestamp;
- }
-
public String getAction() {
return action;
}
@@ -160,25 +133,4 @@ public class AuthenticationSessionEntity extends SessionEntity {
this.authNotes = authNotes;
}
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof AuthenticationSessionEntity)) return false;
-
- AuthenticationSessionEntity that = (AuthenticationSessionEntity) o;
-
- if (id != null ? !id.equals(that.id) : that.id != null) return false;
-
- return true;
- }
-
- @Override
- public int hashCode() {
- return id != null ? id.hashCode() : 0;
- }
-
- @Override
- public String toString() {
- return String.format("AuthenticationSessionEntity [id=%s, realm=%s, clientUuid=%s ]", getId(), getRealmId(), getClientUuid());
- }
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/RootAuthenticationSessionEntity.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/RootAuthenticationSessionEntity.java
new file mode 100644
index 0000000..751b6f0
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/RootAuthenticationSessionEntity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017 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.models.sessions.infinispan.entities;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RootAuthenticationSessionEntity extends SessionEntity {
+
+ private String id;
+ private int timestamp;
+ private Map<String, AuthenticationSessionEntity> authenticationSessions = new ConcurrentHashMap<>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public int getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(int timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public Map<String, AuthenticationSessionEntity> getAuthenticationSessions() {
+ return authenticationSessions;
+ }
+
+ public void setAuthenticationSessions(Map<String, AuthenticationSessionEntity> authenticationSessions) {
+ this.authenticationSessions = authenticationSessions;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RootAuthenticationSessionEntity)) return false;
+
+ RootAuthenticationSessionEntity that = (RootAuthenticationSessionEntity) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id != null ? id.hashCode() : 0;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("RootAuthenticationSessionEntity [ id=%s, realm=%s ]", getId(), getRealmId());
+ }
+}
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 5ae8416..47174f8 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
@@ -28,15 +28,14 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.cache.infinispan.events.AuthenticationSessionAuthNoteUpdateEvent;
-import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
-import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
+import org.keycloak.models.sessions.infinispan.entities.RootAuthenticationSessionEntity;
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
import org.keycloak.models.sessions.infinispan.events.SessionEventsSenderTransaction;
-import org.keycloak.models.sessions.infinispan.stream.AuthenticationSessionPredicate;
+import org.keycloak.models.sessions.infinispan.stream.RootAuthenticationSessionPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
-import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.AuthenticationSessionProvider;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -46,11 +45,11 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
private static final Logger log = Logger.getLogger(InfinispanAuthenticationSessionProvider.class);
private final KeycloakSession session;
- private final Cache<String, AuthenticationSessionEntity> cache;
+ private final Cache<String, RootAuthenticationSessionEntity> cache;
protected final InfinispanKeycloakTransaction tx;
protected final SessionEventsSenderTransaction clusterEventsSenderTx;
- public InfinispanAuthenticationSessionProvider(KeycloakSession session, Cache<String, AuthenticationSessionEntity> cache) {
+ public InfinispanAuthenticationSessionProvider(KeycloakSession session, Cache<String, RootAuthenticationSessionEntity> cache) {
this.session = session;
this.cache = cache;
@@ -62,38 +61,33 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
}
@Override
- public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client) {
+ public RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm) {
String id = KeycloakModelUtils.generateId();
- return createAuthenticationSession(id, realm, client);
+ return createRootAuthenticationSession(id, realm);
}
+
@Override
- public AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client) {
- AuthenticationSessionEntity entity = new AuthenticationSessionEntity();
+ public RootAuthenticationSessionModel createRootAuthenticationSession(String id, RealmModel realm) {
+ RootAuthenticationSessionEntity entity = new RootAuthenticationSessionEntity();
entity.setId(id);
entity.setRealmId(realm.getId());
entity.setTimestamp(Time.currentTime());
- entity.setClientUuid(client.getId());
tx.put(cache, id, entity);
- AuthenticationSessionAdapter wrap = wrap(realm, entity);
- return wrap;
+ return wrap(realm, entity);
}
- private AuthenticationSessionAdapter wrap(RealmModel realm, AuthenticationSessionEntity entity) {
- return entity==null ? null : new AuthenticationSessionAdapter(session, this, cache, realm, entity);
- }
- @Override
- public AuthenticationSessionModel getAuthenticationSession(RealmModel realm, String authenticationSessionId) {
- AuthenticationSessionEntity entity = getAuthenticationSessionEntity(realm, authenticationSessionId);
- return wrap(realm, entity);
+ private RootAuthenticationSessionAdapter wrap(RealmModel realm, RootAuthenticationSessionEntity entity) {
+ return entity==null ? null : new RootAuthenticationSessionAdapter(session, this, cache, realm, entity);
}
- private AuthenticationSessionEntity getAuthenticationSessionEntity(RealmModel realm, String authSessionId) {
+
+ private RootAuthenticationSessionEntity getRootAuthenticationSessionEntity(RealmModel realm, String authSessionId) {
// Chance created in this transaction
- AuthenticationSessionEntity entity = tx.get(cache, authSessionId);
+ RootAuthenticationSessionEntity entity = tx.get(cache, authSessionId);
if (entity == null) {
entity = cache.get(authSessionId);
@@ -102,10 +96,6 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
return entity;
}
- @Override
- public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession) {
- tx.remove(cache, authenticationSession.getId());
- }
@Override
public void removeExpired(RealmModel realm) {
@@ -115,16 +105,16 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
- Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = CacheDecorators.localCache(cache)
+ Iterator<Map.Entry<String, RootAuthenticationSessionEntity>> itr = CacheDecorators.localCache(cache)
.entrySet()
.stream()
- .filter(AuthenticationSessionPredicate.create(realm.getId()).expired(expired))
+ .filter(RootAuthenticationSessionPredicate.create(realm.getId()).expired(expired))
.iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
- AuthenticationSessionEntity entity = itr.next().getValue();
+ RootAuthenticationSessionEntity entity = itr.next().getValue();
tx.remove(CacheDecorators.localCache(cache), entity.getId());
}
@@ -141,10 +131,10 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
}
protected void onRealmRemovedEvent(String realmId) {
- Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = CacheDecorators.localCache(cache)
+ Iterator<Map.Entry<String, RootAuthenticationSessionEntity>> itr = CacheDecorators.localCache(cache)
.entrySet()
.stream()
- .filter(AuthenticationSessionPredicate.create(realmId))
+ .filter(RootAuthenticationSessionPredicate.create(realmId))
.iterator();
while (itr.hasNext()) {
@@ -156,28 +146,20 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
- // Send message to all DCs. The remoteCache will notify client listeners on all DCs for remove authentication sessions of this client
- clusterEventsSenderTx.addEvent(
- ClientRemovedSessionEvent.create(session, InfinispanAuthenticationSessionProviderFactory.CLIENT_REMOVED_AUTHSESSION_EVENT, realm.getId(), false, client.getId()),
- ClusterProvider.DCNotify.ALL_DCS);
+ // No update anything on clientRemove for now. AuthenticationSessions of removed client will be handled at runtime if needed.
+
+// clusterEventsSenderTx.addEvent(
+// ClientRemovedSessionEvent.create(session, InfinispanAuthenticationSessionProviderFactory.CLIENT_REMOVED_AUTHSESSION_EVENT, realm.getId(), false, client.getId()),
+// ClusterProvider.DCNotify.ALL_DCS);
}
protected void onClientRemovedEvent(String realmId, String clientUuid) {
- Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = CacheDecorators.localCache(cache)
- .entrySet()
- .stream()
- .filter(AuthenticationSessionPredicate.create(realmId).client(clientUuid))
- .iterator();
- while (itr.hasNext()) {
- CacheDecorators.localCache(cache)
- .remove(itr.next().getKey());
- }
}
@Override
- public void updateNonlocalSessionAuthNotes(String authSessionId, Map<String, String> authNotesFragment) {
+ public void updateNonlocalSessionAuthNotes(String authSessionId, ClientModel client, Map<String, String> authNotesFragment) {
if (authSessionId == null) {
return;
}
@@ -185,18 +167,31 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
ClusterProvider cluster = session.getProvider(ClusterProvider.class);
cluster.notify(
InfinispanAuthenticationSessionProviderFactory.AUTHENTICATION_SESSION_EVENTS,
- AuthenticationSessionAuthNoteUpdateEvent.create(authSessionId, authNotesFragment),
+ AuthenticationSessionAuthNoteUpdateEvent.create(authSessionId, client.getId(), authNotesFragment),
true,
ClusterProvider.DCNotify.ALL_BUT_LOCAL_DC
);
}
+
+ @Override
+ public RootAuthenticationSessionModel getRootAuthenticationSession(RealmModel realm, String authenticationSessionId) {
+ RootAuthenticationSessionEntity entity = getRootAuthenticationSessionEntity(realm, authenticationSessionId);
+ return wrap(realm, entity);
+ }
+
+
+ @Override
+ public void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession) {
+ tx.remove(cache, authenticationSession.getId());
+ }
+
@Override
public void close() {
}
- public Cache<String, AuthenticationSessionEntity> getCache() {
+ public Cache<String, RootAuthenticationSessionEntity> getCache() {
return cache;
}
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
index 04e1dc8..4055885 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanAuthenticationSessionProviderFactory.java
@@ -26,6 +26,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.cache.infinispan.events.AuthenticationSessionAuthNoteUpdateEvent;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.RootAuthenticationSessionEntity;
import org.keycloak.models.sessions.infinispan.events.AbstractAuthSessionClusterListener;
import org.keycloak.models.sessions.infinispan.events.ClientRemovedSessionEvent;
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
@@ -46,7 +47,7 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
private static final Logger log = Logger.getLogger(InfinispanAuthenticationSessionProviderFactory.class);
- private volatile Cache<String, AuthenticationSessionEntity> authSessionsCache;
+ private volatile Cache<String, RootAuthenticationSessionEntity> authSessionsCache;
public static final String PROVIDER_ID = "infinispan";
@@ -113,11 +114,18 @@ public class InfinispanAuthenticationSessionProviderFactory implements Authentic
}
AuthenticationSessionAuthNoteUpdateEvent event = (AuthenticationSessionAuthNoteUpdateEvent) clEvent;
- AuthenticationSessionEntity authSession = this.authSessionsCache.get(event.getAuthSessionId());
- updateAuthSession(authSession, event.getAuthNotesFragment());
+ RootAuthenticationSessionEntity authSession = this.authSessionsCache.get(event.getAuthSessionId());
+ updateAuthSession(authSession, event.getClientUUID(), event.getAuthNotesFragment());
}
- private static void updateAuthSession(AuthenticationSessionEntity authSession, Map<String, String> authNotesFragment) {
+
+ private static void updateAuthSession(RootAuthenticationSessionEntity rootAuthSession, String clientUUID, Map<String, String> authNotesFragment) {
+ if (rootAuthSession == null) {
+ return;
+ }
+
+ AuthenticationSessionEntity authSession = rootAuthSession.getAuthenticationSessions().get(clientUUID);
+
if (authSession != null) {
if (authSession.getAuthNotes() == null) {
authSession.setAuthNotes(new ConcurrentHashMap<>());
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java
new file mode 100644
index 0000000..e7cd727
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/RootAuthenticationSessionAdapter.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2017 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.models.sessions.infinispan;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.infinispan.Cache;
+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.sessions.infinispan.entities.AuthenticationSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.RootAuthenticationSessionEntity;
+import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class RootAuthenticationSessionAdapter implements RootAuthenticationSessionModel {
+
+ private KeycloakSession session;
+ private InfinispanAuthenticationSessionProvider provider;
+ private Cache<String, RootAuthenticationSessionEntity> cache;
+ private RealmModel realm;
+ private RootAuthenticationSessionEntity entity;
+
+ public RootAuthenticationSessionAdapter(KeycloakSession session, InfinispanAuthenticationSessionProvider provider,
+ Cache<String, RootAuthenticationSessionEntity> cache, RealmModel realm,
+ RootAuthenticationSessionEntity entity) {
+ this.session = session;
+ this.provider = provider;
+ this.cache = cache;
+ this.realm = realm;
+ this.entity = entity;
+ }
+
+ void update() {
+ provider.tx.replace(cache, entity.getId(), entity);
+ }
+
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public RealmModel getRealm() {
+ return realm;
+ }
+
+ @Override
+ public int getTimestamp() {
+ return entity.getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(int timestamp) {
+ entity.setTimestamp(timestamp);
+ update();
+ }
+
+ @Override
+ public Map<String, AuthenticationSessionModel> getAuthenticationSessions() {
+ Map<String, AuthenticationSessionModel> result = new HashMap<>();
+
+ for (Map.Entry<String, AuthenticationSessionEntity> entry : entity.getAuthenticationSessions().entrySet()) {
+ String clientUUID = entry.getKey();
+ result.put(clientUUID , new AuthenticationSessionAdapter(session, this, clientUUID, entry.getValue()));
+ }
+
+ return result;
+ }
+
+ @Override
+ public AuthenticationSessionModel getAuthenticationSession(ClientModel client) {
+ return client==null ? null : getAuthenticationSessions().get(client.getId());
+ }
+
+ @Override
+ public AuthenticationSessionModel createAuthenticationSession(ClientModel client) {
+ AuthenticationSessionEntity authSessionEntity = new AuthenticationSessionEntity();
+ entity.getAuthenticationSessions().put(client.getId(), authSessionEntity);
+ update();
+
+ return new AuthenticationSessionAdapter(session, this, client.getId(), authSessionEntity);
+ }
+
+ @Override
+ public void restartSession(RealmModel realm) {
+ entity.getAuthenticationSessions().clear();
+ entity.setTimestamp(Time.currentTime());
+ update();
+ }
+}
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 c54533c..55628cb 100644
--- a/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/AuthenticatedClientSessionModel.java
@@ -27,6 +27,11 @@ import org.keycloak.sessions.CommonClientSessionModel;
*/
public interface AuthenticatedClientSessionModel extends CommonClientSessionModel {
+ String getId();
+
+ int getTimestamp();
+ void setTimestamp(int timestamp);
+
/**
* Detaches the client session from its user session.
*/
diff --git a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java
index 8e84f1a..56b83ac 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionModel.java
@@ -21,7 +21,6 @@ import java.util.Map;
import java.util.Set;
import org.keycloak.models.ClientModel;
-import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
/**
@@ -31,10 +30,8 @@ import org.keycloak.models.UserModel;
*/
public interface AuthenticationSessionModel extends CommonClientSessionModel {
-//
-// public UserSessionModel getUserSession();
-// public void setUserSession(UserSessionModel userSession);
+ RootAuthenticationSessionModel getParentSession();
Map<String, ExecutionStatus> getExecutionStatus();
void setExecutionStatus(String authenticator, ExecutionStatus status);
@@ -125,8 +122,4 @@ public interface AuthenticationSessionModel extends CommonClientSessionModel {
*/
void clearClientNotes();
- void updateClient(ClientModel client);
-
- // Will completely restart whole state of authentication session. It will just keep same ID. It will setup it with provided realm and client.
- void restartSession(RealmModel realm, ClientModel client);
}
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 8cc4035..9a01446 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/AuthenticationSessionProvider.java
@@ -31,26 +31,26 @@ public interface AuthenticationSessionProvider extends Provider {
* Creates and registers a new authentication session with random ID. Authentication session
* entity will be prefilled with current timestamp, the given realm and client.
*/
- AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client);
+ RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm);
- AuthenticationSessionModel createAuthenticationSession(String id, RealmModel realm, ClientModel client);
+ RootAuthenticationSessionModel createRootAuthenticationSession(String id, RealmModel realm);
- AuthenticationSessionModel getAuthenticationSession(RealmModel realm, String authenticationSessionId);
+ RootAuthenticationSessionModel getRootAuthenticationSession(RealmModel realm, String authenticationSessionId);
- void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession);
+ void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession);
void removeExpired(RealmModel realm);
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);
/**
- * Requests update of authNotes of an authentication session that is not owned
+ * Requests update of authNotes of a root authentication session that is not owned
* by this instance but might exist somewhere in the cluster.
*
* @param authSessionId
* @param authNotesFragment Map with authNote values. Auth note is removed if the corresponding value in the map is {@code null}.
*/
- void updateNonlocalSessionAuthNotes(String authSessionId, Map<String, String> authNotesFragment);
+ void updateNonlocalSessionAuthNotes(String authSessionId, ClientModel client, Map<String, String> authNotesFragment);
}
diff --git a/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
index a87309c..b59ffa1 100644
--- a/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
+++ b/server-spi/src/main/java/org/keycloak/sessions/CommonClientSessionModel.java
@@ -33,13 +33,9 @@ public interface CommonClientSessionModel {
public String getRedirectUri();
public void setRedirectUri(String uri);
- public String getId();
public RealmModel getRealm();
public ClientModel getClient();
- public int getTimestamp();
- public void setTimestamp(int timestamp);
-
public String getAction();
public void setAction(String action);
diff --git a/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java b/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java
new file mode 100644
index 0000000..fb656cf
--- /dev/null
+++ b/server-spi/src/main/java/org/keycloak/sessions/RootAuthenticationSessionModel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017 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.sessions;
+
+import java.util.Map;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+
+/**
+ * Represents usually one browser session with potentially many browser tabs. Every browser tab is represented by {@link AuthenticationSessionModel}
+ * of different client.
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public interface RootAuthenticationSessionModel {
+
+ String getId();
+ RealmModel getRealm();
+
+ int getTimestamp();
+ void setTimestamp(int timestamp);
+
+
+ /**
+ * Key is client UUID, Value is AuthenticationSessionModel for particular client
+ * @return authentication sessions or empty map if no authenticationSessions presents. Never return null.
+ */
+ Map<String, AuthenticationSessionModel> getAuthenticationSessions();
+
+
+ /**
+ * @return authentication session for particular client or null if it doesn't yet exists.
+ */
+ AuthenticationSessionModel getAuthenticationSession(ClientModel client);
+
+
+ /**
+ * Create new authentication session and returns it. Overwrites existing session for particular client if already exists.
+ *
+ * @param client
+ * @return non-null fresh authentication session
+ */
+ AuthenticationSessionModel createAuthenticationSession(ClientModel client);
+
+
+ /**
+ * Will completely restart whole state of authentication session. It will just keep same ID. It will setup it with provided realm.
+ */
+ void restartSession(RealmModel realm);
+
+}
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java b/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
index 1550a8d..29ce6c4 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/ActionTokenContext.java
@@ -30,6 +30,7 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilderException;
import javax.ws.rs.core.UriInfo;
import org.jboss.resteasy.spi.HttpRequest;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
/**
*
@@ -111,7 +112,9 @@ public class ActionTokenContext<T extends JsonWebToken> {
// set up the account service as the endpoint to call.
ClientModel client = realm.getClientByClientId(clientId == null ? Constants.ACCOUNT_MANAGEMENT_CLIENT_ID : clientId);
- authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, true);
+ authSession = rootAuthSession.createAuthenticationSession(client);
+
authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
String redirectUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java
index 43e4f90..f21cf68 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/execactions/ExecuteActionsActionTokenHandler.java
@@ -75,8 +75,8 @@ public class ExecuteActionsActionTokenHandler extends AbstractActionTokenHander<
final KeycloakSession session = tokenContext.getSession();
if (tokenContext.isAuthenticationSessionFresh()) {
// Update the authentication session in the token
- token.setAuthenticationSessionId(authSession.getId());
- UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
+ token.setAuthenticationSessionId(authSession.getParentSession().getId());
+ UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId());
String confirmUri = builder.build(realm.getName()).toString();
return session.getProvider(LoginFormsProvider.class)
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java
index 39c6f9a..64c61dd 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionToken.java
@@ -31,6 +31,7 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
private static final String JSON_FIELD_IDENTITY_PROVIDER_USERNAME = "idpu";
private static final String JSON_FIELD_IDENTITY_PROVIDER_ALIAS = "idpa";
private static final String JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID = "oasid";
+ private static final String JSON_FIELD_ORIGINAL_CLIENT_UUID = "ocid";
@JsonProperty(value = JSON_FIELD_IDENTITY_PROVIDER_USERNAME)
private String identityProviderUsername;
@@ -41,9 +42,13 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
@JsonProperty(value = JSON_FIELD_ORIGINAL_AUTHENTICATION_SESSION_ID)
private String originalAuthenticationSessionId;
- public IdpVerifyAccountLinkActionToken(String userId, int absoluteExpirationInSecs, String authenticationSessionId,
+ @JsonProperty(value = JSON_FIELD_ORIGINAL_CLIENT_UUID)
+ private String originalClientUUID;
+
+ public IdpVerifyAccountLinkActionToken(String userId, int absoluteExpirationInSecs, String authenticationSessionId, String clientUUID,
String identityProviderUsername, String identityProviderAlias) {
super(userId, TOKEN_TYPE, absoluteExpirationInSecs, null, authenticationSessionId);
+ this.originalClientUUID = clientUUID;
this.identityProviderUsername = identityProviderUsername;
this.identityProviderAlias = identityProviderAlias;
}
@@ -74,4 +79,12 @@ public class IdpVerifyAccountLinkActionToken extends DefaultActionToken {
public void setOriginalAuthenticationSessionId(String originalAuthenticationSessionId) {
this.originalAuthenticationSessionId = originalAuthenticationSessionId;
}
+
+ public String getOriginalClientUUID() {
+ return originalClientUUID;
+ }
+
+ public void setOriginalClientUUID(String originalClientUUID) {
+ this.originalClientUUID = originalClientUUID;
+ }
}
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
index 7f9d58c..514ca1a 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/idpverifyemail/IdpVerifyAccountLinkActionTokenHandler.java
@@ -23,6 +23,7 @@ import org.keycloak.authentication.actiontoken.*;
import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticator;
import org.keycloak.events.*;
import org.keycloak.forms.login.LoginFormsProvider;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -31,7 +32,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.sessions.AuthenticationSessionModel;
-import org.keycloak.sessions.AuthenticationSessionProvider;
+
import java.util.Collections;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
@@ -76,8 +77,8 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
if (tokenContext.isAuthenticationSessionFresh()) {
token.setOriginalAuthenticationSessionId(token.getAuthenticationSessionId());
- token.setAuthenticationSessionId(authSession.getId());
- UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
+ token.setAuthenticationSessionId(authSession.getParentSession().getId());
+ UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId());
String confirmUri = builder.build(realm.getName()).toString();
return session.getProvider(LoginFormsProvider.class)
@@ -94,14 +95,16 @@ public class IdpVerifyAccountLinkActionTokenHandler extends AbstractActionTokenH
AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
asm.removeAuthenticationSession(realm, authSession, true);
- AuthenticationSessionProvider authSessProvider = session.authenticationSessions();
- authSession = authSessProvider.getAuthenticationSession(realm, token.getOriginalAuthenticationSessionId());
+ ClientModel originalClient = realm.getClientById(token.getOriginalClientUUID());
+ authSession = asm.getAuthenticationSessionByIdAndClient(realm, token.getOriginalAuthenticationSessionId(), originalClient);
if (authSession != null) {
authSession.setAuthNote(IdpEmailVerificationAuthenticator.VERIFY_ACCOUNT_IDP_USERNAME, token.getIdentityProviderUsername());
} else {
- authSessProvider.updateNonlocalSessionAuthNotes(
+
+ session.authenticationSessions().updateNonlocalSessionAuthNotes(
token.getAuthenticationSessionId(),
+ originalClient,
Collections.singletonMap(IdpEmailVerificationAuthenticator.VERIFY_ACCOUNT_IDP_USERNAME, token.getIdentityProviderUsername())
);
}
diff --git a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java
index 98ad8b4..91ceb2a 100644
--- a/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java
+++ b/services/src/main/java/org/keycloak/authentication/actiontoken/verifyemail/VerifyEmailActionTokenHandler.java
@@ -77,8 +77,8 @@ public class VerifyEmailActionTokenHandler extends AbstractActionTokenHander<Ver
if (tokenContext.isAuthenticationSessionFresh()) {
// Update the authentication session in the token
token.setOriginalAuthenticationSessionId(token.getAuthenticationSessionId());
- token.setAuthenticationSessionId(authSession.getId());
- UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
+ token.setAuthenticationSessionId(authSession.getParentSession().getId());
+ UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId());
String confirmUri = builder.build(realm.getName()).toString();
return session.getProvider(LoginFormsProvider.class)
diff --git a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
index d033143..4c0db68 100755
--- a/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
+++ b/services/src/main/java/org/keycloak/authentication/AuthenticationProcessor.java
@@ -56,6 +56,7 @@ import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
@@ -222,7 +223,7 @@ public class AuthenticationProcessor {
public String generateCode() {
ClientSessionCode accessCode = new ClientSessionCode(session, getRealm(), getAuthenticationSession());
- authenticationSession.setTimestamp(Time.currentTime());
+ authenticationSession.getParentSession().setTimestamp(Time.currentTime());
return accessCode.getOrGenerateCode();
}
@@ -632,7 +633,10 @@ public class AuthenticationProcessor {
} else if (e.getError() == AuthenticationFlowError.FORK_FLOW) {
ForkFlowException reset = (ForkFlowException)e;
- AuthenticationSessionModel clone = clone(session, authenticationSession);
+
+ RootAuthenticationSessionModel rootClone = clone(session, authenticationSession.getClient(), authenticationSession.getParentSession());
+ AuthenticationSessionModel clone = rootClone.getAuthenticationSession(authenticationSession.getClient());
+
clone.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
setAuthenticationSession(clone);
@@ -748,7 +752,7 @@ public class AuthenticationProcessor {
public static void resetFlow(AuthenticationSessionModel authSession, String flowPath) {
logger.debug("RESET FLOW");
- authSession.setTimestamp(Time.currentTime());
+ authSession.getParentSession().setTimestamp(Time.currentTime());
authSession.setAuthenticatedUser(null);
authSession.clearExecutionStatus();
authSession.clearUserSessionNotes();
@@ -759,20 +763,26 @@ public class AuthenticationProcessor {
authSession.setAuthNote(CURRENT_FLOW_PATH, flowPath);
}
- public static AuthenticationSessionModel clone(KeycloakSession session, AuthenticationSessionModel authSession) {
- AuthenticationSessionModel clone = new AuthenticationSessionManager(session).createAuthenticationSession(authSession.getRealm(), authSession.getClient(), true);
+ public static RootAuthenticationSessionModel clone(KeycloakSession session, ClientModel client, RootAuthenticationSessionModel authSession) {
+ RootAuthenticationSessionModel clone = new AuthenticationSessionManager(session).createAuthenticationSession(authSession.getRealm(), true);
// Transfer just the client "notes", but not "authNotes"
- for (Map.Entry<String, String> entry : authSession.getClientNotes().entrySet()) {
- clone.setClientNote(entry.getKey(), entry.getValue());
+ for (Map.Entry<String, AuthenticationSessionModel> entry : authSession.getAuthenticationSessions().entrySet()) {
+ AuthenticationSessionModel asmOrig = entry.getValue();
+ AuthenticationSessionModel asmClone = clone.createAuthenticationSession(asmOrig.getClient());
+
+ asmClone.setRedirectUri(asmOrig.getRedirectUri());
+ asmClone.setProtocol(asmOrig.getProtocol());
+
+ for (Map.Entry<String, String> clientNote : asmOrig.getClientNotes().entrySet()) {
+ asmClone.setClientNote(clientNote.getKey(), clientNote.getValue());
+ }
}
- clone.setRedirectUri(authSession.getRedirectUri());
- clone.setProtocol(authSession.getProtocol());
clone.setTimestamp(Time.currentTime());
- clone.setAuthNote(FORKED_FROM, authSession.getId());
- logger.debugf("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
+ clone.getAuthenticationSession(client).setAuthNote(FORKED_FROM, authSession.getId());
+ logger.debugf("Forked authSession %s from authSession %s . Client: '%s'", clone.getId(), authSession.getId(), client.getClientId());
return clone;
@@ -825,7 +835,8 @@ public class AuthenticationProcessor {
if (!code.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
throw new AuthenticationFlowException(AuthenticationFlowError.EXPIRED_CODE);
}
- authenticationSession.setTimestamp(Time.currentTime());
+
+ authenticationSession.getParentSession().setTimestamp(Time.currentTime());
}
public Response authenticateOnly() throws AuthenticationFlowException {
@@ -872,9 +883,9 @@ public class AuthenticationProcessor {
if (userSession == null) { // if no authenticator attached a usersession
- userSession = session.sessions().getUserSession(realm, authSession.getId());
+ userSession = session.sessions().getUserSession(realm, authSession.getParentSession().getId());
if (userSession == null) {
- userSession = session.sessions().createUserSession(authSession.getId(), realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
+ userSession = session.sessions().createUserSession(authSession.getParentSession().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()
@@ -936,7 +947,7 @@ public class AuthenticationProcessor {
if (nextRequiredAction != null) {
return AuthenticationManager.redirectToRequiredActions(session, realm, authenticationSession, uriInfo, nextRequiredAction);
} else {
- event.detail(Details.CODE_ID, authenticationSession.getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set
+ event.detail(Details.CODE_ID, authenticationSession.getParentSession().getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set
// the user has successfully logged in and we can clear his/her previous login failure attempts.
logSuccess();
return AuthenticationManager.finishedRequiredActions(session, authenticationSession, userSession, connection, request, uriInfo, event);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
index 35854d0..a0bf4d3 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java
@@ -123,18 +123,17 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
.user(existingUser)
.detail(Details.USERNAME, existingUser.getUsername())
.detail(Details.EMAIL, existingUser.getEmail())
- .detail(Details.CODE_ID, authSession.getId())
+ .detail(Details.CODE_ID, authSession.getParentSession().getId())
.removeDetail(Details.AUTH_METHOD)
.removeDetail(Details.AUTH_TYPE);
IdpVerifyAccountLinkActionToken token = new IdpVerifyAccountLinkActionToken(
- existingUser.getId(), absoluteExpirationInSecs, authSession.getId(),
+ existingUser.getId(), absoluteExpirationInSecs, authSession.getParentSession().getId(), authSession.getClient().getId(),
brokerContext.getUsername(), brokerContext.getIdpConfig().getAlias()
);
- UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
+ UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId());
String link = builder
.queryParam(Constants.EXECUTION, context.getExecution().getId())
- .queryParam(Constants.CLIENT_ID, context.getExecution().getId())
.build(realm.getName()).toString();
long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs);
diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
index d48d3b8..5c87908 100755
--- a/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/authenticators/resetcred/ResetCredentialEmail.java
@@ -89,7 +89,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
// We send the secret in the email in a link as a query param.
- ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authenticationSession.getId());
+ ResetCredentialsActionToken token = new ResetCredentialsActionToken(user.getId(), absoluteExpirationInSecs, authenticationSession.getParentSession().getId());
String link = UriBuilder
.fromUri(context.getActionTokenUrl(token.serialize(context.getSession(), context.getRealm(), context.getUriInfo())))
.build()
@@ -101,7 +101,7 @@ public class ResetCredentialEmail implements Authenticator, AuthenticatorFactory
event.clone().event(EventType.SEND_RESET_PASSWORD)
.user(user)
.detail(Details.USERNAME, username)
- .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, authenticationSession.getId()).success();
+ .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, authenticationSession.getParentSession().getId()).success();
context.forkWithSuccessMessage(new FormMessage(Messages.EMAIL_SENT));
} catch (EmailException e) {
event.clone().event(EventType.SEND_RESET_PASSWORD)
diff --git a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
index e87652a..4b001df 100755
--- a/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
+++ b/services/src/main/java/org/keycloak/authentication/RequiredActionContextResult.java
@@ -149,7 +149,7 @@ public class RequiredActionContextResult implements RequiredActionContext {
@Override
public String generateCode() {
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, getRealm(), getAuthenticationSession());
- authenticationSession.setTimestamp(Time.currentTime());
+ authenticationSession.getParentSession().setTimestamp(Time.currentTime());
return accessCode.getOrGenerateCode();
}
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 4208308..e061a3d 100755
--- a/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
+++ b/services/src/main/java/org/keycloak/authentication/requiredactions/VerifyEmail.java
@@ -134,8 +134,8 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
int validityInSecs = realm.getActionTokenGeneratedByUserLifespan(VerifyEmailActionToken.TOKEN_TYPE);
int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
- VerifyEmailActionToken token = new VerifyEmailActionToken(user.getId(), absoluteExpirationInSecs, authSession.getId(), user.getEmail());
- UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo));
+ VerifyEmailActionToken token = new VerifyEmailActionToken(user.getId(), absoluteExpirationInSecs, authSession.getParentSession().getId(), user.getEmail());
+ UriBuilder builder = Urls.actionTokenBuilder(uriInfo.getBaseUri(), token.serialize(session, realm, uriInfo), authSession.getClient().getClientId());
String link = builder.build(realm.getName()).toString();
long expirationInMinutes = TimeUnit.SECONDS.toMinutes(validityInSecs);
diff --git a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
index e3903a8..e74bc11 100644
--- a/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
+++ b/services/src/main/java/org/keycloak/authorization/admin/PolicyEvaluationService.java
@@ -236,7 +236,8 @@ public class PolicyEvaluationService {
ClientModel clientModel = realm.getClientById(clientId);
String id = KeycloakModelUtils.generateId();
- AuthenticationSessionModel authSession = keycloakSession.authenticationSessions().createAuthenticationSession(id, realm, clientModel);
+ AuthenticationSessionModel authSession = keycloakSession.authenticationSessions().createRootAuthenticationSession(id, realm)
+ .createAuthenticationSession(clientModel);
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setAuthenticatedUser(userModel);
userSession = keycloakSession.sessions().createUserSession(id, realm, userModel, userModel.getUsername(), "127.0.0.1", "passwd", false, null, null);
diff --git a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
index abae0b8..edeb68d 100755
--- a/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
+++ b/services/src/main/java/org/keycloak/protocol/AuthorizationEndpointBase.java
@@ -40,6 +40,7 @@ import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
@@ -108,7 +109,7 @@ public abstract class AuthorizationEndpointBase {
AuthenticationFlowModel flow = getAuthenticationFlow();
String flowId = flow.getId();
AuthenticationProcessor processor = createProcessor(authSession, flowId, LoginActionsService.AUTHENTICATE_PATH);
- event.detail(Details.CODE_ID, authSession.getId());
+ event.detail(Details.CODE_ID, authSession.getParentSession().getId());
if (isPassive) {
// OIDC prompt == NONE or SAML 2 IsPassive flag
// This means that client is just checking if the user is already completely logged in.
@@ -168,45 +169,61 @@ public abstract class AuthorizationEndpointBase {
protected AuthorizationEndpointChecks getOrCreateAuthenticationSession(ClientModel client, String requestState) {
AuthenticationSessionManager manager = new AuthenticationSessionManager(session);
String authSessionId = manager.getCurrentAuthenticationSessionId(realm);
- AuthenticationSessionModel authSession = authSessionId==null ? null : session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+ RootAuthenticationSessionModel rootAuthSession = authSessionId==null ? null : session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId);
+ AuthenticationSessionModel authSession;
- if (authSession != null) {
+ if (rootAuthSession != null) {
- ClientSessionCode<AuthenticationSessionModel> check = new ClientSessionCode<>(session, realm, authSession);
- if (!check.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
+ authSession = rootAuthSession.getAuthenticationSession(client);
- logger.debugf("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
- authSession.restartSession(realm, client);
- return new AuthorizationEndpointChecks(authSession);
+ if (authSession != null) {
+ ClientSessionCode<AuthenticationSessionModel> check = new ClientSessionCode<>(session, realm, authSession);
+ if (!check.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
- } else if (isNewRequest(authSession, client, requestState)) {
- // Check if we have lastProcessedExecution note or if some request parameter beside state (eg. prompt, kc_idp_hint) changed. 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 (shouldRestartAuthSession(authSession)) {
- logger.debug("New request from application received, but authentication session already exists. Restart existing authentication session");
- authSession.restartSession(realm, client);
- } else {
- 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);
- }
+ logger.debugf("Authentication session '%s' exists, but is expired. Restart existing authentication session", rootAuthSession.getId());
+ rootAuthSession.restartSession(realm);
+ authSession = rootAuthSession.createAuthenticationSession(client);
- return new AuthorizationEndpointChecks(authSession);
+ return new AuthorizationEndpointChecks(authSession);
- } else {
- logger.debug("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
+ } else if (isNewRequest(authSession, client, requestState)) {
+ // Check if we have lastProcessedExecution note or if some request parameter beside state (eg. prompt, kc_idp_hint) changed. 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 (shouldRestartAuthSession(authSession)) {
+ logger.debugf("New request from application received, but authentication session '%s' already exists and has client '%s'. Restart child authentication session for client.",
+ rootAuthSession.getId(), client.getClientId());
+
+ authSession = rootAuthSession.createAuthenticationSession(client);
+
+ } else {
+ logger.debugf("New request from application received, but authentication session '%s' already exists and has client '%s'. Update client information in existing authentication session.",
+ rootAuthSession.getId(), client.getClientId());
+ authSession.clearClientNotes();
+ }
- // 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)) {
return new AuthorizationEndpointChecks(authSession);
- } else {
- CacheControlUtil.noBackButtonCacheControlHeader();
- Response response = new AuthenticationFlowURLHelper(session, realm, uriInfo)
- .showPageExpired(authSession);
- return new AuthorizationEndpointChecks(response);
+ } else {
+ 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)) {
+ return new AuthorizationEndpointChecks(authSession);
+ } else {
+ CacheControlUtil.noBackButtonCacheControlHeader();
+
+ Response response = new AuthenticationFlowURLHelper(session, realm, uriInfo)
+ .showPageExpired(authSession);
+ return new AuthorizationEndpointChecks(response);
+ }
}
+ } else {
+ logger.debugf("Sent request to authz endpoint. Authentication session with ID '%s' exists, but doesn't have client: '%s' . Adding client to authentication session",
+ rootAuthSession.getId(), client.getClientId());
+
+ authSession = rootAuthSession.createAuthenticationSession(client);
+ return new AuthorizationEndpointChecks(authSession);
}
}
@@ -214,10 +231,12 @@ public abstract class AuthorizationEndpointBase {
if (userSession != null) {
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);
+ rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(authSessionId, realm);
+ authSession = rootAuthSession.createAuthenticationSession(client);
} else {
- authSession = manager.createAuthenticationSession(realm, client, true);
- logger.debugf("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
+ rootAuthSession = manager.createAuthenticationSession(realm, true);
+ authSession = rootAuthSession.createAuthenticationSession(client);
+ logger.debugf("Sent request to authz endpoint. Created new authentication session with ID '%s'", rootAuthSession.getId());
}
return new AuthorizationEndpointChecks(authSession);
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 94c4d24..0e8d36e 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
@@ -80,6 +80,7 @@ import org.keycloak.services.resources.admin.AdminAuth;
import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.util.TokenUtil;
import org.keycloak.utils.ProfileHelper;
@@ -255,7 +256,7 @@ public class TokenEndpoint {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
}
- ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, event, AuthenticatedClientSessionModel.class);
+ ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, client, event, AuthenticatedClientSessionModel.class);
if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
@@ -469,7 +470,9 @@ public class TokenEndpoint {
}
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
- AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, false);
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
+ AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
+
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setAction(AuthenticatedClientSessionModel.Action.AUTHENTICATE.name());
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
@@ -553,13 +556,16 @@ public class TokenEndpoint {
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
- AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, false);
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
+ AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
+
authSession.setAuthenticatedUser(clientUser);
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
- UserSessionModel userSession = session.sessions().createUserSession(authSession.getId(), realm, clientUser, clientUsername, clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
+ UserSessionModel userSession = session.sessions().createUserSession(authSession.getParentSession().getId(), realm, clientUser, clientUsername,
+ clientConnection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null);
event.session(userSession);
AuthenticationManager.setRolesAndMappersInSession(authSession);
@@ -763,7 +769,9 @@ public class TokenEndpoint {
String scope = formParams.getFirst(OAuth2Constants.SCOPE);
- AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, targetClient, false);
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
+ AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(targetClient);
+
authSession.setAuthenticatedUser(targetUser);
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));
diff --git a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
index b4e037b..59cd0b9 100644
--- a/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
+++ b/services/src/main/java/org/keycloak/protocol/RestartLoginCookie.java
@@ -31,6 +31,7 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie;
@@ -143,7 +144,8 @@ public class RestartLoginCookie {
}
- public static AuthenticationSessionModel restartSession(KeycloakSession session, RealmModel realm) throws Exception {
+ public static AuthenticationSessionModel restartSession(KeycloakSession session, RealmModel realm,
+ RootAuthenticationSessionModel rootSession, String expectedClientId) throws Exception {
Cookie cook = session.getContext().getRequestHeaders().getCookies().get(KC_RESTART);
if (cook == null) {
logger.debug("KC_RESTART cookie doesn't exist");
@@ -161,7 +163,18 @@ public class RestartLoginCookie {
ClientModel client = realm.getClientByClientId(cookie.getClientId());
if (client == null) return null;
- AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
+ // Restart just if client from cookie matches client from the URL.
+ if (!client.getClientId().equals(expectedClientId)) {
+ logger.debugf("Skip restarting from the KC_RESTART. Clients doesn't match: Cookie client: %s, Requested client: %s", client.getClientId(), expectedClientId);
+ return null;
+ }
+
+ // Need to create brand new session and setup cookie
+ if (rootSession == null) {
+ rootSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, true);
+ }
+
+ AuthenticationSessionModel authSession = rootSession.createAuthenticationSession(client);
authSession.setProtocol(cookie.getAuthMethod());
authSession.setRedirectUri(cookie.getRedirectUri());
authSession.setAction(cookie.getAction());
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 8791010..649c77d 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -56,6 +56,7 @@ import org.keycloak.services.util.CookieHelper;
import org.keycloak.services.util.P3PHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import javax.crypto.SecretKey;
import javax.ws.rs.core.Cookie;
@@ -215,16 +216,20 @@ public class AuthenticationManager {
}
private static AuthenticationSessionModel createOrJoinLogoutSession(RealmModel realm, final AuthenticationSessionManager asm, boolean browserCookie) {
+ // Account management client is used as a placeholder
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
- AuthenticationSessionModel logoutAuthSession = asm.getCurrentAuthenticationSession(realm);
+
+ AuthenticationSessionModel logoutAuthSession = asm.getCurrentAuthenticationSession(realm, client);
// Try to join existing logout session if it exists and browser session is required
if (browserCookie && logoutAuthSession != null) {
if (Objects.equals(AuthenticationSessionModel.Action.LOGGING_OUT.name(), logoutAuthSession.getAction())) {
return logoutAuthSession;
}
- logoutAuthSession.restartSession(realm, client);
+ // Re-create the authentication session for logout
+ logoutAuthSession = logoutAuthSession.getParentSession().createAuthenticationSession(client);
} else {
- logoutAuthSession = asm.createAuthenticationSession(realm, client, browserCookie);
+ RootAuthenticationSessionModel rootLogoutSession = asm.createAuthenticationSession(realm, browserCookie);
+ logoutAuthSession = rootLogoutSession.createAuthenticationSession(client);
}
logoutAuthSession.setAction(AuthenticationSessionModel.Action.LOGGING_OUT.name());
return logoutAuthSession;
@@ -381,8 +386,8 @@ public class AuthenticationManager {
/**
* Sets logout state of the particular client into the {@code logoutAuthSession}
* @param logoutAuthSession logoutAuthSession. May be {@code null} in which case this is a no-op.
- * @param client Client. Must not be {@code null}
- * @param state
+ * @param clientUuid Client. Must not be {@code null}
+ * @param action
*/
public static void setClientLogoutAction(AuthenticationSessionModel logoutAuthSession, String clientUuid, AuthenticationSessionModel.Action action) {
if (logoutAuthSession != null && clientUuid != null) {
@@ -479,8 +484,11 @@ public class AuthenticationManager {
}
public static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
+ // Account management client is used as a placeholder
+ ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
+
final AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
- AuthenticationSessionModel logoutAuthSession = asm.getCurrentAuthenticationSession(realm);
+ AuthenticationSessionModel logoutAuthSession = asm.getCurrentAuthenticationSession(realm, client);
checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession);
expireIdentityCookie(realm, uriInfo, connection);
@@ -832,7 +840,7 @@ public class AuthenticationManager {
logger.debugv("processAccessCode: go to oauth page?: {0}", client.isConsentRequired());
- event.detail(Details.CODE_ID, authSession.getId());
+ event.detail(Details.CODE_ID, authSession.getParentSession().getId());
Set<String> requiredActions = user.getRequiredActions();
Response action = executionActions(session, authSession, request, event, realm, user, requiredActions);
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 ac6259e..8a625b3 100644
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationSessionManager.java
@@ -28,6 +28,7 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.services.util.CookieHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.sessions.StickySessionEncoderProvider;
/**
@@ -45,22 +46,22 @@ public class AuthenticationSessionManager {
this.session = session;
}
+
/**
- * Creates a fresh authentication session for the given realm and client. Optionally sets the browser
+ * Creates a fresh authentication session for the given realm . Optionally sets the browser
* authentication session cookie {@link #AUTH_SESSION_ID} with the ID of the new session.
* @param realm
- * @param client
* @param browserCookie Set the cookie in the browser for the
* @return
*/
- public AuthenticationSessionModel createAuthenticationSession(RealmModel realm, ClientModel client, boolean browserCookie) {
- AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client);
+ public RootAuthenticationSessionModel createAuthenticationSession(RealmModel realm, boolean browserCookie) {
+ RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm);
if (browserCookie) {
- setAuthSessionCookie(authSession.getId(), realm);
+ setAuthSessionCookie(rootAuthSession.getId(), realm);
}
- return authSession;
+ return rootAuthSession;
}
@@ -73,14 +74,20 @@ public class AuthenticationSessionManager {
return getAuthSessionCookieDecoded(realm);
}
+
/**
* Returns current authentication session if it exists, otherwise returns {@code null}.
* @param realm
* @return
*/
- public AuthenticationSessionModel getCurrentAuthenticationSession(RealmModel realm) {
+ public AuthenticationSessionModel getCurrentAuthenticationSession(RealmModel realm, ClientModel client) {
String authSessionId = getAuthSessionCookieDecoded(realm);
- return authSessionId==null ? null : session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
+
+ if (authSessionId == null) {
+ return null;
+ }
+
+ return getAuthenticationSessionByIdAndClient(realm, authSessionId, client);
}
@@ -124,8 +131,10 @@ public class AuthenticationSessionManager {
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession, boolean expireRestartCookie) {
- log.debugf("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
- session.authenticationSessions().removeAuthenticationSession(realm, authSession);
+ RootAuthenticationSessionModel rootAuthSession = authSession.getParentSession();
+
+ log.debugf("Removing authSession '%s'. Expire restart cookie: %b", rootAuthSession.getId(), expireRestartCookie);
+ session.authenticationSessions().removeRootAuthenticationSession(realm, rootAuthSession);
// expire restart cookie
if (expireRestartCookie) {
@@ -138,7 +147,14 @@ public class AuthenticationSessionManager {
// Check to see if we already have authenticationSession with same ID
public UserSessionModel getUserSession(AuthenticationSessionModel authSession) {
- return session.sessions().getUserSession(authSession.getRealm(), authSession.getId());
+ return session.sessions().getUserSession(authSession.getRealm(), authSession.getParentSession().getId());
+ }
+
+
+ // Don't look at cookie. Just lookup authentication session based on the ID and client. Return null if not found
+ public AuthenticationSessionModel getAuthenticationSessionByIdAndClient(RealmModel realm, String authSessionId, ClientModel client) {
+ RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId);
+ return rootAuthSession==null ? null : rootAuthSession.getAuthenticationSession(client);
}
}
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 503973e..2acf683 100755
--- a/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
+++ b/services/src/main/java/org/keycloak/services/managers/ClientSessionCode.java
@@ -80,7 +80,8 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
}
}
- public static <CLIENT_SESSION extends CommonClientSessionModel> ParseResult<CLIENT_SESSION> parseResult(String code, KeycloakSession session, RealmModel realm, EventBuilder event, Class<CLIENT_SESSION> sessionClass) {
+ public static <CLIENT_SESSION extends CommonClientSessionModel> ParseResult<CLIENT_SESSION> parseResult(String code, KeycloakSession session, RealmModel realm, ClientModel client,
+ EventBuilder event, Class<CLIENT_SESSION> sessionClass) {
ParseResult<CLIENT_SESSION> result = new ParseResult<>();
if (code == null) {
result.illegalHash = true;
@@ -88,7 +89,7 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
}
try {
CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser = CodeGenerateUtil.getParser(sessionClass);
- result.clientSession = getClientSession(code, session, realm, event, clientSessionParser);
+ result.clientSession = getClientSession(code, session, realm, client, event, clientSessionParser);
if (result.clientSession == null) {
result.authSessionNotFound = true;
return result;
@@ -113,15 +114,16 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
}
- public static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event, Class<CLIENT_SESSION> sessionClass) {
+ public static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, ClientModel client,
+ EventBuilder event, Class<CLIENT_SESSION> sessionClass) {
CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser = CodeGenerateUtil.getParser(sessionClass);
- return getClientSession(code, session, realm, event, clientSessionParser);
+ return getClientSession(code, session, realm, client, event, clientSessionParser);
}
- private static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event,
+ private static <CLIENT_SESSION extends CommonClientSessionModel> CLIENT_SESSION getClientSession(String code, KeycloakSession session, RealmModel realm, ClientModel client, EventBuilder event,
CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser) {
- return clientSessionParser.parseSession(code, session, realm, event);
+ return clientSessionParser.parseSession(code, session, realm, client, event);
}
@@ -135,7 +137,8 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
}
public boolean isActionActive(ActionType actionType) {
- int timestamp = commonLoginSession.getTimestamp();
+ CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser = (CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION>) CodeGenerateUtil.getParser(commonLoginSession.getClass());
+ int timestamp = clientSessionParser.getTimestamp(commonLoginSession);
int lifespan;
switch (actionType) {
@@ -210,7 +213,9 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
public void setAction(String action) {
commonLoginSession.setAction(action);
- commonLoginSession.setTimestamp(Time.currentTime());
+
+ CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION> clientSessionParser = (CodeGenerateUtil.ClientSessionParser<CLIENT_SESSION>) CodeGenerateUtil.getParser(commonLoginSession.getClass());
+ clientSessionParser.setTimestamp(commonLoginSession, Time.currentTime());
}
public String getOrGenerateCode() {
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 de141ff..8ea2450 100644
--- a/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
+++ b/services/src/main/java/org/keycloak/services/managers/CodeGenerateUtil.java
@@ -32,6 +32,7 @@ import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.models.AuthenticatedClientSessionModel;
+import org.keycloak.models.ClientModel;
import org.keycloak.models.CodeToTokenStoreProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@@ -78,7 +79,7 @@ class CodeGenerateUtil {
interface ClientSessionParser<CS extends CommonClientSessionModel> {
- CS parseSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event);
+ CS parseSession(String code, KeycloakSession session, RealmModel realm, ClientModel client, EventBuilder event);
String retrieveCode(KeycloakSession session, CS clientSession);
@@ -88,6 +89,9 @@ class CodeGenerateUtil {
boolean isExpired(KeycloakSession session, String code, CS clientSession);
+ int getTimestamp(CS clientSession);
+ void setTimestamp(CS clientSession, int timestamp);
+
}
@@ -97,9 +101,9 @@ class CodeGenerateUtil {
private static class AuthenticationSessionModelParser implements ClientSessionParser<AuthenticationSessionModel> {
@Override
- public AuthenticationSessionModel parseSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event) {
+ public AuthenticationSessionModel parseSession(String code, KeycloakSession session, RealmModel realm, ClientModel client, EventBuilder event) {
// Read authSessionID from cookie. Code is ignored for now
- return new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
+ return new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm, client);
}
@Override
@@ -141,6 +145,16 @@ class CodeGenerateUtil {
public boolean isExpired(KeycloakSession session, String code, AuthenticationSessionModel clientSession) {
return false;
}
+
+ @Override
+ public int getTimestamp(AuthenticationSessionModel clientSession) {
+ return clientSession.getParentSession().getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(AuthenticationSessionModel clientSession, int timestamp) {
+ clientSession.getParentSession().setTimestamp(timestamp);
+ }
}
@@ -149,7 +163,7 @@ class CodeGenerateUtil {
private CodeJWT codeJWT;
@Override
- public AuthenticatedClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm, EventBuilder event) {
+ public AuthenticatedClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm, ClientModel client, EventBuilder event) {
SecretKey aesKey = session.keys().getActiveAesKey(realm).getSecretKey();
SecretKey hmacKey = session.keys().getActiveHmacKey(realm).getSecretKey();
@@ -241,6 +255,16 @@ class CodeGenerateUtil {
public boolean isExpired(KeycloakSession session, String code, AuthenticatedClientSessionModel clientSession) {
return !codeJWT.isActive();
}
+
+ @Override
+ public int getTimestamp(AuthenticatedClientSessionModel clientSession) {
+ return clientSession.getTimestamp();
+ }
+
+ @Override
+ public void setTimestamp(AuthenticatedClientSessionModel clientSession, int timestamp) {
+ clientSession.setTimestamp(timestamp);
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
index 99e6ea3..d40dc61 100755
--- a/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
+++ b/services/src/main/java/org/keycloak/services/resources/account/AccountFormService.java
@@ -52,6 +52,7 @@ import org.keycloak.services.Urls;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.AbstractSecuredLocalService;
@@ -60,6 +61,7 @@ import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import org.keycloak.storage.ReadOnlyException;
import org.keycloak.util.JsonSerialization;
@@ -179,7 +181,7 @@ public class AccountFormService extends AbstractSecuredLocalService {
setReferrerOnPage();
UserSessionModel userSession = auth.getSession();
- AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, userSession.getId());
+ AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getAuthenticationSessionByIdAndClient(realm, userSession.getId(), client);
if (authSession != null) {
String forwardedError = authSession.getAuthNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE);
if (forwardedError != null) {
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 0f3d307..611f05d 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -292,7 +292,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
// Create AuthenticationSessionModel with same ID like userSession and refresh cookie
UserSessionModel userSession = cookieResult.getSession();
- AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(userSession.getId(), realmModel, client);
+ AuthenticationSessionModel authSession = session.authenticationSessions().createRootAuthenticationSession(userSession.getId(), realmModel).createAuthenticationSession(client);
new AuthenticationSessionManager(session).setAuthSessionCookie(userSession.getId(), realmModel);
ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
@@ -588,7 +588,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
AuthenticationSessionModel authSession = clientSessionCode.getClientSession();
try {
- this.event.detail(Details.CODE_ID, authSession.getId())
+ this.event.detail(Details.CODE_ID, authSession.getParentSession().getId())
.removeDetail("auth_method");
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
@@ -685,7 +685,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
logger.debugf("Redirect to postBrokerLogin flow after authentication with identityProvider '%s'.", context.getIdpConfig().getAlias());
- authSession.setTimestamp(Time.currentTime());
+ authSession.getParentSession().setTimestamp(Time.currentTime());
SerializedBrokeredIdentityContext ctx = SerializedBrokeredIdentityContext.serialize(context);
ctx.saveToAuthenticationSession(authSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
@@ -789,7 +789,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
if (nextRequiredAction != null) {
return AuthenticationManager.redirectToRequiredActions(session, realmModel, authSession, uriInfo, nextRequiredAction);
} else {
- event.detail(Details.CODE_ID, authSession.getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set
+ event.detail(Details.CODE_ID, authSession.getParentSession().getId()); // todo This should be set elsewhere. find out why tests fail. Don't know where this is supposed to be set
return AuthenticationManager.finishedRequiredActions(session, authSession, null, clientConnection, request, uriInfo, event);
}
}
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 8aaca57..d707e03 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -72,6 +72,7 @@ import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@@ -333,7 +334,8 @@ public class LoginActionsService {
public Response resetCredentialsGET(@QueryParam("code") String code,
@QueryParam("execution") String execution,
@QueryParam("client_id") String clientId) {
- AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
+ ClientModel client = realm.getClientByClientId(clientId);
+ AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm, client);
// we allow applications to link to reset credentials without going through OAuth or SAML handshakes
if (authSession == null && code == null) {
@@ -357,7 +359,10 @@ public class LoginActionsService {
// set up the account service as the endpoint to call.
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
- authSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, client, true);
+
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, true);
+ authSession = rootAuthSession.createAuthenticationSession(client);
+
authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
//authSession.setNote(AuthenticationManager.END_AFTER_REQUIRED_ACTIONS, "true");
authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
@@ -413,9 +418,8 @@ public class LoginActionsService {
ActionTokenContext<T> tokenContext;
String eventError = null;
String defaultErrorMessage = null;
- AuthenticationSessionModel authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm);
- event.event(EventType.EXECUTE_ACTION_TOKEN);
+ AuthenticationSessionModel authSession = null;
// Setup client, so error page will contain "back to application" link
ClientModel client = null;
@@ -424,8 +428,11 @@ public class LoginActionsService {
}
if (client != null) {
session.getContext().setClient(client);
+ authSession = new AuthenticationSessionManager(session).getCurrentAuthenticationSession(realm, client);
}
+ event.event(EventType.EXECUTE_ACTION_TOKEN);
+
// First resolve action token handler
try {
if (tokenString == null) {
@@ -500,7 +507,7 @@ public class LoginActionsService {
authSession = handler.startFreshAuthenticationSession(token, tokenContext);
tokenContext.setAuthenticationSession(authSession, true);
} else if (tokenAuthSessionId == null ||
- ! LoginActionsServiceChecks.doesAuthenticationSessionFromCookieMatchOneFromToken(tokenContext, tokenAuthSessionId)) {
+ ! LoginActionsServiceChecks.doesAuthenticationSessionFromCookieMatchOneFromToken(tokenContext, tokenAuthSessionId, client)) {
// There exists an authentication session but no auth session ID was received in the action token
logger.debugf("Authentication session in progress but no authentication session ID was found in action token %s, restarting.", token.getId());
new AuthenticationSessionManager(session).removeAuthenticationSession(realm, authSession, false);
@@ -737,7 +744,7 @@ public class LoginActionsService {
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());
+ authSession.getParentSession().setTimestamp(Time.currentTime());
String clientId = authSession.getClient().getClientId();
URI redirect = firstBrokerLogin ? Urls.identityProviderAfterFirstBrokerLogin(uriInfo.getBaseUri(), realm.getName(), accessCode.getOrGenerateCode(), clientId) :
@@ -816,7 +823,7 @@ public class LoginActionsService {
OIDCResponseMode responseMode = OIDCResponseMode.parse(respMode, OIDCResponseType.parse(responseType));
event.event(EventType.LOGIN).client(authSession.getClient())
- .detail(Details.CODE_ID, authSession.getId())
+ .detail(Details.CODE_ID, authSession.getParentSession().getId())
.detail(Details.REDIRECT_URI, authSession.getRedirectUri())
.detail(Details.AUTH_METHOD, authSession.getProtocol())
.detail(Details.RESPONSE_TYPE, responseType)
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
index 345f64a..f0e7ef0 100644
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsServiceChecks.java
@@ -35,6 +35,8 @@ import org.keycloak.sessions.CommonClientSessionModel.Action;
import java.util.Objects;
import java.util.function.Consumer;
import org.jboss.logging.Logger;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
+
/**
*
* @author hmlnarik
@@ -75,7 +77,6 @@ public class LoginActionsServiceChecks {
* If there is an action required in the session, furthermore it is not the expected one, and the required
* action is redirection to "required actions", it throws with response performing the redirect to required
* actions.
- * @param <T>
*/
public static class IsActionRequired implements Predicate<JsonWebToken> {
@@ -250,7 +251,7 @@ public class LoginActionsServiceChecks {
*
* @param <T>
*/
- public static <T extends JsonWebToken> boolean doesAuthenticationSessionFromCookieMatchOneFromToken(ActionTokenContext<T> context, String authSessionIdFromToken) throws VerificationException {
+ public static <T extends JsonWebToken> boolean doesAuthenticationSessionFromCookieMatchOneFromToken(ActionTokenContext<T> context, String authSessionIdFromToken, ClientModel client) throws VerificationException {
if (authSessionIdFromToken == null) {
return false;
}
@@ -262,9 +263,8 @@ public class LoginActionsServiceChecks {
return false;
}
- AuthenticationSessionModel authSessionFromCookie = context.getSession()
- .authenticationSessions().getAuthenticationSession(context.getRealm(), authSessionIdFromCookie);
- if (authSessionFromCookie == null) { // Cookie contains ID of expired auth session
+ AuthenticationSessionModel authSessionFromCookie = asm.getAuthenticationSessionByIdAndClient(context.getRealm(), authSessionIdFromCookie, client);
+ if (authSessionFromCookie == null) { // Not our client in root session
return false;
}
@@ -278,14 +278,16 @@ public class LoginActionsServiceChecks {
return false;
}
- AuthenticationSessionModel authSessionFromParent = context.getSession()
- .authenticationSessions().getAuthenticationSession(context.getRealm(), parentSessionId);
+ AuthenticationSessionModel authSessionFromParent = asm.getAuthenticationSessionByIdAndClient(context.getRealm(), parentSessionId, client);
+ if (authSessionFromParent == null) {
+ return false;
+ }
// It's the correct browser. Let's remove forked session as we won't continue
// from the login form (browser flow) but from the token's flow
// Don't expire KC_RESTART cookie at this point
asm.removeAuthenticationSession(context.getRealm(), authSessionFromCookie, false);
- LOG.debugf("Removed forked session: %s", authSessionFromCookie.getId());
+ LOG.debugf("Removed forked session: %s", authSessionFromCookie.getParentSession().getId());
// Refresh browser cookie
asm.setAuthSessionCookie(parentSessionId, context.getRealm());
diff --git a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
index 3624b53..90dd186 100644
--- a/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
+++ b/services/src/main/java/org/keycloak/services/resources/SessionCodeChecks.java
@@ -47,6 +47,7 @@ import org.keycloak.services.messages.Messages;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
public class SessionCodeChecks {
@@ -132,12 +133,6 @@ public class SessionCodeChecks {
return null;
}
- // object retrieve
- AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, event, AuthenticationSessionModel.class);
- if (authSession != null) {
- return authSession;
- }
-
// Setup client to be shown on error/info page based on "client_id" parameter
logger.debugf("Will use client '%s' in back-to-application link", clientId);
ClientModel client = null;
@@ -148,8 +143,16 @@ public class SessionCodeChecks {
session.getContext().setClient(client);
}
+ // object retrieve
+ AuthenticationSessionManager authSessionManager = new AuthenticationSessionManager(session);
+ AuthenticationSessionModel authSession = authSessionManager.getCurrentAuthenticationSession(realm, client);
+ if (authSession != null) {
+ return authSession;
+ }
+
// See if we are already authenticated and userSession with same ID exists.
- String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
+ String sessionId = authSessionManager.getCurrentAuthenticationSessionId(realm);
+ RootAuthenticationSessionModel existingRootAuthSession = null;
if (sessionId != null) {
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession != null) {
@@ -164,10 +167,13 @@ public class SessionCodeChecks {
response = loginForm.createInfoPage();
return null;
}
+
+
+ existingRootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, sessionId);
}
// Otherwise just try to restart from the cookie
- response = restartAuthenticationSessionFromCookie();
+ response = restartAuthenticationSessionFromCookie(existingRootAuthSession);
return null;
}
@@ -186,7 +192,7 @@ public class SessionCodeChecks {
}
// Client checks
- event.detail(Details.CODE_ID, authSession.getId());
+ event.detail(Details.CODE_ID, authSession.getParentSession().getId());
ClientModel client = authSession.getClient();
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
@@ -240,7 +246,7 @@ public class SessionCodeChecks {
return false;
}
} else {
- ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, event, AuthenticationSessionModel.class);
+ ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, client, event, AuthenticationSessionModel.class);
clientCode = result.getCode();
if (clientCode == null) {
@@ -341,11 +347,12 @@ public class SessionCodeChecks {
}
- private Response restartAuthenticationSessionFromCookie() {
+ private Response restartAuthenticationSessionFromCookie(RootAuthenticationSessionModel existingRootSession) {
logger.debug("Authentication session not found. Trying to restart from cookie.");
AuthenticationSessionModel authSession = null;
+
try {
- authSession = RestartLoginCookie.restartSession(session, realm);
+ authSession = RestartLoginCookie.restartSession(session, realm, existingRootSession, clientId);
} catch (Exception e) {
ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
}
diff --git a/services/src/main/java/org/keycloak/services/Urls.java b/services/src/main/java/org/keycloak/services/Urls.java
index 6e954f9..b14ef06 100755
--- a/services/src/main/java/org/keycloak/services/Urls.java
+++ b/services/src/main/java/org/keycloak/services/Urls.java
@@ -182,9 +182,11 @@ public class Urls {
return loginResetCredentialsBuilder(baseUri).build(realmName);
}
- public static UriBuilder actionTokenBuilder(URI baseUri, String tokenString) {
+ public static UriBuilder actionTokenBuilder(URI baseUri, String tokenString, String clientId) {
return loginActionsBase(baseUri).path(LoginActionsService.class, "executeActionToken")
- .queryParam("key", tokenString);
+ .queryParam("key", tokenString)
+ .queryParam(Constants.CLIENT_ID, clientId);
+
}
public static UriBuilder loginResetCredentialsBuilder(URI baseUri) {
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 d4c67a7..432bc7b 100755
--- a/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/social/twitter/TwitterIdentityProvider.java
@@ -25,6 +25,7 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.provider.ExchangeTokenToIdentityProviderToken;
import org.keycloak.broker.provider.IdentityProvider;
+import org.keycloak.broker.provider.util.IdentityBrokerState;
import org.keycloak.broker.social.SocialIdentityProvider;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.Details;
@@ -197,7 +198,9 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
twitter.setOAuthConsumer(getConfig().getClientId(), getConfig().getClientSecret());
- authSession = ClientSessionCode.getClientSession(state, session, realm, event, AuthenticationSessionModel.class);
+ String clientId = IdentityBrokerState.encoded(state).getClientId();
+ ClientModel client = realm.getClientByClientId(clientId);
+ authSession = ClientSessionCode.getClientSession(state, session, realm, client, event, AuthenticationSessionModel.class);
String twitterToken = authSession.getAuthNote(TWITTER_TOKEN);
String twitterSecret = authSession.getAuthNote(TWITTER_TOKENSECRET);
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java
index add2a89..415cfd1 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/MailUtils.java
@@ -56,13 +56,14 @@ public class MailUtils {
assertEquals("text/html; charset=UTF-8", htmlContentType);
final String htmlBody = (String) multipart.getBodyPart(1).getContent();
+ final String htmlChangePwdUrl = MailUtils.getLink(htmlBody);
// .replace() accounts for escaping the ampersand
// It's not escaped in the html version because html retrieved from a
// message bundle is considered safe and it must be unescaped to display
// properly.
- final String htmlChangePwdUrl = MailUtils.getLink(htmlBody).replace("&", "&");
+ final String htmlChangePwdUrlToCompare = htmlChangePwdUrl.replace("&", "&");
- assertEquals(htmlChangePwdUrl, textChangePwdUrl);
+ assertEquals(htmlChangePwdUrlToCompare, textChangePwdUrl);
return htmlChangePwdUrl;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index 55dd453..e95d500 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -578,25 +578,7 @@ public class RequiredActionEmailVerificationTest extends AbstractTestRealmKeyclo
public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException {
- Multipart multipart = (Multipart) message.getContent();
-
- final String textContentType = multipart.getBodyPart(0).getContentType();
-
- assertEquals("text/plain; charset=UTF-8", textContentType);
-
- final String textBody = (String) multipart.getBodyPart(0).getContent();
- final String textChangePwdUrl = MailUtils.getLink(textBody);
-
- final String htmlContentType = multipart.getBodyPart(1).getContentType();
-
- assertEquals("text/html; charset=UTF-8", htmlContentType);
-
- final String htmlBody = (String) multipart.getBodyPart(1).getContent();
- final String htmlChangePwdUrl = MailUtils.getLink(htmlBody);
-
- assertEquals(htmlChangePwdUrl, textChangePwdUrl);
-
- return htmlChangePwdUrl;
+ return MailUtils.getPasswordResetEmailLink(message);
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/LastSessionRefreshCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/LastSessionRefreshCrossDCTest.java
index 714c8a3..c72932f 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/LastSessionRefreshCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/LastSessionRefreshCrossDCTest.java
@@ -159,8 +159,6 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
) {
- // TODO:mposolda Disable periodic cleaner now on all Keycloak nodes. Make sure it's re-enabled after finish
-
// Ensure to remove all current sessions and offline sessions
setTimeOffset(10000000);
getTestingClientForStartedNodeInDc(0).testing("test").removeExpired("test");
@@ -281,8 +279,6 @@ public class LastSessionRefreshCrossDCTest extends AbstractAdminCrossDCTest {
) throws Exception {
- // TODO:mposolda Disable periodic cleaner now on all Keycloak nodes. Make sure it's re-enabled after finish
-
// Ensure to remove all current sessions and offline sessions
setTimeOffset(10000000);
getTestingClientForStartedNodeInDc(0).testing("test").removeExpired("test");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java
index a81f30f..ac71037 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/crossdc/SessionExpirationCrossDCTest.java
@@ -630,23 +630,4 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
}
- @Test
- public void testClientRemoveAuthSessions(
- @JmxInfinispanChannelStatistics() InfinispanStatistics channelStatisticsCrossDc) throws Exception {
-
- createInitialAuthSessions();
-
- channelStatisticsCrossDc.reset();
-
- // Remove test-app client
- ApiUtil.findClientByClientId(getAdminClient().realm(REALM_NAME), "test-app").remove();
-
- // Assert sessions removed on node1 and node2 and on remote caches.
- assertAuthSessionsStatisticsExpected("After client removed", channelStatisticsCrossDc,
- 0);
- }
-
-
-
-
}
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 9d914bd..a276cdc 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
@@ -17,6 +17,7 @@
package org.keycloak.testsuite.forms;
+import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Rule;
@@ -281,5 +282,36 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
}
+ // KEYCLOAK-5797
+ @Test
+ public void loginWithDifferentClients() throws Exception {
+ // Open tab1 and start login here
+ oauth.openLoginForm();
+ loginPage.assertCurrent();
+ loginPage.login("login-test", "bad-password");
+ String tab1Url = driver.getCurrentUrl();
+
+ // Go to tab2 and start login with different client "root-url-client"
+ oauth.clientId("root-url-client");
+ oauth.redirectUri("http://localhost:8180/foo/bar/baz");
+ oauth.openLoginForm();
+ loginPage.assertCurrent();
+ String tab2Url = driver.getCurrentUrl();
+
+ // Go back to tab1 and finish login here
+ driver.navigate().to(tab1Url);
+ loginPage.login("login-test", "password");
+ updatePasswordPage.changePassword("password", "password");
+ updateProfilePage.update("John", "Doe3", "john@doe3.com");
+
+ // Assert I am redirected to the appPage in tab1
+ appPage.assertCurrent();
+
+ // Go back to tab2 and finish login here. Should be on the root-url-client page
+ driver.navigate().to(tab2Url);
+ String currentUrl = driver.getCurrentUrl();
+ Assert.assertThat(currentUrl, Matchers.startsWith("http://localhost:8180/foo/bar/baz"));
+ }
+
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java
index 6135c56..375070b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/RestartCookieTest.java
@@ -90,7 +90,7 @@ public class RestartCookieTest extends AbstractTestRealmKeycloakTest {
// KEYCLOAK-5440
@Test
- public void invalidLoginAndBackButton() throws IOException, MessagingException {
+ public void testRestartCookieBackwardsCompatible() throws IOException, MessagingException {
String oldRestartCookie = testingClient.server().fetchString((KeycloakSession session) -> {
try {
String cookieVal = OLD_RESTART_COOKIE_JSON.replace("\n", "").replace(" ", "");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/MailAssert.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/MailAssert.java
index 463c32f..0575317 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/MailAssert.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/MailAssert.java
@@ -50,6 +50,7 @@ public class MailAssert {
if (message.getContent() instanceof MimeMultipart) {
MimeMultipart mimeMultipart = (MimeMultipart) message.getContent();
+ // TEXT content is on index 0
messageContent = String.valueOf(mimeMultipart.getBodyPart(0).getContent());
} else {
messageContent = String.valueOf(message.getContent());
@@ -61,6 +62,9 @@ public class MailAssert {
assertTrue(errorMessage, messageContent.contains(content));
for (String string : messageContent.split("\n")) {
if (string.contains("http://")) {
+
+ // Ampersand escaped in the text version. Needs to be replaced to have correct URL
+ string = string.replace("&", "&");
return string;
}
}
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index e977aef..49b037b 100755
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -356,15 +356,16 @@ public abstract class AbstractIdentityProviderTest {
assertEquals("text/html; charset=UTF-8", htmlContentType);
final String htmlBody = (String) multipart.getBodyPart(1).getContent();
-
+
+ final String htmlChangePwdUrl = MailUtil.getLink(htmlBody);
// .replace() accounts for escaping the ampersand
// It's not escaped in the html version because html retrieved from a
// message bundle is considered safe and it must be unescaped to display
// properly.
- final String htmlVerificationUrl = MailUtil.getLink(htmlBody).replace("&", "&");
+ final String htmlChangePwdUrlToCompare = htmlChangePwdUrl.replace("&", "&");
- assertEquals(htmlVerificationUrl, textVerificationUrl);
+ assertEquals(htmlChangePwdUrlToCompare, textVerificationUrl);
- return htmlVerificationUrl;
+ return htmlChangePwdUrl;
}
}
diff --git a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
index db08e81..e28a976 100644
--- a/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
+++ b/testsuite/integration-deprecated/src/test/java/org/keycloak/testsuite/model/AuthenticationSessionProviderTest.java
@@ -32,6 +32,7 @@ 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.sessions.RootAuthenticationSessionModel;
import org.keycloak.testsuite.rule.KeycloakRule;
import static org.junit.Assert.assertNotNull;
@@ -83,37 +84,40 @@ public class AuthenticationSessionProviderTest {
ClientModel client1 = realm.getClientByClientId("test-app");
UserModel user1 = session.users().getUserByUsername("user1", realm);
- AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client1);
+ RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm);
+ AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client1);
authSession.setAction("foo");
- authSession.setTimestamp(100);
+ rootAuthSession.setTimestamp(100);
resetSession();
// Ensure session is here
- authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
+ rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSession.getId());
testAuthenticationSession(authSession, client1.getId(), null, "foo");
- Assert.assertEquals(100, authSession.getTimestamp());
+ Assert.assertEquals(100, rootAuthSession.getTimestamp());
// Update and commit
authSession.setAction("foo-updated");
- authSession.setTimestamp(200);
+ rootAuthSession.setTimestamp(200);
authSession.setAuthenticatedUser(session.users().getUserByUsername("user1", realm));
resetSession();
// Ensure session was updated
- authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
+ rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSession.getId());
+ client1 = realm.getClientByClientId("test-app");
+ authSession = rootAuthSession.getAuthenticationSession(client1);
testAuthenticationSession(authSession, client1.getId(), user1.getId(), "foo-updated");
- Assert.assertEquals(200, authSession.getTimestamp());
+ Assert.assertEquals(200, rootAuthSession.getTimestamp());
// Remove and commit
- session.authenticationSessions().removeAuthenticationSession(realm, authSession);
+ session.authenticationSessions().removeRootAuthenticationSession(realm, rootAuthSession);
resetSession();
// Ensure session was removed
- Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSession.getId()));
+ Assert.assertNull(session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSession.getId()));
}
@@ -122,10 +126,10 @@ public class AuthenticationSessionProviderTest {
ClientModel client1 = realm.getClientByClientId("test-app");
UserModel user1 = session.users().getUserByUsername("user1", realm);
- AuthenticationSessionModel authSession = session.authenticationSessions().createAuthenticationSession(realm, client1);
+ AuthenticationSessionModel authSession = session.authenticationSessions().createRootAuthenticationSession(realm).createAuthenticationSession(client1);
authSession.setAction("foo");
- authSession.setTimestamp(100);
+ authSession.getParentSession().setTimestamp(100);
authSession.setAuthenticatedUser(user1);
authSession.setAuthNote("foo", "bar");
@@ -134,20 +138,17 @@ public class AuthenticationSessionProviderTest {
resetSession();
+ // Test restart root authentication session
client1 = realm.getClientByClientId("test-app");
- authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
- authSession.restartSession(realm, client1);
+ authSession = session.authenticationSessions().getRootAuthenticationSession(realm, authSession.getParentSession().getId())
+ .getAuthenticationSession(client1);
+ authSession.getParentSession().restartSession(realm);
resetSession();
- authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
- testAuthenticationSession(authSession, client1.getId(), null, null);
- Assert.assertTrue(authSession.getTimestamp() > 0);
-
- Assert.assertTrue(authSession.getClientNotes().isEmpty());
- Assert.assertNull(authSession.getAuthNote("foo2"));
- Assert.assertTrue(authSession.getExecutionStatus().isEmpty());
-
+ RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, authSession.getParentSession().getId());
+ Assert.assertNull(rootAuthSession.getAuthenticationSession(client1));
+ Assert.assertTrue(rootAuthSession.getTimestamp() > 0);
}
@@ -159,58 +160,59 @@ public class AuthenticationSessionProviderTest {
realm.setAccessCodeLifespanLogin(30);
// Login lifespan is largest
- String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ String authSessionId = session.authenticationSessions().createRootAuthenticationSession(realm).getId();
+
resetSession();
Time.setOffset(25);
session.authenticationSessions().removeExpired(realm);
resetSession();
- assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ assertNotNull(session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId));
Time.setOffset(35);
session.authenticationSessions().removeExpired(realm);
resetSession();
- assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ assertNull(session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId));
// User action is largest
realm.setAccessCodeLifespanUserAction(40);
Time.setOffset(0);
- authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ authSessionId = session.authenticationSessions().createRootAuthenticationSession(realm).getId();
resetSession();
Time.setOffset(35);
session.authenticationSessions().removeExpired(realm);
resetSession();
- assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ assertNotNull(session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId));
Time.setOffset(45);
session.authenticationSessions().removeExpired(realm);
resetSession();
- assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ assertNull(session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId));
// Access code is largest
realm.setAccessCodeLifespan(50);
Time.setOffset(0);
- authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
+ authSessionId = session.authenticationSessions().createRootAuthenticationSession(realm).getId();
resetSession();
Time.setOffset(45);
session.authenticationSessions().removeExpired(realm);
resetSession();
- assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ assertNotNull(session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId));
Time.setOffset(55);
session.authenticationSessions().removeExpired(realm);
resetSession();
- assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
+ assertNull(session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId));
} finally {
Time.setOffset(0);
@@ -227,8 +229,8 @@ public class AuthenticationSessionProviderTest {
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();
+ String authSessionId = session.authenticationSessions().createRootAuthenticationSession(realm).getId();
+ String authSessionId2 = session.authenticationSessions().createRootAuthenticationSession(fooRealm).getId();
resetSession();
@@ -236,27 +238,36 @@ public class AuthenticationSessionProviderTest {
resetSession();
- AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
- testAuthenticationSession(authSession, realm.getClientByClientId("test-app").getId(), null, null);
- Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
+ RootAuthenticationSessionModel authSession = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId);
+ Assert.assertNotNull(authSession);
+ Assert.assertNull(session.authenticationSessions().getRootAuthenticationSession(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 authSessionId = session.authenticationSessions().createRootAuthenticationSession(realm).getId();
+ AuthenticationSessionModel authSession1 = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId).createAuthenticationSession(realm.getClientByClientId("test-app"));
+ AuthenticationSessionModel authSession2 = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId).createAuthenticationSession(realm.getClientByClientId("third-party"));
+
+ authSession1.setAuthNote("foo", "bar");
+ authSession2.setAuthNote("foo", "baz");
String testAppClientUUID = realm.getClientByClientId("test-app").getId();
resetSession();
+ RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId);
+ Assert.assertEquals(2, rootAuthSession.getAuthenticationSessions().size());
+ Assert.assertEquals("bar", rootAuthSession.getAuthenticationSession(realm.getClientByClientId("test-app")).getAuthNote("foo"));
+ Assert.assertEquals("baz", rootAuthSession.getAuthenticationSession(realm.getClientByClientId("third-party")).getAuthNote("foo"));
+
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));
+ rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, authSessionId);
+ Assert.assertEquals("bar", rootAuthSession.getAuthenticationSession(realm.getClientByClientId("test-app")).getAuthNote("foo"));
+ Assert.assertNull(rootAuthSession.getAuthenticationSession(realm.getClientByClientId("third-party")));
// Revert client
realm.addClient("third-party");