Skip to content

Commit

Permalink
KEYCLOAK-5797 Refactoring authenticationSessions to support login in …
Browse files Browse the repository at this point in the history
…multiple browser tabs with different clients
  • Loading branch information
mposolda committed Nov 30, 2017
1 parent b466f4d commit 7b03eed
Show file tree
Hide file tree
Showing 48 changed files with 840 additions and 546 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class AuthenticationSessionAuthNoteUpdateEvent implements ClusterEvent {

private String authSessionId;

private String clientUUID;

private Map<String, String> authNotesFragment;

/**
Expand All @@ -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;
}
Expand All @@ -55,13 +58,18 @@ public String getAuthSessionId() {
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> {
Expand All @@ -73,6 +81,7 @@ public void writeObject(ObjectOutput output, AuthenticationSessionAuthNoteUpdate
output.writeByte(VERSION_1);

MarshallUtil.marshallString(value.authSessionId, output);
MarshallUtil.marshallString(value.clientUUID, output);
MarshallUtil.marshallMap(value.authNotesFragment, output);
}

Expand All @@ -88,6 +97,7 @@ public AuthenticationSessionAuthNoteUpdateEvent readObject(ObjectInput input) th

public AuthenticationSessionAuthNoteUpdateEvent readObjectVersion1(ObjectInput input) throws IOException, ClassNotFoundException {
return create(
MarshallUtil.unmarshallString(input),
MarshallUtil.unmarshallString(input),
MarshallUtil.unmarshallMap(input, HashMap::new)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,13 @@
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
Expand All @@ -39,39 +38,37 @@
*/
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
Expand All @@ -85,16 +82,6 @@ public void setRedirectUri(String uri) {
update();
}

@Override
public int getTimestamp() {
return entity.getTimestamp();
}

@Override
public void setTimestamp(int timestamp) {
entity.setTimestamp(timestamp);
update();
}

@Override
public String getAction() {
Expand Down Expand Up @@ -303,7 +290,7 @@ public void clearExecutionStatus() {

@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) {
Expand All @@ -312,20 +299,4 @@ public void setAuthenticatedUser(UserModel user) {
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,14 @@
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>
Expand All @@ -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;

Expand All @@ -62,38 +61,33 @@ public InfinispanAuthenticationSessionProvider(KeycloakSession session, Cache<St
}

@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);
Expand All @@ -102,10 +96,6 @@ private AuthenticationSessionEntity getAuthenticationSessionEntity(RealmModel re
return entity;
}

@Override
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession) {
tx.remove(cache, authenticationSession.getId());
}

@Override
public void removeExpired(RealmModel realm) {
Expand All @@ -115,16 +105,16 @@ public void removeExpired(RealmModel realm) {


// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<Map.Entry<String, 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());
}

Expand All @@ -141,10 +131,10 @@ public void onRealmRemoved(RealmModel realm) {
}

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()) {
Expand All @@ -156,47 +146,52 @@ protected void onRealmRemovedEvent(String realmId) {

@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;
}

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;
}
}
Loading

0 comments on commit 7b03eed

Please sign in to comment.