KEYCLOAK-4626 Authentication sessions - SAML, offline tokens, broker logout and other fixes

This commit is contained in:
mposolda 2017-04-21 14:24:47 +02:00
parent 47aaa5a636
commit 168153c6e7
102 changed files with 2589 additions and 3188 deletions

View file

@ -170,8 +170,8 @@ public class HttpClientBuilder {
return this;
}
public HttpClientBuilder disableCookieCache() {
this.disableCookieCache = true;
public HttpClientBuilder disableCookieCache(boolean disable) {
this.disableCookieCache = disable;
return this;
}
@ -334,7 +334,7 @@ public class HttpClientBuilder {
}
public HttpClient build(AdapterHttpClientConfig adapterConfig) {
disableCookieCache(); // disable cookie cache as we don't want sticky sessions for load balancing
disableCookieCache(true); // disable cookie cache as we don't want sticky sessions for load balancing
String truststorePath = adapterConfig.getTruststore();
if (truststorePath != null) {

View file

@ -168,7 +168,7 @@ public class OIDCFilterSessionStore extends FilterSessionStore implements Adapte
HttpSession httpSession = request.getSession();
httpSession.setAttribute(KeycloakAccount.class.getName(), sAccount);
httpSession.setAttribute(KeycloakSecurityContext.class.getName(), sAccount.getKeycloakSecurityContext());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getClientSession(), account.getPrincipal().getName(), httpSession.getId());
if (idMapper != null) idMapper.map(account.getKeycloakSecurityContext().getToken().getSessionState(), account.getPrincipal().getName(), httpSession.getId());
//String username = securityContext.getToken().getSubject();
//log.fine("userSessionManagement.login: " + username);
}

View file

@ -97,9 +97,6 @@ public class AccessToken extends IDToken {
}
}
@JsonProperty("client_session")
protected String clientSession;
@JsonProperty("trusted-certs")
protected Set<String> trustedCertificates;
@ -156,10 +153,6 @@ public class AccessToken extends IDToken {
return resourceAccess.get(resource);
}
public String getClientSession() {
return clientSession;
}
public Access addAccess(String service) {
Access access = resourceAccess.get(service);
if (access != null) return access;
@ -168,11 +161,6 @@ public class AccessToken extends IDToken {
return access;
}
public AccessToken clientSession(String session) {
this.clientSession = session;
return this;
}
@Override
public AccessToken id(String id) {
return (AccessToken) super.id(id);

View file

@ -40,7 +40,6 @@ public class RefreshToken extends AccessToken {
*/
public RefreshToken(AccessToken token) {
this();
this.clientSession = token.getClientSession();
this.issuer = token.issuer;
this.subject = token.subject;
this.issuedFor = token.issuedFor;

View file

@ -47,7 +47,7 @@ is in your `/etc/hosts` before other records for the 127.0.0.1 host to avoid iss
**5)** Configure Kerberos client (On linux it's in file `/etc/krb5.conf` ). You need to configure `KEYCLOAK.ORG` realm for host `localhost` and enable `forwardable` flag, which is needed
for credential delegation example, as application needs to forward Kerberos ticket and authenticate with it against LDAP server.
See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration/src/test/resources/kerberos/test-krb5.conf) for inspiration.
See [this file](https://github.com/keycloak/keycloak/blob/master/testsuite/integration-arquillian/tests/base/src/test/resources/kerberos/test-krb5.conf) for inspiration.
On OS X the file to edit (or create) is `/Library/Preferences/edu.mit.Kerberos` with the same syntax as `krb5.conf`.
On Windows the file to edit (or create) is `c:\Windows\krb5.ini` with the same syntax as `krb5.conf`.

View file

@ -27,7 +27,7 @@ import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@ -36,14 +36,16 @@ import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
*/
public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {
private final ClientLoginSessionEntity entity;
private final AuthenticatedClientSessionEntity entity;
private final ClientModel client;
private final InfinispanUserSessionProvider provider;
private final Cache<String, SessionEntity> cache;
private UserSessionAdapter userSession;
public AuthenticatedClientSessionAdapter(ClientLoginSessionEntity entity, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
public AuthenticatedClientSessionAdapter(AuthenticatedClientSessionEntity entity, ClientModel client, UserSessionAdapter userSession, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache) {
this.provider = provider;
this.entity = entity;
this.client = client;
this.cache = cache;
this.userSession = userSession;
}
@ -55,23 +57,23 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override
public void setUserSession(UserSessionModel userSession) {
String clientUUID = entity.getClient();
String clientUUID = client.getId();
UserSessionEntity sessionEntity = this.userSession.getEntity();
// Dettach userSession
if (userSession == null) {
if (sessionEntity.getClientLoginSessions() != null) {
sessionEntity.getClientLoginSessions().remove(clientUUID);
if (sessionEntity.getAuthenticatedClientSessions() != null) {
sessionEntity.getAuthenticatedClientSessions().remove(clientUUID);
update();
this.userSession = null;
}
} else {
this.userSession = (UserSessionAdapter) userSession;
if (sessionEntity.getClientLoginSessions() == null) {
sessionEntity.setClientLoginSessions(new HashMap<>());
if (sessionEntity.getAuthenticatedClientSessions() == null) {
sessionEntity.setAuthenticatedClientSessions(new HashMap<>());
}
sessionEntity.getClientLoginSessions().put(clientUUID, entity);
sessionEntity.getAuthenticatedClientSessions().put(clientUUID, entity);
update();
}
}
@ -104,8 +106,7 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
@Override
public ClientModel getClient() {
String client = entity.getClient();
return getRealm().getClientById(client);
return client;
}
@Override
@ -192,4 +193,5 @@ public class AuthenticatedClientSessionAdapter implements AuthenticatedClientSes
copy.putAll(entity.getNotes());
return copy;
}
}

View file

@ -1,292 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionAdapter implements ClientSessionModel {
private KeycloakSession session;
private InfinispanUserSessionProvider provider;
private Cache<String, SessionEntity> cache;
private RealmModel realm;
private ClientSessionEntity entity;
private boolean offline;
public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm,
ClientSessionEntity entity, boolean offline) {
this.session = session;
this.provider = provider;
this.cache = cache;
this.realm = realm;
this.entity = entity;
this.offline = offline;
}
@Override
public String getId() {
return entity.getId();
}
@Override
public RealmModel getRealm() {
return realm;
}
@Override
public ClientModel getClient() {
return realm.getClientById(entity.getClient());
}
@Override
public UserSessionAdapter getUserSession() {
return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession(), offline) : null;
}
@Override
public void setUserSession(UserSessionModel userSession) {
if (userSession == null) {
if (entity.getUserSession() != null) {
provider.dettachSession(getUserSession(), this);
}
entity.setUserSession(null);
} else {
UserSessionAdapter userSessionAdapter = (UserSessionAdapter) userSession;
if (entity.getUserSession() != null) {
if (entity.getUserSession().equals(userSession.getId())) {
return;
} else {
provider.dettachSession(userSessionAdapter, this);
}
} else {
provider.attachSession(userSessionAdapter, this);
}
entity.setUserSession(userSession.getId());
}
update();
}
@Override
public String getRedirectUri() {
return entity.getRedirectUri();
}
@Override
public void setRedirectUri(String uri) {
entity.setRedirectUri(uri);
update();
}
@Override
public int getTimestamp() {
return entity.getTimestamp();
}
@Override
public void setTimestamp(int timestamp) {
entity.setTimestamp(timestamp);
update();
}
@Override
public String getAction() {
return entity.getAction();
}
@Override
public void setAction(String action) {
entity.setAction(action);
update();
}
@Override
public Set<String> getRoles() {
if (entity.getRoles() == null || entity.getRoles().isEmpty()) return Collections.emptySet();
return new HashSet<>(entity.getRoles());
}
@Override
public void setRoles(Set<String> roles) {
entity.setRoles(roles);
update();
}
@Override
public Set<String> getProtocolMappers() {
if (entity.getProtocolMappers() == null || entity.getProtocolMappers().isEmpty()) return Collections.emptySet();
return new HashSet<>(entity.getProtocolMappers());
}
@Override
public void setProtocolMappers(Set<String> protocolMappers) {
entity.setProtocolMappers(protocolMappers);
update();
}
@Override
public String getProtocol() {
return entity.getAuthMethod();
}
@Override
public void setProtocol(String authMethod) {
entity.setAuthMethod(authMethod);
update();
}
@Override
public String getNote(String name) {
return entity.getNotes() != null ? entity.getNotes().get(name) : null;
}
@Override
public void setNote(String name, String value) {
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<String, String>());
}
entity.getNotes().put(name, value);
update();
}
@Override
public void removeNote(String name) {
if (entity.getNotes() != null) {
entity.getNotes().remove(name);
update();
}
}
@Override
public Map<String, String> getNotes() {
if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
Map<String, String> copy = new HashMap<>();
copy.putAll(entity.getNotes());
return copy;
}
@Override
public void setUserSessionNote(String name, String value) {
if (entity.getUserSessionNotes() == null) {
entity.setUserSessionNotes(new HashMap<String, String>());
}
entity.getUserSessionNotes().put(name, value);
update();
}
@Override
public Map<String, String> getUserSessionNotes() {
if (entity.getUserSessionNotes() == null) {
return Collections.EMPTY_MAP;
}
HashMap<String, String> copy = new HashMap<>();
copy.putAll(entity.getUserSessionNotes());
return copy;
}
@Override
public void clearUserSessionNotes() {
entity.setUserSessionNotes(new HashMap<String, String>());
update();
}
@Override
public Set<String> getRequiredActions() {
Set<String> copy = new HashSet<>();
copy.addAll(entity.getRequiredActions());
return copy;
}
@Override
public void addRequiredAction(String action) {
entity.getRequiredActions().add(action);
update();
}
@Override
public void removeRequiredAction(String action) {
entity.getRequiredActions().remove(action);
update();
}
@Override
public void addRequiredAction(UserModel.RequiredAction action) {
addRequiredAction(action.name());
}
@Override
public void removeRequiredAction(UserModel.RequiredAction action) {
removeRequiredAction(action.name());
}
void update() {
provider.getTx().replace(cache, entity.getId(), entity);
}
@Override
public Map<String, ExecutionStatus> getExecutionStatus() {
return entity.getAuthenticatorStatus();
}
@Override
public void setExecutionStatus(String authenticator, ExecutionStatus status) {
entity.getAuthenticatorStatus().put(authenticator, status);
update();
}
@Override
public void clearExecutionStatus() {
entity.getAuthenticatorStatus().clear();
update();
}
@Override
public UserModel getAuthenticatedUser() {
return entity.getAuthUserId() == null ? null : session.users().getUserById(entity.getAuthUserId(), realm); }
@Override
public void setAuthenticatedUser(UserModel user) {
if (user == null) entity.setAuthUserId(null);
else entity.setAuthUserId(user.getId());
update();
}
}

View file

@ -19,11 +19,9 @@ package org.keycloak.models.sessions.infinispan;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@ -41,21 +39,6 @@ public class Consumers {
return new UserSessionModelsConsumer(provider, realm, offline);
}
public static class UserSessionIdAndTimestampConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private Map<String, Integer> sessions = new HashMap<>();
@Override
public void accept(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
if (e instanceof ClientSessionEntity) {
ClientSessionEntity ce = (ClientSessionEntity) e;
sessions.put(ce.getUserSession(), ce.getTimestamp());
}
}
}
public static class UserSessionModelsConsumer implements Consumer<Map.Entry<String, SessionEntity>> {
private InfinispanUserSessionProvider provider;

View file

@ -120,6 +120,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
// TODO: Should likely listen to "RealmRemovedEvent" received from cluster and clean just local sessions
@Override
public void onRealmRemoved(RealmModel realm) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId())).iterator();
@ -128,6 +129,7 @@ public class InfinispanAuthenticationSessionProvider implements AuthenticationSe
}
}
// TODO: Should likely listen to "ClientRemovedEvent" received from cluster and clean just local sessions
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
Iterator<Map.Entry<String, AuthenticationSessionEntity>> itr = cache.entrySet().stream().filter(AuthenticationSessionPredicate.create(realm.getId()).client(client.getId())).iterator();

View file

@ -25,9 +25,7 @@ import org.keycloak.common.util.Time;
import org.keycloak.models.ClientInitialAccessModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserLoginFailureModel;
import org.keycloak.models.UserModel;
@ -35,31 +33,29 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.sessions.infinispan.entities.ClientInitialAccessEntity;
import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.stream.ClientInitialAccessPredicate;
import org.keycloak.models.sessions.infinispan.stream.ClientSessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.Comparators;
import org.keycloak.models.sessions.infinispan.stream.Mappers;
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.UserLoginFailurePredicate;
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.RealmInfoUtil;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
@ -90,31 +86,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return offline ? offlineSessionCache : sessionCache;
}
// TODO:mposolda remove
@Override
public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
String id = KeycloakModelUtils.generateId();
ClientSessionEntity entity = new ClientSessionEntity();
entity.setId(id);
entity.setRealm(realm.getId());
entity.setTimestamp(Time.currentTime());
entity.setClient(client.getId());
tx.put(sessionCache, id, entity);
ClientSessionAdapter wrap = wrap(realm, entity, false);
return wrap;
}
@Override
public AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession) {
ClientLoginSessionEntity entity = new ClientLoginSessionEntity();
entity.setClient(client.getId());
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, (UserSessionAdapter) userSession, this, sessionCache);
AuthenticatedClientSessionAdapter adapter = new AuthenticatedClientSessionAdapter(entity, client, (UserSessionAdapter) userSession, this, sessionCache);
adapter.setUserSession(userSession);
return adapter;
}
@ -123,6 +99,15 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(id);
updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
tx.putIfAbsent(sessionCache, id, entity);
return wrap(realm, entity, false);
}
void updateSessionEntity(UserSessionEntity entity, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
entity.setRealm(realm.getId());
entity.setUser(user.getId());
entity.setLoginUsername(loginUsername);
@ -137,41 +122,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(currentTime);
entity.setLastSessionRefresh(currentTime);
tx.putIfAbsent(sessionCache, id, entity);
return wrap(realm, entity, false);
}
@Override
public ClientSessionModel getClientSession(RealmModel realm, String id) {
return getClientSession(realm, id, false);
}
protected ClientSessionModel getClientSession(RealmModel realm, String id, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
ClientSessionEntity entity = (ClientSessionEntity) tx.get(cache, id); // Chance created in this transaction
if (entity == null) {
entity = (ClientSessionEntity) cache.get(id);
}
return wrap(realm, entity, offline);
}
@Override
public ClientSessionModel getClientSession(String id) {
// Chance created in this transaction
ClientSessionEntity entity = (ClientSessionEntity) tx.get(sessionCache, id);
if (entity == null) {
entity = (ClientSessionEntity) sessionCache.get(id);
}
if (entity != null) {
RealmModel realm = session.realms().getRealm(entity.getRealm());
return wrap(realm, entity, false);
}
return null;
}
@Override
@ -230,37 +181,50 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
protected List<UserSessionModel> getUserSessions(final RealmModel realm, ClientModel client, int firstResult, int maxResults, final boolean offline) {
final Cache<String, SessionEntity> cache = getCache(offline);
Iterator<UserSessionTimestamp> itr = cache.entrySet().stream()
.filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession())
.map(Mappers.clientSessionToUserSessionTimestamp())
.iterator();
Stream<UserSessionEntity> stream = cache.entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
.map(Mappers.userSessionEntity())
.sorted(Comparators.userSessionLastSessionRefresh());
Map<String, UserSessionTimestamp> m = new HashMap<>();
while(itr.hasNext()) {
UserSessionTimestamp next = itr.next();
if (!m.containsKey(next.getUserSessionId()) || m.get(next.getUserSessionId()).getClientSessionTimestamp() < next.getClientSessionTimestamp()) {
m.put(next.getUserSessionId(), next);
}
// Doesn't work due to ISPN-6575 . TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0
// if (firstResult > 0) {
// stream = stream.skip(firstResult);
// }
//
// if (maxResults > 0) {
// stream = stream.limit(maxResults);
// }
//
// List<UserSessionEntity> entities = stream.collect(Collectors.toList());
// Workaround for ISPN-6575 TODO Fix once infinispan upgraded to 8.2.2.Final or 9.0 and replace with the more effective code above
if (firstResult < 0) {
firstResult = 0;
}
if (maxResults < 0) {
maxResults = Integer.MAX_VALUE;
}
Stream<UserSessionTimestamp> stream = new LinkedList<>(m.values()).stream().sorted(Comparators.userSessionTimestamp());
int count = firstResult + maxResults;
if (count > 0) {
stream = stream.limit(count);
}
List<UserSessionEntity> entities = stream.collect(Collectors.toList());
if (firstResult > 0) {
stream = stream.skip(firstResult);
if (firstResult > entities.size()) {
return Collections.emptyList();
}
if (maxResults > 0) {
stream = stream.limit(maxResults);
}
maxResults = Math.min(maxResults, entities.size() - firstResult);
entities = entities.subList(firstResult, firstResult + maxResults);
final List<UserSessionModel> sessions = new LinkedList<>();
stream.forEach(new Consumer<UserSessionTimestamp>() {
entities.stream().forEach(new Consumer<UserSessionEntity>() {
@Override
public void accept(UserSessionTimestamp userSessionTimestamp) {
SessionEntity entity = cache.get(userSessionTimestamp.getUserSessionId());
if (entity != null) {
sessions.add(wrap(realm, (UserSessionEntity) entity, offline));
}
public void accept(UserSessionEntity userSessionEntity) {
sessions.add(wrap(realm, userSessionEntity, offline));
}
});
@ -273,7 +237,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
protected long getUserSessionsCount(RealmModel realm, ClientModel client, boolean offline) {
return getCache(offline).entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId()).requireUserSession()).map(Mappers.clientSessionToUserSessionId()).distinct().count();
return getCache(offline).entrySet().stream()
.filter(UserSessionPredicate.create(realm.getId()).client(client.getId()))
.count();
}
@Override
@ -303,9 +269,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void removeExpired(RealmModel realm) {
log.debugf("Removing expired sessions");
removeExpiredUserSessions(realm);
removeExpiredClientSessions(realm);
removeExpiredOfflineUserSessions(realm);
removeExpiredOfflineClientSessions(realm);
removeExpiredClientInitialAccess(realm);
}
@ -322,33 +286,11 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
counter++;
UserSessionEntity entity = (UserSessionEntity) itr.next().getValue();
tx.remove(sessionCache, entity.getId());
if (entity.getClientSessions() != null) {
for (String clientSessionId : entity.getClientSessions()) {
tx.remove(sessionCache, clientSessionId);
}
}
}
log.debugf("Removed %d expired user sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredClientSessions(RealmModel realm) {
int expiredDettachedClientSession = Time.currentTime() - RealmInfoUtil.getDettachedClientSessionLifespan(realm);
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<Map.Entry<String, SessionEntity>> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredDettachedClientSession).requireNullUserSession()).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
tx.remove(sessionCache, itr.next().getKey());
}
log.debugf("Removed %d expired client sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredOfflineUserSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
@ -366,33 +308,14 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
persister.removeUserSession(entity.getId(), true);
for (String clientSessionId : entity.getClientSessions()) {
tx.remove(offlineSessionCache, clientSessionId);
for (String clientUUID : entity.getAuthenticatedClientSessions().keySet()) {
persister.removeClientSession(entity.getId(), clientUUID, true);
}
}
log.debugf("Removed %d expired offline user sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredOfflineClientSessions(RealmModel realm) {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
int expiredOffline = Time.currentTime() - realm.getOfflineSessionIdleTimeout();
// Each cluster node cleanups just local sessions, which are those owned by himself (+ few more taking l1 cache into account)
Iterator<String> itr = offlineSessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).expiredRefresh(expiredOffline)).map(Mappers.sessionId()).iterator();
int counter = 0;
while (itr.hasNext()) {
counter++;
String sessionId = itr.next();
tx.remove(offlineSessionCache, sessionId);
persister.removeClientSession(sessionId, true);
}
log.debugf("Removed %d expired offline client sessions for realm '%s'", counter, realm.getName());
}
private void removeExpiredClientInitialAccess(RealmModel realm) {
Iterator<String> itr = sessionCache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL)
.entrySet().stream().filter(ClientInitialAccessPredicate.create(realm.getId()).expired(Time.currentTime())).map(Mappers.sessionId()).iterator();
@ -454,21 +377,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public void onClientRemoved(RealmModel realm, ClientModel client) {
onClientRemoved(realm, client, true);
onClientRemoved(realm, client, false);
}
private void onClientRemoved(RealmModel realm, ClientModel client, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
Iterator<Map.Entry<String, SessionEntity>> itr = cache.entrySet().stream().filter(ClientSessionPredicate.create(realm.getId()).client(client.getId())).iterator();
while (itr.hasNext()) {
ClientSessionEntity entity = (ClientSessionEntity) itr.next().getValue();
ClientSessionAdapter adapter = wrap(realm, entity, offline);
adapter.setUserSession(null);
tx.remove(cache, entity.getId());
}
// Nothing for now. userSession.getAuthenticatedClientSessions() will check lazily if particular client exists and update userSession on-the-fly.
}
@ -484,55 +393,10 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
public void close() {
}
void attachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (!entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().add(clientSessionId);
userSession.update();
}
}
@Override
public void removeClientSession(RealmModel realm, ClientSessionModel clientSession) {
removeClientSession(realm, clientSession, false);
}
protected void removeClientSession(RealmModel realm, ClientSessionModel clientSession, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
UserSessionModel userSession = clientSession.getUserSession();
if (userSession != null) {
UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
if (entity.getClientSessions() != null) {
entity.getClientSessions().remove(clientSession.getId());
}
tx.replace(cache, entity.getId(), entity);
}
tx.remove(cache, clientSession.getId());
}
void dettachSession(UserSessionAdapter userSession, ClientSessionModel clientSession) {
UserSessionEntity entity = userSession.getEntity();
String clientSessionId = clientSession.getId();
if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
entity.getClientSessions().remove(clientSessionId);
userSession.update();
}
}
protected void removeUserSession(RealmModel realm, UserSessionEntity sessionEntity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
tx.remove(cache, sessionEntity.getId());
if (sessionEntity.getClientSessions() != null) {
for (String clientSessionId : sessionEntity.getClientSessions()) {
tx.remove(cache, clientSessionId);
}
}
}
InfinispanKeycloakTransaction getTx() {
@ -560,11 +424,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return models;
}
ClientSessionAdapter wrap(RealmModel realm, ClientSessionEntity entity, boolean offline) {
Cache<String, SessionEntity> cache = getCache(offline);
return entity != null ? new ClientSessionAdapter(session, this, cache, realm, entity, offline) : null;
}
ClientInitialAccessAdapter wrap(RealmModel realm, ClientInitialAccessEntity entity) {
Cache<String, SessionEntity> cache = getCache(false);
return entity != null ? new ClientInitialAccessAdapter(session, this, cache, realm, entity) : null;
@ -574,14 +433,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return entity != null ? new UserLoginFailureAdapter(this, loginFailureCache, key, entity) : null;
}
List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities, boolean offline) {
List<ClientSessionModel> models = new LinkedList<>();
for (ClientSessionEntity e : entities) {
models.add(wrap(realm, e, offline));
}
return models;
}
UserSessionEntity getUserSessionEntity(UserSessionModel userSession, boolean offline) {
if (userSession instanceof UserSessionAdapter) {
return ((UserSessionAdapter) userSession).getEntity();
@ -594,7 +445,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
@Override
public UserSessionModel createOfflineUserSession(UserSessionModel userSession) {
UserSessionAdapter offlineUserSession = importUserSession(userSession, true);
UserSessionAdapter offlineUserSession = importUserSession(userSession, true, false);
// started and lastSessionRefresh set to current time
int currentTime = Time.currentTime();
@ -605,7 +456,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId) {
public UserSessionAdapter getOfflineUserSession(RealmModel realm, String userSessionId) {
return getUserSession(realm, userSessionId, true);
}
@ -617,26 +468,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
}
// TODO:mposolda
/*
@Override
public ClientSessionModel createOfflineClientSession(ClientSessionModel clientSession) {
ClientSessionAdapter offlineClientSession = importClientSession(clientSession, true);
public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession) {
UserSessionAdapter userSessionAdapter = (offlineUserSession instanceof UserSessionAdapter) ? (UserSessionAdapter) offlineUserSession :
getOfflineUserSession(offlineUserSession.getRealm(), offlineUserSession.getId());
AuthenticatedClientSessionAdapter offlineClientSession = importClientSession(userSessionAdapter, clientSession);
// update timestamp to current time
offlineClientSession.setTimestamp(Time.currentTime());
return offlineClientSession;
}*/
@Override
public AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession) {
return null;
}
@Override
public ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId) {
return getClientSession(realm, clientSessionId, true);
}
@Override
@ -653,12 +497,6 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return userSessions;
}
@Override
public void removeOfflineClientSession(RealmModel realm, String clientSessionId) {
ClientSessionModel clientSession = getOfflineClientSession(realm, clientSessionId);
removeClientSession(realm, clientSession, true);
}
@Override
public long getOfflineSessionsCount(RealmModel realm, ClientModel client) {
return getUserSessionsCount(realm, client, true);
@ -670,7 +508,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
}
@Override
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline) {
public UserSessionAdapter importUserSession(UserSessionModel userSession, boolean offline, boolean importAuthenticatedClientSessions) {
UserSessionEntity entity = new UserSessionEntity();
entity.setId(userSession.getId());
entity.setRealm(userSession.getRealm().getId());
@ -688,34 +526,45 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
entity.setStarted(userSession.getStarted());
entity.setLastSessionRefresh(userSession.getLastSessionRefresh());
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, userSession.getId(), entity);
return wrap(userSession.getRealm(), entity, offline);
UserSessionAdapter importedSession = wrap(userSession.getRealm(), entity, offline);
// Handle client sessions
if (importAuthenticatedClientSessions) {
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
importClientSession(importedSession, clientSession);
}
}
return importedSession;
}
@Override
public ClientSessionAdapter importClientSession(ClientSessionModel clientSession, boolean offline) {
ClientSessionEntity entity = new ClientSessionEntity();
entity.setId(clientSession.getId());
entity.setRealm(clientSession.getRealm().getId());
private AuthenticatedClientSessionAdapter importClientSession(UserSessionAdapter importedUserSession, AuthenticatedClientSessionModel clientSession) {
AuthenticatedClientSessionEntity entity = new AuthenticatedClientSessionEntity();
entity.setAction(clientSession.getAction());
entity.setAuthenticatorStatus(clientSession.getExecutionStatus());
entity.setAuthMethod(clientSession.getProtocol());
if (clientSession.getAuthenticatedUser() != null) {
entity.setAuthUserId(clientSession.getAuthenticatedUser().getId());
}
entity.setClient(clientSession.getClient().getId());
entity.setNotes(clientSession.getNotes());
entity.setProtocolMappers(clientSession.getProtocolMappers());
entity.setRedirectUri(clientSession.getRedirectUri());
entity.setRoles(clientSession.getRoles());
entity.setTimestamp(clientSession.getTimestamp());
entity.setUserSessionNotes(clientSession.getUserSessionNotes());
Cache<String, SessionEntity> cache = getCache(offline);
tx.put(cache, clientSession.getId(), entity);
return wrap(clientSession.getRealm(), entity, offline);
Map<String, AuthenticatedClientSessionEntity> clientSessions = importedUserSession.getEntity().getAuthenticatedClientSessions();
if (clientSessions == null) {
clientSessions = new HashMap<>();
importedUserSession.getEntity().setAuthenticatedClientSessions(clientSessions);
}
clientSessions.put(clientSession.getClient().getId(), entity);
importedUserSession.update();
return new AuthenticatedClientSessionAdapter(entity, clientSession.getClient(), importedUserSession, this, importedUserSession.getCache());
}
@Override

View file

@ -19,12 +19,12 @@ package org.keycloak.models.sessions.infinispan;
import org.infinispan.Cache;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.sessions.infinispan.entities.ClientLoginSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.AuthenticatedClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
@ -64,15 +64,31 @@ public class UserSessionAdapter implements UserSessionModel {
@Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
Map<String, ClientLoginSessionEntity> clientSessionEntities = entity.getClientLoginSessions();
Map<String, AuthenticatedClientSessionEntity> clientSessionEntities = entity.getAuthenticatedClientSessions();
Map<String, AuthenticatedClientSessionModel> result = new HashMap<>();
List<String> removedClientUUIDS = new LinkedList<>();
if (clientSessionEntities != null) {
clientSessionEntities.forEach((String key, ClientLoginSessionEntity value) -> {
result.put(key, new AuthenticatedClientSessionAdapter(value, this, provider, cache));
clientSessionEntities.forEach((String key, AuthenticatedClientSessionEntity value) -> {
// Check if client still exists
ClientModel client = realm.getClientById(key);
if (client != null) {
result.put(key, new AuthenticatedClientSessionAdapter(value, client, this, provider, cache));
} else {
removedClientUUIDS.add(key);
}
});
}
// Update user session
if (!removedClientUUIDS.isEmpty()) {
for (String clientUUID : removedClientUUIDS) {
entity.getAuthenticatedClientSessions().remove(clientUUID);
}
update();
}
return Collections.unmodifiableMap(result);
}
@ -101,6 +117,7 @@ public class UserSessionAdapter implements UserSessionModel {
@Override
public void setUser(UserModel user) {
entity.setUser(user.getId());
update();
}
@Override
@ -180,19 +197,14 @@ public class UserSessionAdapter implements UserSessionModel {
}
@Override
public List<ClientSessionModel> getClientSessions() {
if (entity.getClientSessions() != null) {
List<ClientSessionModel> clientSessions = new LinkedList<>();
for (String c : entity.getClientSessions()) {
ClientSessionModel clientSession = provider.getClientSession(realm, c, offline);
if (clientSession != null) {
clientSessions.add(clientSession);
}
}
return clientSessions;
} else {
return Collections.emptyList();
}
public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
provider.updateSessionEntity(entity, realm, user, loginUsername, ipAddress, authMethod, rememberMe, brokerSessionId, brokerUserId);
entity.setState(null);
entity.setNotes(null);
entity.setAuthenticatedClientSessions(null);
update();
}
@Override
@ -217,4 +229,7 @@ public class UserSessionAdapter implements UserSessionModel {
provider.getTx().replace(cache, entity.getId(), entity);
}
Cache<String, SessionEntity> getCache() {
return cache;
}
}

View file

@ -23,10 +23,9 @@ import java.util.Set;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientLoginSessionEntity {
public class AuthenticatedClientSessionEntity {
private String id;
private String client;
private String authMethod;
private String redirectUri;
private int timestamp;
@ -44,14 +43,6 @@ public class ClientLoginSessionEntity {
this.id = id;
}
public String getClient() {
return client;
}
public void setClient(String client) {
this.client = client;
}
public String getAuthMethod() {
return authMethod;
}

View file

@ -1,152 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ClientSessionModel;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionEntity extends SessionEntity {
private String client;
private String userSession;
private String authMethod;
private String redirectUri;
private int timestamp;
private String action;
private Set<String> roles;
private Set<String> protocolMappers;
private Map<String, String> notes;
private Map<String, String> userSessionNotes;
private Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus = new HashMap<>();
private String authUserId;
private Set<String> requiredActions = new HashSet<>();
public String getClient() {
return client;
}
public void setClient(String client) {
this.client = client;
}
public String getUserSession() {
return userSession;
}
public void setUserSession(String userSession) {
this.userSession = userSession;
}
public String getAuthMethod() {
return authMethod;
}
public void setAuthMethod(String authMethod) {
this.authMethod = authMethod;
}
public String getRedirectUri() {
return redirectUri;
}
public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public Set<String> getRoles() {
return roles;
}
public void setRoles(Set<String> roles) {
this.roles = roles;
}
public Set<String> getProtocolMappers() {
return protocolMappers;
}
public void setProtocolMappers(Set<String> protocolMappers) {
this.protocolMappers = protocolMappers;
}
public Map<String, String> getNotes() {
return notes;
}
public void setNotes(Map<String, String> notes) {
this.notes = notes;
}
public Map<String, ClientSessionModel.ExecutionStatus> getAuthenticatorStatus() {
return authenticatorStatus;
}
public void setAuthenticatorStatus(Map<String, ClientSessionModel.ExecutionStatus> authenticatorStatus) {
this.authenticatorStatus = authenticatorStatus;
}
public String getAuthUserId() {
return authUserId;
}
public void setAuthUserId(String authUserId) {
this.authUserId = authUserId;
}
public Map<String, String> getUserSessionNotes() {
return userSessionNotes;
}
public void setUserSessionNotes(Map<String, String> userSessionNotes) {
this.userSessionNotes = userSessionNotes;
}
public Set<String> getRequiredActions() {
return requiredActions;
}
}

View file

@ -46,13 +46,11 @@ public class UserSessionEntity extends SessionEntity {
private int lastSessionRefresh;
private Set<String> clientSessions = new CopyOnWriteArraySet<>();
private UserSessionModel.State state;
private Map<String, String> notes = new ConcurrentHashMap<>();
private Map<String, ClientLoginSessionEntity> clientLoginSessions;
private Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions;
public String getUser() {
return user;
@ -110,10 +108,6 @@ public class UserSessionEntity extends SessionEntity {
this.lastSessionRefresh = lastSessionRefresh;
}
public Set<String> getClientSessions() {
return clientSessions;
}
public Map<String, String> getNotes() {
return notes;
}
@ -122,12 +116,12 @@ public class UserSessionEntity extends SessionEntity {
this.notes = notes;
}
public Map<String, ClientLoginSessionEntity> getClientLoginSessions() {
return clientLoginSessions;
public Map<String, AuthenticatedClientSessionEntity> getAuthenticatedClientSessions() {
return authenticatedClientSessions;
}
public void setClientLoginSessions(Map<String, ClientLoginSessionEntity> clientLoginSessions) {
this.clientLoginSessions = clientLoginSessions;
public void setAuthenticatedClientSessions(Map<String, AuthenticatedClientSessionEntity> authenticatedClientSessions) {
this.authenticatedClientSessions = authenticatedClientSessions;
}
public UserSessionModel.State getState() {

View file

@ -19,7 +19,6 @@ package org.keycloak.models.sessions.infinispan.initializer;
import org.jboss.logging.Logger;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
@ -64,12 +63,7 @@ public class OfflineUserSessionLoader implements SessionLoader {
for (UserSessionModel persistentSession : sessions) {
// Save to memory/infinispan
UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true);
for (ClientSessionModel persistentClientSession : persistentSession.getClientSessions()) {
ClientSessionModel offlineClientSession = session.sessions().importClientSession(persistentClientSession, true);
offlineClientSession.setUserSession(offlineUserSession);
}
UserSessionModel offlineUserSession = session.sessions().importUserSession(persistentSession, true, true);
}
return true;

View file

@ -1,129 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.mapreduce;
import org.infinispan.distexec.mapreduce.Collector;
import org.infinispan.distexec.mapreduce.Mapper;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
public ClientSessionMapper(String realm) {
this.realm = realm;
}
private enum EmitValue {
KEY, ENTITY, USER_SESSION_AND_TIMESTAMP
}
private String realm;
private EmitValue emit = EmitValue.ENTITY;
private String client;
private String userSession;
private Long expiredRefresh;
private Boolean requireNullUserSession = false;
public static ClientSessionMapper create(String realm) {
return new ClientSessionMapper(realm);
}
public ClientSessionMapper emitKey() {
emit = EmitValue.KEY;
return this;
}
public ClientSessionMapper emitUserSessionAndTimestamp() {
emit = EmitValue.USER_SESSION_AND_TIMESTAMP;
return this;
}
public ClientSessionMapper client(String client) {
this.client = client;
return this;
}
public ClientSessionMapper userSession(String userSession) {
this.userSession = userSession;
return this;
}
public ClientSessionMapper expiredRefresh(long expiredRefresh) {
this.expiredRefresh = expiredRefresh;
return this;
}
public ClientSessionMapper requireNullUserSession(boolean requireNullUserSession) {
this.requireNullUserSession = requireNullUserSession;
return this;
}
@Override
public void map(String key, SessionEntity e, Collector collector) {
if (!realm.equals(e.getRealm())) {
return;
}
if (!(e instanceof ClientSessionEntity)) {
return;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (client != null && !entity.getClient().equals(client)) {
return;
}
if (userSession != null && !userSession.equals(entity.getUserSession())) {
return;
}
if (requireNullUserSession && entity.getUserSession() != null) {
return;
}
if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
return;
}
switch (emit) {
case KEY:
collector.emit(key, key);
break;
case ENTITY:
collector.emit(key, entity);
break;
case USER_SESSION_AND_TIMESTAMP:
if (entity.getUserSession() != null) {
collector.emit(entity.getUserSession(), entity.getTimestamp());
}
break;
}
}
}

View file

@ -1,77 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.mapreduce;
import org.infinispan.distexec.mapreduce.Collector;
import org.infinispan.distexec.mapreduce.Mapper;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
import java.util.Collection;
/**
* Return all clientSessions attached to any from input list of userSessions
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClientSessionsOfUserSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
private String realm;
private Collection<String> userSessions;
private EmitValue emit = EmitValue.ENTITY;
private enum EmitValue {
KEY, ENTITY
}
public ClientSessionsOfUserSessionMapper(String realm, Collection<String> userSessions) {
this.realm = realm;
this.userSessions = userSessions;
}
public ClientSessionsOfUserSessionMapper emitKey() {
emit = EmitValue.KEY;
return this;
}
@Override
public void map(String key, SessionEntity e, Collector<String, Object> collector) {
if (!realm.equals(e.getRealm())) {
return;
}
if (!(e instanceof ClientSessionEntity)) {
return;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (userSessions.contains(entity.getUserSession())) {
switch (emit) {
case KEY:
collector.emit(entity.getId(), entity.getId());
break;
case ENTITY:
collector.emit(entity.getId(), entity);
break;
}
}
}
}

View file

@ -1,114 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import java.io.Serializable;
import java.util.Map;
import java.util.function.Predicate;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientSessionPredicate implements Predicate<Map.Entry<String, SessionEntity>>, Serializable {
private String realm;
private String client;
private String userSession;
private Long expiredRefresh;
private Boolean requireUserSession = false;
private Boolean requireNullUserSession = false;
private ClientSessionPredicate(String realm) {
this.realm = realm;
}
public static ClientSessionPredicate create(String realm) {
return new ClientSessionPredicate(realm);
}
public ClientSessionPredicate client(String client) {
this.client = client;
return this;
}
public ClientSessionPredicate userSession(String userSession) {
this.userSession = userSession;
return this;
}
public ClientSessionPredicate expiredRefresh(long expiredRefresh) {
this.expiredRefresh = expiredRefresh;
return this;
}
public ClientSessionPredicate requireUserSession() {
requireUserSession = true;
return this;
}
public ClientSessionPredicate requireNullUserSession() {
requireNullUserSession = true;
return this;
}
@Override
public boolean test(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
if (!realm.equals(e.getRealm())) {
return false;
}
if (!(e instanceof ClientSessionEntity)) {
return false;
}
ClientSessionEntity entity = (ClientSessionEntity) e;
if (client != null && !entity.getClient().equals(client)) {
return false;
}
if (userSession != null && !userSession.equals(entity.getUserSession())) {
return false;
}
if (requireUserSession && entity.getUserSession() == null) {
return false;
}
if (requireNullUserSession && entity.getUserSession() != null) {
return false;
}
if (expiredRefresh != null && entity.getTimestamp() > expiredRefresh) {
return false;
}
return true;
}
}

View file

@ -18,6 +18,8 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Comparator;
@ -38,4 +40,17 @@ public class Comparators {
}
}
public static Comparator<UserSessionEntity> userSessionLastSessionRefresh() {
return new UserSessionLastSessionRefreshComparator();
}
private static class UserSessionLastSessionRefreshComparator implements Comparator<UserSessionEntity>, Serializable {
@Override
public int compare(UserSessionEntity u1, UserSessionEntity u2) {
return u1.getLastSessionRefresh() - u2.getLastSessionRefresh();
}
}
}

View file

@ -18,10 +18,10 @@
package org.keycloak.models.sessions.infinispan.stream;
import org.keycloak.models.sessions.infinispan.UserSessionTimestamp;
import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
import org.keycloak.models.sessions.infinispan.entities.LoginFailureKey;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import java.io.Serializable;
import java.util.Map;
@ -33,10 +33,6 @@ import java.util.function.Function;
*/
public class Mappers {
public static Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp> clientSessionToUserSessionTimestamp() {
return new ClientSessionToUserSessionTimestampMapper();
}
public static Function<Map.Entry<String, Optional<UserSessionTimestamp>>, UserSessionTimestamp> userSessionTimestamp() {
return new UserSessionTimestampMapper();
}
@ -49,23 +45,14 @@ public class Mappers {
return new SessionEntityMapper();
}
public static Function<Map.Entry<String, SessionEntity>, UserSessionEntity> userSessionEntity() {
return new UserSessionEntityMapper();
}
public static Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey> loginFailureId() {
return new LoginFailureIdMapper();
}
public static Function<Map.Entry<String, SessionEntity>, String> clientSessionToUserSessionId() {
return new ClientSessionToUserSessionIdMapper();
}
private static class ClientSessionToUserSessionTimestampMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionTimestamp>, Serializable {
@Override
public UserSessionTimestamp apply(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
ClientSessionEntity entity = (ClientSessionEntity) e;
return new UserSessionTimestamp(entity.getUserSession(), entity.getTimestamp());
}
}
private static class UserSessionTimestampMapper implements Function<Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>>, org.keycloak.models.sessions.infinispan.UserSessionTimestamp>, Serializable {
@Override
public org.keycloak.models.sessions.infinispan.UserSessionTimestamp apply(Map.Entry<String, Optional<org.keycloak.models.sessions.infinispan.UserSessionTimestamp>> e) {
@ -87,6 +74,13 @@ public class Mappers {
}
}
private static class UserSessionEntityMapper implements Function<Map.Entry<String, SessionEntity>, UserSessionEntity>, Serializable {
@Override
public UserSessionEntity apply(Map.Entry<String, SessionEntity> entry) {
return (UserSessionEntity) entry.getValue();
}
}
private static class LoginFailureIdMapper implements Function<Map.Entry<LoginFailureKey, LoginFailureEntity>, LoginFailureKey>, Serializable {
@Override
public LoginFailureKey apply(Map.Entry<LoginFailureKey, LoginFailureEntity> entry) {
@ -94,12 +88,4 @@ public class Mappers {
}
}
private static class ClientSessionToUserSessionIdMapper implements Function<Map.Entry<String, SessionEntity>, String>, Serializable {
@Override
public String apply(Map.Entry<String, SessionEntity> entry) {
SessionEntity e = entry.getValue();
ClientSessionEntity entity = (ClientSessionEntity) e;
return entity.getUserSession();
}
}
}

View file

@ -33,6 +33,8 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
private String user;
private String client;
private Integer expired;
private Integer expiredRefresh;
@ -53,6 +55,11 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return this;
}
public UserSessionPredicate client(String clientUUID) {
this.client = clientUUID;
return this;
}
public UserSessionPredicate expired(Integer expired, Integer expiredRefresh) {
this.expired = expired;
this.expiredRefresh = expiredRefresh;
@ -87,6 +94,10 @@ public class UserSessionPredicate implements Predicate<Map.Entry<String, Session
return false;
}
if (client != null && (entity.getAuthenticatedClientSessions() == null || !entity.getAuthenticatedClientSessions().containsKey(client))) {
return false;
}
if (brokerSessionId != null && !brokerSessionId.equals(entity.getBrokerSessionId())) {
return false;
}

View file

@ -19,7 +19,6 @@ package org.keycloak.models.jpa.session;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
@ -35,8 +34,9 @@ import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -69,12 +69,11 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
public void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline) {
public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
PersistentAuthenticatedClientSessionAdapter adapter = new PersistentAuthenticatedClientSessionAdapter(clientSession);
PersistentClientSessionModel model = adapter.getUpdatedModel();
PersistentClientSessionEntity entity = new PersistentClientSessionEntity();
entity.setClientSessionId(clientSession.getId());
entity.setClientId(clientSession.getClient().getId());
entity.setTimestamp(clientSession.getTimestamp());
String offlineStr = offlineToString(offline);
@ -122,9 +121,9 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
@Override
public void removeClientSession(String clientSessionId, boolean offline) {
public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
String offlineStr = offlineToString(offline);
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(clientSessionId, offlineStr));
PersistentClientSessionEntity sessionEntity = em.find(PersistentClientSessionEntity.class, new PersistentClientSessionEntity.Key(userSessionId, clientUUID, offlineStr));
if (sessionEntity != null) {
em.remove(sessionEntity);
@ -218,8 +217,6 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
userSessionIds.add(entity.getUserSessionId());
}
// TODO:mposolda
/*
if (!userSessionIds.isEmpty()) {
TypedQuery<PersistentClientSessionEntity> query2 = em.createNamedQuery("findClientSessionsByUserSessions", PersistentClientSessionEntity.class);
query2.setParameter("userSessionIds", userSessionIds);
@ -230,14 +227,14 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
int j = 0;
for (UserSessionModel ss : result) {
PersistentUserSessionAdapter userSession = (PersistentUserSessionAdapter) ss;
List<ClientSessionModel> currentClientSessions = userSession.getClientSessions(); // This is empty now and we want to fill it
Map<String, AuthenticatedClientSessionModel> currentClientSessions = userSession.getAuthenticatedClientSessions(); // This is empty now and we want to fill it
boolean next = true;
while (next && j < clientSessions.size()) {
PersistentClientSessionEntity clientSession = clientSessions.get(j);
if (clientSession.getUserSessionId().equals(userSession.getId())) {
PersistentClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.add(clientSessAdapter);
PersistentAuthenticatedClientSessionAdapter clientSessAdapter = toAdapter(userSession.getRealm(), userSession, clientSession);
currentClientSessions.put(clientSession.getClientId(), clientSessAdapter);
j++;
} else {
next = false;
@ -245,7 +242,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
}
}
}
*/
return result;
}
@ -256,7 +253,7 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
model.setLastSessionRefresh(entity.getLastSessionRefresh());
model.setData(entity.getData());
List<ClientSessionModel> clientSessions = new LinkedList<>();
Map<String, AuthenticatedClientSessionModel> clientSessions = new HashMap<>();
return new PersistentUserSessionAdapter(model, realm, user, clientSessions);
}
@ -264,7 +261,6 @@ public class JpaUserSessionPersisterProvider implements UserSessionPersisterProv
ClientModel client = realm.getClientById(entity.getClientId());
PersistentClientSessionModel model = new PersistentClientSessionModel();
model.setClientSessionId(entity.getClientSessionId());
model.setClientId(entity.getClientId());
model.setUserSessionId(userSession.getId());
model.setUserId(userSession.getUser().getId());

View file

@ -20,7 +20,6 @@ package org.keycloak.models.jpa.session;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.session.UserSessionPersisterProviderFactory;

View file

@ -45,12 +45,10 @@ import java.io.Serializable;
public class PersistentClientSessionEntity {
@Id
@Column(name="CLIENT_SESSION_ID", length = 36)
protected String clientSessionId;
@Column(name = "USER_SESSION_ID", length = 36)
protected String userSessionId;
@Id
@Column(name="CLIENT_ID", length = 36)
protected String clientId;
@ -64,14 +62,6 @@ public class PersistentClientSessionEntity {
@Column(name="DATA")
protected String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;
}
@ -114,20 +104,27 @@ public class PersistentClientSessionEntity {
public static class Key implements Serializable {
protected String clientSessionId;
protected String userSessionId;
protected String clientId;
protected String offline;
public Key() {
}
public Key(String clientSessionId, String offline) {
this.clientSessionId = clientSessionId;
public Key(String userSessionId, String clientId, String offline) {
this.userSessionId = userSessionId;
this.clientId = clientId;
this.offline = offline;
}
public String getClientSessionId() {
return clientSessionId;
public String getUserSessionId() {
return userSessionId;
}
public String getClientId() {
return clientId;
}
public String getOffline() {
@ -141,7 +138,8 @@ public class PersistentClientSessionEntity {
Key key = (Key) o;
if (this.clientSessionId != null ? !this.clientSessionId.equals(key.clientSessionId) : key.clientSessionId != null) return false;
if (this.userSessionId != null ? !this.userSessionId.equals(key.userSessionId) : key.userSessionId != null) return false;
if (this.clientId != null ? !this.clientId.equals(key.clientId) : key.clientId != null) return false;
if (this.offline != null ? !this.offline.equals(key.offline) : key.offline != null) return false;
return true;
@ -149,7 +147,8 @@ public class PersistentClientSessionEntity {
@Override
public int hashCode() {
int result = this.clientSessionId != null ? this.clientSessionId.hashCode() : 0;
int result = this.userSessionId != null ? this.userSessionId.hashCode() : 0;
result = 37 * result + (this.clientId != null ? this.clientId.hashCode() : 0);
result = 31 * result + (this.offline != null ? this.offline.hashCode() : 0);
return result;
}

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="mposolda@redhat.com" id="3.2.0">
<dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION" />
<dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="CLIENT_SESSION_ID" />
<addPrimaryKey columnNames="USER_SESSION_ID,CLIENT_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK3" tableName="OFFLINE_CLIENT_SESSION"/>
</changeSet>
</databaseChangeLog>

View file

@ -47,4 +47,5 @@
<include file="META-INF/jpa-changelog-2.5.0.xml"/>
<include file="META-INF/jpa-changelog-2.5.1.xml"/>
<include file="META-INF/jpa-changelog-3.0.0.xml"/>
<include file="META-INF/jpa-changelog-3.2.0.xml"/>
</databaseChangeLog>

View file

@ -17,7 +17,6 @@
package org.keycloak.broker.provider;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;

View file

@ -17,7 +17,6 @@
package org.keycloak.broker.provider;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.sessions.AuthenticationSessionModel;

View file

@ -78,8 +78,6 @@ public interface LoginFormsProvider extends Provider {
public LoginFormsProvider setClientSessionCode(String accessCode);
public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authenticationSession);
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappers);
public LoginFormsProvider setAccessRequest(String message);

View file

@ -70,7 +70,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
}
@Override
public void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline) {
public void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline) {
}
@ -85,7 +85,7 @@ public class DisabledUserSessionPersisterProvider implements UserSessionPersiste
}
@Override
public void removeClientSession(String clientSessionId, boolean offline) {
public void removeClientSession(String userSessionId, String clientUUID, boolean offline) {
}

View file

@ -20,7 +20,6 @@ package org.keycloak.models.session;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@ -55,10 +54,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
model = new PersistentClientSessionModel();
model.setClientId(clientSession.getClient().getId());
model.setClientSessionId(clientSession.getId());
if (clientSession.getUserSession() != null) {
model.setUserId(clientSession.getUserSession().getUser().getId());
}
model.setUserId(clientSession.getUserSession().getUser().getId());
model.setUserSessionId(clientSession.getUserSession().getId());
model.setTimestamp(clientSession.getTimestamp());
@ -101,7 +97,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
@Override
public String getId() {
return model.getClientSessionId();
return null;
}
@Override
@ -194,7 +190,7 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
public void setNote(String name, String value) {
PersistentClientSessionData entity = getData();
if (entity.getNotes() == null) {
entity.setNotes(new HashMap<String, String>());
entity.setNotes(new HashMap<>());
}
entity.getNotes().put(name, value);
}
@ -214,13 +210,12 @@ public class PersistentAuthenticatedClientSessionAdapter implements Authenticate
return entity.getNotes();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof ClientSessionModel)) return false;
if (o == null || !(o instanceof AuthenticatedClientSessionModel)) return false;
ClientSessionModel that = (ClientSessionModel) o;
AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
return that.getId().equals(getId());
}

View file

@ -22,20 +22,12 @@ package org.keycloak.models.session;
*/
public class PersistentClientSessionModel {
private String clientSessionId;
private String userSessionId;
private String clientId;
private String userId;
private int timestamp;
private String data;
public String getClientSessionId() {
return clientSessionId;
}
public void setClientSessionId(String clientSessionId) {
this.clientSessionId = clientSessionId;
}
public String getUserSessionId() {
return userSessionId;

View file

@ -19,7 +19,6 @@ package org.keycloak.models.session;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -28,7 +27,6 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -39,7 +37,7 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
private final PersistentUserSessionModel model;
private final UserModel user;
private final RealmModel realm;
private final List<ClientSessionModel> clientSessions;
private final Map<String, AuthenticatedClientSessionModel> authenticatedClientSessions;
private PersistentUserSessionData data;
@ -60,14 +58,14 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
this.user = other.getUser();
this.realm = other.getRealm();
this.clientSessions = other.getClientSessions();
this.authenticatedClientSessions = other.getAuthenticatedClientSessions();
}
public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, List<ClientSessionModel> clientSessions) {
public PersistentUserSessionAdapter(PersistentUserSessionModel model, RealmModel realm, UserModel user, Map<String, AuthenticatedClientSessionModel> clientSessions) {
this.model = model;
this.realm = realm;
this.user = user;
this.clientSessions = clientSessions;
this.authenticatedClientSessions = clientSessions;
}
// Lazily init data
@ -160,15 +158,9 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
model.setLastSessionRefresh(seconds);
}
@Override
public List<ClientSessionModel> getClientSessions() {
return clientSessions;
}
// TODO:mposolda
@Override
public Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions() {
return null;
return authenticatedClientSessions;
}
@Override
@ -208,6 +200,11 @@ public class PersistentUserSessionAdapter implements UserSessionModel {
getData().setState(state);
}
@Override
public void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId) {
throw new IllegalStateException("Not supported");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

View file

@ -35,7 +35,7 @@ public interface UserSessionPersisterProvider extends Provider {
void createUserSession(UserSessionModel userSession, boolean offline);
// Assuming that corresponding userSession is already persisted
void createClientSession(UserSessionModel userSession, AuthenticatedClientSessionModel clientSession, boolean offline);
void createClientSession(AuthenticatedClientSessionModel clientSession, boolean offline);
void updateUserSession(UserSessionModel userSession, boolean offline);
@ -43,7 +43,7 @@ public interface UserSessionPersisterProvider extends Provider {
void removeUserSession(String userSessionId, boolean offline);
// Called during revoke. It will remove userSession too if this was last clientSession attached to it
void removeClientSession(String clientSessionId, boolean offline);
void removeClientSession(String userSessionId, String clientUUID, boolean offline);
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);

View file

@ -30,8 +30,8 @@ public interface AuthenticatedClientSessionModel extends CommonClientSessionMode
void setUserSession(UserSessionModel userSession);
UserSessionModel getUserSession();
public String getNote(String name);
public void setNote(String name, String value);
public void removeNote(String name);
public Map<String, String> getNotes();
String getNote(String name);
void setNote(String name, String value);
void removeNote(String name);
Map<String, String> getNotes();
}

View file

@ -17,7 +17,6 @@
package org.keycloak.models;
import java.util.List;
import java.util.Map;
/**
@ -55,9 +54,6 @@ public interface UserSessionModel {
Map<String, AuthenticatedClientSessionModel> getAuthenticatedClientSessions();
// TODO: Remove
List<ClientSessionModel> getClientSessions();
public String getNote(String name);
public void setNote(String name, String value);
public void removeNote(String name);
@ -68,8 +64,10 @@ public interface UserSessionModel {
void setUser(UserModel user);
// Will completely restart whole state of user session. It will just keep same ID.
void restartSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
public static enum State {
LOGGING_IN, // TODO:mposolda Maybe state "LOGGING_IN" is useless now once userSession is attached after requiredActions
LOGGED_IN,
LOGGING_OUT,
LOGGED_OUT

View file

@ -27,10 +27,7 @@ import java.util.List;
*/
public interface UserSessionProvider extends Provider {
ClientSessionModel createClientSession(RealmModel realm, ClientModel client);
AuthenticatedClientSessionModel createClientSession(RealmModel realm, ClientModel client, UserSessionModel userSession);
ClientSessionModel getClientSession(RealmModel realm, String id);
ClientSessionModel getClientSession(String id);
UserSessionModel createUserSession(String id, RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe, String brokerSessionId, String brokerUserId);
UserSessionModel getUserSession(RealmModel realm, String id);
@ -42,14 +39,13 @@ public interface UserSessionProvider extends Provider {
long getActiveUserSessions(RealmModel realm, ClientModel client);
// This will remove attached ClientLoginSessionModels too
/** This will remove attached ClientLoginSessionModels too **/
void removeUserSession(RealmModel realm, UserSessionModel session);
void removeUserSessions(RealmModel realm, UserModel user);
// Implementation should propagate removal of expired userSessions to userSessionPersister too
/** Implementation should propagate removal of expired userSessions to userSessionPersister too **/
void removeExpired(RealmModel realm);
void removeUserSessions(RealmModel realm);
void removeClientSession(RealmModel realm, ClientSessionModel clientSession);
UserLoginFailureModel getUserLoginFailure(RealmModel realm, String userId);
UserLoginFailureModel addUserLoginFailure(RealmModel realm, String userId);
@ -59,25 +55,22 @@ public interface UserSessionProvider extends Provider {
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);
/** Newly created userSession won't contain attached AuthenticatedClientSessions **/
UserSessionModel createOfflineUserSession(UserSessionModel userSession);
UserSessionModel getOfflineUserSession(RealmModel realm, String userSessionId);
// Removes the attached clientSessions as well
/** Removes the attached clientSessions as well **/
void removeOfflineUserSession(RealmModel realm, UserSessionModel userSession);
AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession);
ClientSessionModel getOfflineClientSession(RealmModel realm, String clientSessionId);
/** Will automatically attach newly created offline client session to the offlineUserSession **/
AuthenticatedClientSessionModel createOfflineClientSession(AuthenticatedClientSessionModel clientSession, UserSessionModel offlineUserSession);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, UserModel user);
// Don't remove userSession even if it's last userSession
void removeOfflineClientSession(RealmModel realm, String clientSessionId);
long getOfflineSessionsCount(RealmModel realm, ClientModel client);
List<UserSessionModel> getOfflineUserSessions(RealmModel realm, ClientModel client, int first, int max);
// Triggered by persister during pre-load
UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline);
ClientSessionModel importClientSession(ClientSessionModel persistentClientSession, boolean offline);
/** Triggered by persister during pre-load. It optionally imports authenticatedClientSessions too if requested. Otherwise the imported UserSession will have empty list of AuthenticationSessionModel **/
UserSessionModel importUserSession(UserSessionModel persistentUserSession, boolean offline, boolean importAuthenticatedClientSessions);
ClientInitialAccessModel createClientInitialAccessModel(RealmModel realm, int expiration, int count);
ClientInitialAccessModel getClientInitialAccessModel(RealmModel realm, String id);

View file

@ -35,7 +35,6 @@ public interface AuthenticationSessionProvider extends Provider {
void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authenticationSession);
// TODO: test and add to scheduler
void removeExpired(RealmModel realm);
void onRealmRemoved(RealmModel realm);
void onClientRemoved(RealmModel realm, ClientModel client);

View file

@ -33,7 +33,6 @@ import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -53,11 +52,11 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.PageExpiredRedirect;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.HashMap;
@ -73,7 +72,6 @@ public class AuthenticationProcessor {
public static final String LAST_PROCESSED_EXECUTION = "last.processed.execution";
public static final String CURRENT_FLOW_PATH = "current.flow.path";
public static final String FORKED_FROM = "forked.from";
public static final String FORWARDED_ERROR_MESSAGE_NOTE = "forwardedErrorMessage";
public static final String BROKER_SESSION_ID = "broker.session.id";
public static final String BROKER_USER_ID = "broker.user.id";
@ -595,9 +593,9 @@ public class AuthenticationProcessor {
}
public boolean isSuccessful(AuthenticationExecutionModel model) {
ClientSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
AuthenticationSessionModel.ExecutionStatus status = authenticationSession.getExecutionStatus().get(model.getId());
if (status == null) return false;
return status == ClientSessionModel.ExecutionStatus.SUCCESS;
return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS;
}
public Response handleBrowserException(Exception failure) {
@ -629,7 +627,7 @@ public class AuthenticationProcessor {
} else if (e.getError() == AuthenticationFlowError.FORK_FLOW) {
ForkFlowException reset = (ForkFlowException)e;
AuthenticationSessionModel clone = clone(session, authenticationSession);
clone.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
clone.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
setAuthenticationSession(clone);
AuthenticationProcessor processor = new AuthenticationProcessor();
@ -726,9 +724,9 @@ public class AuthenticationProcessor {
public Response redirectToFlow() {
URI redirect = new PageExpiredRedirect(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
URI redirect = new AuthenticationFlowURLHelper(session, realm, uriInfo).getLastExecutionUrl(authenticationSession);
logger.info("Redirecting to URL: " + redirect.toString());
logger.debug("Redirecting to URL: " + redirect.toString());
return Response.status(302).location(redirect).build();
@ -750,6 +748,8 @@ public class AuthenticationProcessor {
authSession.clearUserSessionNotes();
authSession.clearAuthNotes();
authSession.setAction(CommonClientSessionModel.Action.AUTHENTICATE.name());
authSession.setAuthNote(CURRENT_FLOW_PATH, flowPath);
}
@ -766,7 +766,7 @@ public class AuthenticationProcessor {
clone.setTimestamp(Time.currentTime());
clone.setAuthNote(FORKED_FROM, authSession.getId());
logger.infof("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
logger.debugf("Forked authSession %s from authSession %s", clone.getId(), authSession.getId());
return clone;
@ -777,10 +777,9 @@ public class AuthenticationProcessor {
logger.debug("authenticationAction");
checkClientSession(true);
String current = authenticationSession.getAuthNote(CURRENT_AUTHENTICATION_EXECUTION);
if (!execution.equals(current)) {
// TODO:mposolda debug
logger.info("Current execution does not equal executed execution. Might be a page refresh");
return new PageExpiredRedirect(session, realm, uriInfo).showPageExpired(authenticationSession);
if (execution == null || !execution.equals(current)) {
logger.debug("Current execution does not equal executed execution. Might be a page refresh");
return new AuthenticationFlowURLHelper(session, realm, uriInfo).showPageExpired(authenticationSession);
}
UserModel authUser = authenticationSession.getAuthenticatedUser();
validateUser(authUser);
@ -812,7 +811,7 @@ public class AuthenticationProcessor {
ClientSessionCode code = new ClientSessionCode(session, realm, authenticationSession);
if (checkAction) {
String action = ClientSessionModel.Action.AUTHENTICATE.name();
String action = AuthenticationSessionModel.Action.AUTHENTICATE.name();
if (!code.isValidAction(action)) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CLIENT_SESSION);
}
@ -862,26 +861,29 @@ public class AuthenticationProcessor {
if (attemptedUsername != null) username = attemptedUsername;
String rememberMe = authSession.getAuthNote(Details.REMEMBER_ME);
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("true");
String brokerSessionId = authSession.getAuthNote(BROKER_SESSION_ID);
String brokerUserId = authSession.getAuthNote(BROKER_USER_ID);
if (userSession == null) { // if no authenticator attached a usersession
userSession = session.sessions().getUserSession(realm, authSession.getId());
if (userSession == null) {
String brokerSessionId = authSession.getAuthNote(BROKER_SESSION_ID);
String brokerUserId = authSession.getAuthNote(BROKER_USER_ID);
userSession = session.sessions().createUserSession(authSession.getId(), realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
, remember, brokerSessionId, brokerUserId);
} else if (userSession.getUser() == null || !AuthenticationManager.isSessionValid(realm, userSession)) {
userSession.restartSession(realm, authSession.getAuthenticatedUser(), username, connection.getRemoteAddr(), authSession.getProtocol()
, remember, brokerSessionId, brokerUserId);
} else {
// We have existing userSession even if it wasn't attached to authenticator. Could happen if SSO authentication was ignored (eg. prompt=login) and in some other cases.
// We need to handle case when different user was used and update that (TODO:mposolda evaluate this again and corner cases like token refresh etc. AND ROLES!!! LIKELY ERROR SHOULD BE SHOWN IF ATTEMPT TO AUTHENTICATE AS DIFFERENT USER)
logger.info("No SSO login, but found existing userSession with ID '%s' after finished authentication.");
// We need to handle case when different user was used
logger.debugf("No SSO login, but found existing userSession with ID '%s' after finished authentication.", userSession.getId());
if (!authSession.getAuthenticatedUser().equals(userSession.getUser())) {
event.detail(Details.EXISTING_USER, userSession.getUser().getId());
event.error(Errors.DIFFERENT_USER_AUTHENTICATED);
throw new ErrorPageException(session, Messages.DIFFERENT_USER_AUTHENTICATED, userSession.getUser().getUsername());
}
}
userSession.setState(UserSessionModel.State.LOGGING_IN);
userSession.setState(UserSessionModel.State.LOGGED_IN);
}
if (remember) {

View file

@ -20,9 +20,9 @@ package org.keycloak.authentication;
import org.jboss.logging.Logger;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.ServicesLogger;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Response;
import java.util.Iterator;
@ -51,11 +51,11 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
protected boolean isProcessed(AuthenticationExecutionModel model) {
if (model.isDisabled()) return true;
ClientSessionModel.ExecutionStatus status = processor.getAuthenticationSession().getExecutionStatus().get(model.getId());
AuthenticationSessionModel.ExecutionStatus status = processor.getAuthenticationSession().getExecutionStatus().get(model.getId());
if (status == null) return false;
return status == ClientSessionModel.ExecutionStatus.SUCCESS || status == ClientSessionModel.ExecutionStatus.SKIPPED
|| status == ClientSessionModel.ExecutionStatus.ATTEMPTED
|| status == ClientSessionModel.ExecutionStatus.SETUP_REQUIRED;
return status == AuthenticationSessionModel.ExecutionStatus.SUCCESS || status == AuthenticationSessionModel.ExecutionStatus.SKIPPED
|| status == AuthenticationSessionModel.ExecutionStatus.ATTEMPTED
|| status == AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED;
}
@ -75,7 +75,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
Response flowChallenge = authenticationFlow.processAction(actionExecution);
if (flowChallenge == null) {
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
return processFlow();
} else {
@ -115,7 +115,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
}
if (model.isAlternative() && alternativeSuccessful) {
logger.debug("Skip alternative execution");
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
if (model.isAuthenticatorFlow()) {
@ -123,7 +123,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
AuthenticationFlow authenticationFlow = processor.createFlowExecution(model.getFlowId(), model);
Response flowChallenge = authenticationFlow.processFlow();
if (flowChallenge == null) {
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
if (model.isAlternative()) alternativeSuccessful = true;
continue;
} else {
@ -131,13 +131,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
alternativeChallenge = flowChallenge;
challengedAlternativeExecution = model;
} else if (model.isRequired()) {
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return flowChallenge;
} else if (model.isOptional()) {
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
} else {
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
return flowChallenge;
@ -154,7 +154,7 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (authenticator.requiresUser() && authUser == null) {
if (alternativeChallenge != null) {
processor.getAuthenticationSession().setExecutionStatus(challengedAlternativeExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
processor.getAuthenticationSession().setExecutionStatus(challengedAlternativeExecution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return alternativeChallenge;
}
throw new AuthenticationFlowException("authenticator: " + factory.getId(), AuthenticationFlowError.UNKNOWN_USER);
@ -166,14 +166,14 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
if (model.isRequired()) {
if (factory.isUserSetupAllowed()) {
logger.debugv("authenticator SETUP_REQUIRED: {0}", factory.getId());
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED);
authenticator.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser());
continue;
} else {
throw new AuthenticationFlowException(AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED);
}
} else if (model.isOptional()) {
processor.getAuthenticationSession().setExecutionStatus(model.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
processor.getAuthenticationSession().setExecutionStatus(model.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
@ -198,13 +198,13 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
switch (status) {
case SUCCESS:
logger.debugv("authenticator SUCCESS: {0}", execution.getAuthenticator());
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
if (execution.isAlternative()) alternativeSuccessful = true;
return null;
case FAILED:
logger.debugv("authenticator FAILED: {0}", execution.getAuthenticator());
processor.logFailure();
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.FAILED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.FAILED);
if (result.getChallenge() != null) {
return sendChallenge(result, execution);
}
@ -214,37 +214,37 @@ public class DefaultAuthenticationFlow implements AuthenticationFlow {
processor.getAuthenticationSession().setAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION, execution.getId());
throw new ForkFlowException(result.getSuccessMessage(), result.getErrorMessage());
case FORCE_CHALLENGE:
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
case CHALLENGE:
logger.debugv("authenticator CHALLENGE: {0}", execution.getAuthenticator());
if (execution.isRequired()) {
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
UserModel authenticatedUser = processor.getAuthenticationSession().getAuthenticatedUser();
if (execution.isOptional() && authenticatedUser != null && result.getAuthenticator().configuredFor(processor.getSession(), processor.getRealm(), authenticatedUser)) {
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
}
if (execution.isAlternative()) {
alternativeChallenge = result.getChallenge();
challengedAlternativeExecution = execution;
} else {
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
}
return null;
case FAILURE_CHALLENGE:
logger.debugv("authenticator FAILURE_CHALLENGE: {0}", execution.getAuthenticator());
processor.logFailure();
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
return sendChallenge(result, execution);
case ATTEMPTED:
logger.debugv("authenticator ATTEMPTED: {0}", execution.getAuthenticator());
if (execution.getRequirement() == AuthenticationExecutionModel.Requirement.REQUIRED) {
throw new AuthenticationFlowException(AuthenticationFlowError.INVALID_CREDENTIALS);
}
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), ClientSessionModel.ExecutionStatus.ATTEMPTED);
processor.getAuthenticationSession().setExecutionStatus(execution.getId(), AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
return null;
case FLOW_RESET:
processor.resetFlow();

View file

@ -24,7 +24,6 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationExecutionModel;
import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -167,13 +166,13 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
if (!actionExecution.equals(formExecution.getId())) {
throw new AuthenticationFlowException("action is not current execution", AuthenticationFlowError.INTERNAL_ERROR);
}
Map<String, ClientSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus = new HashMap<>();
List<FormAction> requiredActions = new LinkedList<>();
List<ValidationContextImpl> successes = new LinkedList<>();
List<ValidationContextImpl> errors = new LinkedList<>();
for (AuthenticationExecutionModel formActionExecution : formActionExecutions) {
if (!formActionExecution.isEnabled()) {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
FormActionFactory factory = (FormActionFactory)processor.getSession().getKeycloakSessionFactory().getProviderFactory(FormAction.class, formActionExecution.getAuthenticator());
@ -190,14 +189,14 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
if (formActionExecution.isRequired()) {
if (factory.isUserSetupAllowed()) {
AuthenticationProcessor.logger.debugv("authenticator SETUP_REQUIRED: {0}", formExecution.getAuthenticator());
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SETUP_REQUIRED);
executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SETUP_REQUIRED);
requiredActions.add(action);
continue;
} else {
throw new AuthenticationFlowException(AuthenticationFlowError.CREDENTIAL_SETUP_REQUIRED);
}
} else if (formActionExecution.isOptional()) {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SKIPPED);
executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SKIPPED);
continue;
}
}
@ -206,10 +205,10 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
ValidationContextImpl result = new ValidationContextImpl(formActionExecution, action);
action.validate(result);
if (result.success) {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.SUCCESS);
executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.SUCCESS);
successes.add(result);
} else {
executionStatus.put(formActionExecution.getId(), ClientSessionModel.ExecutionStatus.CHALLENGED);
executionStatus.put(formActionExecution.getId(), AuthenticationSessionModel.ExecutionStatus.CHALLENGED);
errors.add(result);
}
}
@ -235,14 +234,14 @@ public class FormAuthenticationFlow implements AuthenticationFlow {
context.action.success(context);
}
// set status and required actions only if form is fully successful
for (Map.Entry<String, ClientSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
for (Map.Entry<String, AuthenticationSessionModel.ExecutionStatus> entry : executionStatus.entrySet()) {
processor.getAuthenticationSession().setExecutionStatus(entry.getKey(), entry.getValue());
}
for (FormAction action : requiredActions) {
action.setRequiredActions(processor.getSession(), processor.getRealm(), processor.getAuthenticationSession().getAuthenticatedUser());
}
processor.getAuthenticationSession().setExecutionStatus(actionExecution, ClientSessionModel.ExecutionStatus.SUCCESS);
processor.getAuthenticationSession().setExecutionStatus(actionExecution, AuthenticationSessionModel.ExecutionStatus.SUCCESS);
processor.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
return null;
}

View file

@ -20,12 +20,14 @@ import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.actiontoken.*;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.events.Errors;
import org.keycloak.events.EventType;
import org.keycloak.models.UserModel;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.resources.LoginActionsServiceChecks.IsActionRequired;
import org.keycloak.sessions.CommonClientSessionModel.Action;
import javax.ws.rs.core.Response;
@ -61,7 +63,7 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
@Override
public Response handleToken(ResetCredentialsActionToken token, ActionTokenContext tokenContext, ProcessFlow processFlow) {
AuthenticationProcessor authProcessor = new ResetCredsAuthenticationProcessor(tokenContext);
AuthenticationProcessor authProcessor = new ResetCredsAuthenticationProcessor();
return processFlow.processFlow(
false,
@ -87,34 +89,31 @@ public class ResetCredentialsActionTokenHandler extends AbstractActionTokenHande
public static class ResetCredsAuthenticationProcessor extends AuthenticationProcessor {
private final ActionTokenContext tokenContext;
public ResetCredsAuthenticationProcessor(ActionTokenContext tokenContext) {
this.tokenContext = tokenContext;
}
@Override
protected Response authenticationComplete() {
boolean firstBrokerLoginInProgress = (tokenContext.getAuthenticationSession().getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
boolean firstBrokerLoginInProgress = (authenticationSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
if (firstBrokerLoginInProgress) {
UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, tokenContext.getRealm(), tokenContext.getAuthenticationSession());
if (!linkingUser.getId().equals(tokenContext.getAuthenticationSession().getAuthenticatedUser().getId())) {
UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
return ErrorPage.error(session,
Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE,
tokenContext.getAuthenticationSession().getAuthenticatedUser().getUsername(),
authenticationSession.getAuthenticatedUser().getUsername(),
linkingUser.getUsername()
);
}
logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login.", linkingUser.getUsername());
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authenticationSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
authenticationSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
// TODO:mposolda Isn't this a bug that we redirect to 'afterBrokerLoginEndpoint' without rather continue with firstBrokerLogin and other authenticators like OTP?
//return redirectToAfterBrokerLoginEndpoint(authSession, true);
return null;
logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login with identity provider '%s'.",
linkingUser.getUsername(), serializedCtx.getIdentityProviderId());
return LoginActionsService.redirectToAfterBrokerLoginEndpoint(session, realm, uriInfo, authenticationSession, true);
} else {
return super.authenticationComplete();
}
}
}
}

View file

@ -47,7 +47,7 @@ import java.util.Map;
* <li>{@code realm} the {@link RealmModel}</li>
* <li>{@code user} the current {@link UserModel}</li>
* <li>{@code session} the active {@link KeycloakSession}</li>
* <li>{@code clientSession} the current {@link org.keycloak.models.ClientSessionModel}</li>
* <li>{@code clientSession} the current {@link org.keycloak.sessions.AuthenticationSessionModel}</li>
* <li>{@code httpRequest} the current {@link org.jboss.resteasy.spi.HttpRequest}</li>
* <li>{@code LOG} a {@link org.jboss.logging.Logger} scoped to {@link ScriptBasedAuthenticator}/li>
* </ol>

View file

@ -69,17 +69,14 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
return;
}
LoginFormsProvider loginFormsProvider = context.getSession().getProvider(LoginFormsProvider.class)
.setClientSessionCode(context.generateCode())
.setAuthenticationSession(authSession)
.setUser(context.getUser());
LoginFormsProvider loginFormsProvider = context.form();
Response challenge;
// Do not allow resending e-mail by simple page refresh, i.e. when e-mail sent, it should be resent properly via email-verification endpoint
if (! Objects.equals(authSession.getAuthNote(Constants.VERIFY_EMAIL_KEY), email)) {
authSession.setAuthNote(Constants.VERIFY_EMAIL_KEY, email);
context.getEvent().clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, email).success();
challenge = sendVerifyEmail(context.getSession(), context.generateCode(), context.getUser(), context.getAuthenticationSession());
challenge = sendVerifyEmail(context.getSession(), loginFormsProvider, context.getUser(), context.getAuthenticationSession());
} else {
challenge = loginFormsProvider.createResponse(UserModel.RequiredAction.VERIFY_EMAIL);
}
@ -87,9 +84,15 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
context.challenge(challenge);
}
@Override
public void processAction(RequiredActionContext context) {
context.failure();
logger.infof("Re-sending email requested for user: %s", context.getUser().getUsername());
// This will allow user to re-send email again
context.getAuthenticationSession().removeAuthNote(Constants.VERIFY_EMAIL_KEY);
requiredActionChallenge(context);
}
@ -124,15 +127,10 @@ public class VerifyEmail implements RequiredActionProvider, RequiredActionFactor
return UserModel.RequiredAction.VERIFY_EMAIL.name();
}
public static Response sendVerifyEmail(KeycloakSession session, String clientCode, UserModel user, AuthenticationSessionModel authSession) throws UriBuilderException, IllegalArgumentException {
private Response sendVerifyEmail(KeycloakSession session, LoginFormsProvider forms, UserModel user, AuthenticationSessionModel authSession) throws UriBuilderException, IllegalArgumentException {
RealmModel realm = session.getContext().getRealm();
UriInfo uriInfo = session.getContext().getUri();
LoginFormsProvider forms = session.getProvider(LoginFormsProvider.class)
.setClientSessionCode(clientCode)
.setAuthenticationSession(authSession)
.setUser(authSession.getAuthenticatedUser());
int validityInSecs = realm.getAccessCodeLifespanUserAction();
int absoluteExpirationInSecs = Time.currentTime() + validityInSecs;
// ExecuteActionsActionToken token = new ExecuteActionsActionToken(user.getId(), absoluteExpirationInSecs, null,

View file

@ -23,7 +23,6 @@ import org.keycloak.authorization.attribute.Attributes;
import org.keycloak.authorization.identity.Identity;
import org.keycloak.authorization.util.Tokens;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
@ -118,8 +117,8 @@ public class KeycloakIdentity implements Identity {
@Override
public String getId() {
if (isResourceServer()) {
ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
return clientSession.getClient().getId();
ClientModel client = getTargetClient();
return client==null ? null : client.getId();
}
return this.accessToken.getSubject();
@ -137,20 +136,10 @@ public class KeycloakIdentity implements Identity {
private boolean isResourceServer() {
UserModel clientUser = null;
if (this.accessToken.getClientSession() != null) {
ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession());
ClientModel clientModel = getTargetClient();
if (clientSession != null) {
clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient());
}
}
if (this.accessToken.getIssuedFor() != null) {
ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm);
if (clientModel != null) {
clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
}
if (clientModel != null) {
clientUser = this.keycloakSession.users().getServiceAccount(clientModel);
}
if (clientUser == null) {
@ -159,4 +148,17 @@ public class KeycloakIdentity implements Identity {
return this.accessToken.getSubject().equals(clientUser.getId());
}
private ClientModel getTargetClient() {
if (this.accessToken.getIssuedFor() != null) {
return realm.getClientByClientId(accessToken.getIssuedFor());
}
if (this.accessToken.getAudience() != null && this.accessToken.getAudience().length > 0) {
String audience = this.accessToken.getAudience()[0];
return realm.getClientByClientId(audience);
}
return null;
}
}

View file

@ -32,7 +32,6 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.keys.loader.PublicKeyStorageManager;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;

View file

@ -31,7 +31,6 @@ import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.events.EventBuilder;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;

View file

@ -19,7 +19,6 @@ package org.keycloak.forms.account.freemarker.model;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@ -79,8 +78,8 @@ public class SessionsBean {
public Set<String> getClients() {
Set<String> clients = new HashSet<String>();
for (ClientSessionModel clientSession : session.getClientSessions()) {
ClientModel client = clientSession.getClient();
for (String clientUUID : session.getAuthenticatedClientSessions().keySet()) {
ClientModel client = realm.getClientById(clientUUID);
clients.add(client.getClientId());
}
return clients;

View file

@ -90,7 +90,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
private UserModel user;
private AuthenticationSessionModel authenticationSession;
private final Map<String, Object> attributes = new HashMap<String, Object>();
public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
@ -156,6 +155,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
uriBuilder.replaceQuery(null);
}
URI baseUri = uriBuilder.build();
if (accessCode != null) {
uriBuilder.queryParam(OAuth2Constants.CODE, accessCode);
}
URI baseUriWithCode = uriBuilder.build();
for (String k : queryParameterMap.keySet()) {
Object[] objects = queryParameterMap.get(k).toArray();
@ -163,13 +169,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
uriBuilder.replaceQueryParam(k, objects);
}
// TODO:hmlnarik Why was the following removed in https://github.com/hmlnarik/keycloak/commit/6df8f13109d6ea77b455e04d884994e5831ea52b#diff-d795b851c2db89d5198c897aba4c40c9
if (accessCode != null) {
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
}
URI baseUri = uriBuilder.build();
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
Theme theme;
try {
@ -221,7 +220,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
identityProviders = LoginFormsUtil.filterIdentityProviders(identityProviders, session, realm, attributes, formData);
attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUri));
attributes.put("social", new IdentityProviderBean(realm, session, identityProviders, baseUriWithCode));
attributes.put("url", new UrlBean(realm, theme, baseUri, this.actionUri));
@ -313,9 +312,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
if (objects.length == 1 && objects[0] == null) continue; //
uriBuilder.replaceQueryParam(k, objects);
}
if (accessCode != null) {
uriBuilder.replaceQueryParam(OAuth2Constants.CODE, accessCode);
}
URI baseUri = uriBuilder.build();
if (accessCode != null) {
@ -572,12 +569,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
return this;
}
@Override
public LoginFormsProvider setAuthenticationSession(AuthenticationSessionModel authSession) {
this.authenticationSession = authSession;
return this;
}
@Override
public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String, RoleModel> resourceRolesRequested, List<ProtocolMapperModel> protocolMappersRequested) {
this.realmRolesRequested = realmRolesRequested;

View file

@ -85,10 +85,6 @@ public class UrlBean {
return Urls.loginUsernameReminder(baseURI, realm).toString();
}
public String getLoginEmailVerificationUrl() {
return Urls.loginActionEmailVerification(baseURI, realm).toString();
}
public String getFirstBrokerLoginUrl() {
return Urls.firstBrokerLoginProcessor(baseURI, realm).toString();
}

View file

@ -21,7 +21,6 @@ import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.Details;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.AuthenticationFlowModel;
@ -35,7 +34,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.PageExpiredRedirect;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import javax.ws.rs.core.Context;
@ -159,7 +158,7 @@ public abstract class AuthorizationEndpointBase {
ClientSessionCode<AuthenticationSessionModel> check = new ClientSessionCode<>(session, realm, authSession);
if (!check.isActionActive(ClientSessionCode.ActionType.LOGIN)) {
logger.infof("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
logger.debugf("Authentication session '%s' exists, but is expired. Restart existing authentication session", authSession.getId());
authSession.restartSession(realm, client);
return new AuthorizationEndpointChecks(authSession);
@ -167,10 +166,10 @@ public abstract class AuthorizationEndpointBase {
// Check if we have lastProcessedExecution and restart the session just if yes. Otherwise update just client information from the AuthorizationEndpoint request.
// This difference is needed, because of logout from JS applications in multiple browser tabs.
if (hasProcessedExecution(authSession)) {
logger.info("New request from application received, but authentication session already exists. Restart existing authentication session");
logger.debug("New request from application received, but authentication session already exists. Restart existing authentication session");
authSession.restartSession(realm, client);
} else {
logger.info("New request from application received, but authentication session already exists. Update client information in existing authentication session");
logger.debug("New request from application received, but authentication session already exists. Update client information in existing authentication session");
authSession.clearClientNotes(); // update client data
authSession.updateClient(client);
}
@ -178,7 +177,7 @@ public abstract class AuthorizationEndpointBase {
return new AuthorizationEndpointChecks(authSession);
} else {
logger.info("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
logger.debug("Re-sent some previous request to Authorization endpoint. Likely browser 'back' or 'refresh' button.");
// See if we have lastProcessedExecution note. If yes, we are expired. Also if we are in different flow than initial one. Otherwise it is browser refresh of initial username/password form
if (!shouldShowExpirePage(authSession)) {
@ -186,7 +185,7 @@ public abstract class AuthorizationEndpointBase {
} else {
CacheControlUtil.noBackButtonCacheControlHeader();
Response response = new PageExpiredRedirect(session, realm, uriInfo)
Response response = new AuthenticationFlowURLHelper(session, realm, uriInfo)
.showPageExpired(authSession);
return new AuthorizationEndpointChecks(response);
}
@ -196,11 +195,11 @@ public abstract class AuthorizationEndpointBase {
UserSessionModel userSession = authSessionId==null ? null : session.sessions().getUserSession(realm, authSessionId);
if (userSession != null) {
logger.infof("Sent request to authz endpoint. We don't have authentication session with ID '%s' but we have userSession. Will re-create authentication session with same ID", authSessionId);
logger.debugf("Sent request to authz endpoint. We don't have authentication session with ID '%s' but we have userSession. Will re-create authentication session with same ID", authSessionId);
authSession = session.authenticationSessions().createAuthenticationSession(authSessionId, realm, client);
} else {
authSession = manager.createAuthenticationSession(realm, client, true);
logger.infof("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
logger.debugf("Sent request to authz endpoint. Created new authentication session with ID '%s'", authSession.getId());
}
return new AuthorizationEndpointChecks(authSession);
@ -224,17 +223,13 @@ public abstract class AuthorizationEndpointBase {
}
String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
// Check if we transitted between flows (eg. clicking "register" on login screen)
if (!initialFlow.equals(lastFlow)) {
logger.infof("Transition between flows! Current flow: %s, Previous flow: %s", initialFlow, lastFlow);
// Check if we transitted between flows (eg. clicking "register" on login screen and then clicking browser 'back', which showed this page)
if (!initialFlow.equals(lastFlow) && AuthenticationSessionModel.Action.AUTHENTICATE.toString().equals(authSession.getAction())) {
logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", initialFlow, lastFlow);
if (lastFlow == null || LoginActionsService.isFlowTransitionAllowed(initialFlow, lastFlow)) {
authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, initialFlow);
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
return false;
} else {
return true;
}
authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, initialFlow);
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
return false;
}
return false;

View file

@ -575,7 +575,6 @@ public class TokenManager {
protected AccessToken initToken(RealmModel realm, ClientModel client, UserModel user, UserSessionModel session, AuthenticatedClientSessionModel clientSession, UriInfo uriInfo) {
AccessToken token = new AccessToken();
token.clientSession(clientSession.getId());
token.id(KeycloakModelUtils.generateId());
token.type(TokenUtil.TOKEN_TYPE_BEARER);
token.subject(user.getId());

View file

@ -28,7 +28,6 @@ import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.RealmModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@ -41,7 +40,6 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.services.ErrorPageException;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsService;
import org.keycloak.services.util.CacheControlUtil;
@ -387,7 +385,7 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
private void updateAuthenticationSession() {
authenticationSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
authenticationSession.setRedirectUri(redirectUri);
authenticationSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
authenticationSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authenticationSession.setClientNote(OIDCLoginProtocol.RESPONSE_TYPE_PARAM, request.getResponseType());
authenticationSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, request.getRedirectUriParam());
authenticationSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(uriInfo.getBaseUri(), realm.getName()));

View file

@ -210,12 +210,13 @@ public class TokenEndpoint {
throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "Missing parameter: " + OAuth2Constants.CODE, Response.Status.BAD_REQUEST);
}
String[] parts = code.split("\\.");
if (parts.length == 4) {
event.detail(Details.CODE_ID, parts[2]);
}
ClientSessionCode.ParseResult<AuthenticatedClientSessionModel> parseResult = ClientSessionCode.parseResult(code, session, realm, AuthenticatedClientSessionModel.class);
if (parseResult.isAuthSessionNotFound() || parseResult.isIllegalHash()) {
String[] parts = code.split("\\.");
if (parts.length == 2) {
event.detail(Details.CODE_ID, parts[1]);
}
event.error(Errors.INVALID_CODE);
// Attempt to use same code twice should invalidate existing clientSession
@ -228,17 +229,16 @@ public class TokenEndpoint {
}
AuthenticatedClientSessionModel clientSession = parseResult.getClientSession();
event.detail(Details.CODE_ID, clientSession.getId());
if (!parseResult.getCode().isValid(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), ClientSessionCode.ActionType.CLIENT)) {
event.error(Errors.INVALID_CODE);
throw new ErrorResponseException(OAuthErrorException.INVALID_GRANT, "Code is expired", Response.Status.BAD_REQUEST);
}
// TODO: This shouldn't be needed to write into the clientLoginSessionModel itself
// TODO: This shouldn't be needed to write into the AuthenticatedClientSessionModel itself
parseResult.getCode().setAction(null);
// TODO: Maybe rather create userSession even at this stage? Not sure...
// TODO: Maybe rather create userSession even at this stage?
UserSessionModel userSession = clientSession.getUserSession();
if (userSession == null) {

View file

@ -93,9 +93,9 @@ abstract class AbstractUserRoleMappingMapper extends AbstractOIDCProtocolMapper
// get a set of all realm roles assigned to the user or its group
Stream<RoleModel> clientUserRoles = getAllUserRolesStream(user).filter(restriction);
boolean dontLimitScope = userSession.getClientSessions().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
boolean dontLimitScope = userSession.getAuthenticatedClientSessions().values().stream().anyMatch(cs -> cs.getClient().isFullScopeAllowed());
if (! dontLimitScope) {
Set<RoleModel> clientRoles = userSession.getClientSessions().stream()
Set<RoleModel> clientRoles = userSession.getAuthenticatedClientSessions().values().stream()
.flatMap(cs -> cs.getClient().getScopeMappings().stream())
.collect(Collectors.toSet());

View file

@ -376,8 +376,12 @@ public class SamlProtocol implements LoginProtocol {
clientSession.setNote(SAML_NAME_ID_FORMAT, nameIdFormat);
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan()).sessionIndex(clientSession.getId())
builder.requestID(requestID).destination(redirectUri).issuer(responseIssuer).assertionExpiration(realm.getAccessCodeLifespan()).subjectExpiration(realm.getAccessTokenLifespan())
.requestIssuer(clientSession.getClient().getClientId()).nameIdentifier(nameIdFormat, nameId).authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get());
String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
builder.sessionIndex(sessionIndex);
if (!samlClient.includeAuthnStatement()) {
builder.disableAuthnStatement(true);
}
@ -682,8 +686,12 @@ public class SamlProtocol implements LoginProtocol {
protected SAML2LogoutRequestBuilder createLogoutRequest(String logoutUrl, AuthenticatedClientSessionModel clientSession, ClientModel client) {
// build userPrincipal with subject used at login
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm)).sessionIndex(clientSession.getId())
SAML2LogoutRequestBuilder logoutBuilder = new SAML2LogoutRequestBuilder().assertionExpiration(realm.getAccessCodeLifespan()).issuer(getResponseIssuer(realm))
.userPrincipal(clientSession.getNote(SAML_NAME_ID), clientSession.getNote(SAML_NAME_ID_FORMAT)).destination(logoutUrl);
String sessionIndex = SamlSessionUtils.getSessionIndex(clientSession);
logoutBuilder.sessionIndex(sessionIndex);
return logoutBuilder;
}

View file

@ -37,8 +37,8 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.keys.RsaKeyMetadata;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
@ -55,7 +55,6 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.util.CacheControlUtil;
@ -99,11 +98,6 @@ public class SamlService extends AuthorizationEndpointBase {
protected static final Logger logger = Logger.getLogger(SamlService.class);
@Context
protected KeycloakSession session;
private String requestRelayState;
public SamlService(RealmModel realm, EventBuilder event) {
super(realm, event);
}
@ -283,7 +277,7 @@ public class SamlService extends AuthorizationEndpointBase {
authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
authSession.setRedirectUri(redirect);
authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authSession.setClientNote(SamlProtocol.SAML_BINDING, bindingType);
authSession.setClientNote(GeneralConstants.RELAY_STATE, relayState);
authSession.setClientNote(SamlProtocol.SAML_REQUEST_ID, requestAbstractType.getID());
@ -378,31 +372,22 @@ public class SamlService extends AuthorizationEndpointBase {
userSession.setNote(SamlProtocol.SAML_LOGOUT_CANONICALIZATION, samlClient.getCanonicalizationMethod());
userSession.setNote(AuthenticationManager.KEYCLOAK_LOGOUT_PROTOCOL, SamlProtocol.LOGIN_PROTOCOL);
// remove client from logout requests
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
if (clientSession.getClient().getId().equals(client.getId())) {
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
}
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessions().get(client.getId());
if (clientSession != null) {
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
}
logger.debug("browser Logout");
return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
} else if (logoutRequest.getSessionIndex() != null) {
for (String sessionIndex : logoutRequest.getSessionIndex()) {
ClientSessionModel clientSession = session.sessions().getClientSession(realm, sessionIndex);
AuthenticatedClientSessionModel clientSession = SamlSessionUtils.getClientSession(session, realm, sessionIndex);
if (clientSession == null)
continue;
UserSessionModel userSession = clientSession.getUserSession();
if (clientSession.getClient().getClientId().equals(client.getClientId())) {
// remove requesting client from logout
clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
// Remove also other clientSessions of this client as there could be more in this UserSession
if (userSession != null) {
for (ClientSessionModel clientSession2 : userSession.getClientSessions()) {
if (clientSession2.getClient().getId().equals(client.getId())) {
clientSession2.setAction(ClientSessionModel.Action.LOGGED_OUT.name());
}
}
}
clientSession.setAction(AuthenticationSessionModel.Action.LOGGED_OUT.name());
}
try {
@ -609,6 +594,10 @@ public class SamlService extends AuthorizationEndpointBase {
event.error(Errors.CLIENT_NOT_FOUND);
return ErrorPage.error(session, Messages.CLIENT_NOT_FOUND);
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
return ErrorPage.error(session, Messages.CLIENT_DISABLED);
}
if (client.getManagementUrl() == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_POST_ATTRIBUTE) == null && client.getAttribute(SamlProtocol.SAML_ASSERTION_CONSUMER_URL_REDIRECT_ATTRIBUTE) == null) {
logger.error("SAML assertion consumer url not set up");
event.error(Errors.INVALID_REDIRECT_URI);
@ -654,7 +643,7 @@ public class SamlService extends AuthorizationEndpointBase {
AuthenticationSessionModel authSession = checks.authSession;
authSession.setProtocol(SamlProtocol.LOGIN_PROTOCOL);
authSession.setAction(ClientSessionModel.Action.AUTHENTICATE.name());
authSession.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
authSession.setClientNote(SamlProtocol.SAML_BINDING, SamlProtocol.SAML_POST_BINDING);
authSession.setClientNote(SamlProtocol.SAML_IDP_INITIATED_LOGIN, "true");
authSession.setRedirectUri(redirect);

View file

@ -0,0 +1,65 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.protocol.saml;
import java.util.regex.Pattern;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class SamlSessionUtils {
private static final String DELIMITER = "::";
// Just perf optimization
private static final Pattern PATTERN = Pattern.compile(DELIMITER);
public static String getSessionIndex(AuthenticatedClientSessionModel clientSession) {
UserSessionModel userSession = clientSession.getUserSession();
ClientModel client = clientSession.getClient();
return userSession.getId() + DELIMITER + client.getId();
}
public static AuthenticatedClientSessionModel getClientSession(KeycloakSession session, RealmModel realm, String sessionIndex) {
if (sessionIndex == null) {
return null;
}
String[] parts = PATTERN.split(sessionIndex);
if (parts.length != 2) {
return null;
}
UserSessionModel userSession = session.sessions().getUserSession(realm, parts[0]);
if (userSession == null) {
return null;
}
return userSession.getAuthenticatedClientSessions().get(parts[1]);
}
}

View file

@ -37,6 +37,8 @@ public class ErrorPageException extends WebApplicationException {
this.parameters = parameters;
}
@Override
public Response getResponse() {
return ErrorPage.error(session, errorMessage, parameters);

View file

@ -537,7 +537,7 @@ public class AuthenticationManager {
.createInfoPage();
return response;
// TODO:mposolda doublecheck if restart-cookie and authentication session are cleared in this flow
// Don't remove authentication session for now, to ensure that browser buttons (back/refresh) will still work fine.
}
RealmModel realm = authSession.getRealm();

View file

@ -74,20 +74,17 @@ public class AuthenticationSessionManager {
boolean sslRequired = realm.getSslRequired().isRequired(session.getContext().getConnection());
CookieHelper.addCookie(AUTH_SESSION_ID, authSessionId, cookiePath, null, null, -1, sslRequired, true);
// TODO trace with isTraceEnabled
log.infof("Set AUTH_SESSION_ID cookie with value %s", authSessionId);
log.debugf("Set AUTH_SESSION_ID cookie with value %s", authSessionId);
}
public String getAuthSessionCookie() {
String cookieVal = CookieHelper.getCookieValue(AUTH_SESSION_ID);
if (log.isTraceEnabled()) {
if (cookieVal != null) {
log.tracef("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
} else {
log.tracef("Not found AUTH_SESSION_ID cookie");
}
if (cookieVal != null) {
log.debugf("Found AUTH_SESSION_ID cookie with value %s", cookieVal);
} else {
log.debugf("Not found AUTH_SESSION_ID cookie");
}
return cookieVal;
@ -95,7 +92,7 @@ public class AuthenticationSessionManager {
public void removeAuthenticationSession(RealmModel realm, AuthenticationSessionModel authSession, boolean expireRestartCookie) {
log.infof("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
log.debugf("Removing authSession '%s'. Expire restart cookie: %b", authSession.getId(), expireRestartCookie);
session.authenticationSessions().removeAuthenticationSession(realm, authSession);
// expire restart cookie

View file

@ -39,6 +39,7 @@ import org.keycloak.protocol.oidc.mappers.UserSessionNoteMapper;
import org.keycloak.representations.adapters.config.BaseRealmConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.sessions.AuthenticationSessionProvider;
import java.net.URI;
import java.util.Collections;
@ -104,6 +105,11 @@ public class ClientManager {
sessionsPersister.onClientRemoved(realm, client);
}
AuthenticationSessionProvider authSessions = realmManager.getSession().authenticationSessions();
if (authSessions != null) {
authSessions.onClientRemoved(realm, client);
}
UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client);
if (serviceAccountUser != null) {
new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser);

View file

@ -112,11 +112,6 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
CommonClientSessionModel clientSessionn = CodeGenerateUtil.getParser(sessionClass).parseSession(code, session, realm);;
CLIENT_SESSION clientSession = sessionClass.cast(clientSessionn);
// TODO:mposolda Move this to somewhere else? Maybe LoginActionsService.sessionCodeChecks should be somehow even for non-action URLs...
if (clientSession != null) {
session.getContext().setClient(clientSession.getClient());
}
return clientSession;
}
@ -168,8 +163,12 @@ public class ClientSessionCode<CLIENT_SESSION extends CommonClientSessionModel>
public Set<RoleModel> getRequestedRoles() {
return getRequestedRoles(commonLoginSession, realm);
}
public static Set<RoleModel> getRequestedRoles(CommonClientSessionModel clientSession, RealmModel realm) {
Set<RoleModel> requestedRoles = new HashSet<>();
for (String roleId : commonLoginSession.getRoles()) {
for (String roleId : clientSession.getRoles()) {
RoleModel role = realm.getRoleById(roleId);
if (role != null) {
requestedRoles.add(role);

View file

@ -23,7 +23,6 @@ import java.util.Map;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
@ -41,7 +40,6 @@ class CodeGenerateUtil {
private static final Map<Class<? extends CommonClientSessionModel>, ClientSessionParser> PARSERS = new HashMap<>();
static {
PARSERS.put(ClientSessionModel.class, new ClientSessionModelParser());
PARSERS.put(AuthenticationSessionModel.class, new AuthenticationSessionModelParser());
PARSERS.put(AuthenticatedClientSessionModel.class, new AuthenticatedClientSessionModelParser());
}
@ -78,54 +76,6 @@ class CodeGenerateUtil {
// IMPLEMENTATIONS
// TODO: remove
private static class ClientSessionModelParser implements ClientSessionParser<ClientSessionModel> {
@Override
public ClientSessionModel parseSession(String code, KeycloakSession session, RealmModel realm) {
try {
String[] parts = code.split("\\.");
String id = parts[2];
return session.sessions().getClientSession(realm, id);
} catch (ArrayIndexOutOfBoundsException e) {
return null;
}
}
@Override
public String generateCode(ClientSessionModel clientSession, String actionId) {
StringBuilder sb = new StringBuilder();
sb.append("cls.");
sb.append(actionId);
sb.append('.');
sb.append(clientSession.getId());
return sb.toString();
}
@Override
public void removeExpiredSession(KeycloakSession session, ClientSessionModel clientSession) {
session.sessions().removeClientSession(clientSession.getRealm(), clientSession);
}
@Override
public String getNote(ClientSessionModel clientSession, String name) {
return clientSession.getNote(name);
}
@Override
public void removeNote(ClientSessionModel clientSession, String name) {
clientSession.removeNote(name);
}
@Override
public void setNote(ClientSessionModel clientSession, String name, String value) {
clientSession.setNote(name, value);
}
}
private static class AuthenticationSessionModelParser implements ClientSessionParser<AuthenticationSessionModel> {
@Override
@ -193,20 +143,6 @@ class CodeGenerateUtil {
sb.append('.');
sb.append(clientUUID);
// TODO:mposolda codeChallengeMethod is not used anywhere. Not sure if it's bug of PKCE contribution. Doublecheck the PKCE specification what should be done regarding code
// https://tools.ietf.org/html/rfc7636#section-4
String codeChallenge = clientSession.getNote(OAuth2Constants.CODE_CHALLENGE);
String codeChallengeMethod = clientSession.getNote(OAuth2Constants.CODE_CHALLENGE_METHOD);
if (codeChallenge != null) {
logger.debugf("PKCE received codeChallenge = %s", codeChallenge);
if (codeChallengeMethod == null) {
logger.debug("PKCE not received codeChallengeMethod, treating plain");
codeChallengeMethod = OAuth2Constants.PKCE_METHOD_PLAIN;
} else {
logger.debugf("PKCE received codeChallengeMethod = %s", codeChallengeMethod);
}
}
return sb.toString();
}

View file

@ -47,6 +47,7 @@ import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.sessions.AuthenticationSessionProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.services.clientregistration.policy.DefaultClientRegistrationPolicies;
@ -248,6 +249,11 @@ public class RealmManager {
sessionsPersister.onRealmRemoved(realm);
}
AuthenticationSessionProvider authSessions = session.authenticationSessions();
if (authSessions != null) {
authSessions.onRealmRemoved(realm);
}
// Refresh periodic sync tasks for configured storageProviders
List<UserStorageProviderModel> storageProviders = realm.getUserStorageProviders();
UserStorageSyncManager storageSync = new UserStorageSyncManager();

View file

@ -104,8 +104,8 @@ public class UserSessionManager {
user.getUsername(), client.getClientId());
}
userSession.getAuthenticatedClientSessions().remove(client.getClientId());
persister.removeClientSession(clientSession.getId(), true);
clientSession.setUserSession(null);
persister.removeClientSession(userSession.getId(), client.getId(), true);
checkOfflineUserSessionHasClientSessions(realm, user, userSession);
anyRemoved = true;
}
@ -148,9 +148,8 @@ public class UserSessionManager {
clientSession.getId(), offlineUserSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
}
AuthenticatedClientSessionModel offlineClientSession = kcSession.sessions().createOfflineClientSession(clientSession);
offlineUserSession.getAuthenticatedClientSessions().put(clientSession.getClient().getId(), offlineClientSession);
persister.createClientSession(offlineUserSession, clientSession, true);
kcSession.sessions().createOfflineClientSession(clientSession, offlineUserSession);
persister.createClientSession(clientSession, true);
}
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not

View file

@ -75,6 +75,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.BruteForceProtector;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.validation.Validation;
import org.keycloak.sessions.AuthenticationSessionModel;
@ -210,6 +211,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
}
event.detail(Details.REDIRECT_URI, redirectUri);
if (nonce == null || hash == null) {
event.error(Errors.INVALID_REDIRECT_URI);
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
@ -239,7 +242,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return Response.status(302).location(builder.build()).build();
}
cookieResult.getSession();
event.session(cookieResult.getSession());
event.user(cookieResult.getUser());
event.detail(Details.USERNAME, cookieResult.getUser().getUsername());
AuthenticatedClientSessionModel clientSession = null;
for (AuthenticatedClientSessionModel cs : cookieResult.getSession().getAuthenticatedClientSessions().values()) {
@ -264,7 +270,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
throw new ErrorPageException(session, Messages.INVALID_REQUEST);
}
event.detail(Details.IDENTITY_PROVIDER, providerId);
ClientModel accountService = this.realmModel.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
if (!accountService.getId().equals(client.getId())) {
@ -307,6 +313,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
authSession.setClientNote(OIDCLoginProtocol.STATE_PARAM, UUID.randomUUID().toString());
authSession.setAuthNote(LINKING_IDENTITY_PROVIDER, cookieResult.getSession().getId() + clientId + providerId);
event.detail(Details.CODE_ID, userSession.getId());
event.success();
try {
@ -508,6 +515,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.event.event(EventType.IDENTITY_PROVIDER_LOGIN)
.detail(Details.REDIRECT_URI, authenticationSession.getRedirectUri())
.detail(Details.IDENTITY_PROVIDER, providerId)
.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
UserModel federatedUser = this.session.users().getUserByFederatedIdentity(federatedIdentityModel, this.realmModel);
@ -786,6 +794,9 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
authSession.setUserSessionNote(Details.IDENTITY_PROVIDER, providerId);
authSession.setUserSessionNote(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
event.detail(Details.IDENTITY_PROVIDER, providerId)
.detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername());
if (isDebugEnabled()) {
logger.debugf("Performing local authentication for user [%s].", federatedUser);
}
@ -961,43 +972,39 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
private ParsedCodeContext parseClientSessionCode(String code) {
ClientSessionCode.ParseResult<AuthenticationSessionModel> parseResult = ClientSessionCode.parseResult(code, this.session, this.realmModel, AuthenticationSessionModel.class);
ClientSessionCode<AuthenticationSessionModel> clientCode = parseResult.getCode();
if (clientCode != null) {
AuthenticationSessionModel authenticationSession = clientCode.getClientSession();
ClientModel client = authenticationSession.getClient();
if (client != null) {
logger.debugf("Got authorization code from client [%s].", client.getClientId());
this.event.client(client);
this.session.getContext().setClient(client);
if (!clientCode.isValid(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", authenticationSession.getId(), authenticationSession.getAction());
// Check if error happened during login or during linking from account management
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.STALE_CODE_ACCOUNT);
Response staleCodeError = (accountManagementFailedLinking != null) ? accountManagementFailedLinking : redirectToErrorPage(Messages.STALE_CODE);
return ParsedCodeContext.response(staleCodeError);
}
if (isDebugEnabled()) {
logger.debugf("Authorization code is valid.");
}
return ParsedCodeContext.clientSessionCode(clientCode);
}
if (code == null) {
logger.debugf("Invalid request. Authorization code was null");
Response staleCodeError = redirectToErrorPage(Messages.INVALID_REQUEST);
return ParsedCodeContext.response(staleCodeError);
}
// TODO:mposolda rather some different page? Maybe "PageExpired" page?
logger.debugf("Authorization code is not valid. Code: %s", code);
Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
return ParsedCodeContext.response(staleCodeError);
SessionCodeChecks checks = new SessionCodeChecks(realmModel, uriInfo, clientConnection, session, event, code, null, LoginActionsService.AUTHENTICATE_PATH);
checks.initialVerify();
if (!checks.verifyActiveAndValidAction(AuthenticationSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
if (authSession != null) {
// Check if error happened during login or during linking from account management
Response accountManagementFailedLinking = checkAccountManagementFailedLinking(authSession, Messages.STALE_CODE_ACCOUNT);
if (accountManagementFailedLinking != null) {
return ParsedCodeContext.response(accountManagementFailedLinking);
} else {
Response errorResponse = checks.getResponse();
// Remove "code" from browser history
errorResponse = BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, errorResponse, true);
return ParsedCodeContext.response(errorResponse);
}
} else {
return ParsedCodeContext.response(checks.getResponse());
}
} else {
if (isDebugEnabled()) {
logger.debugf("Authorization code is valid.");
}
return ParsedCodeContext.clientSessionCode(checks.getClientCode());
}
}
/**
@ -1022,6 +1029,7 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
SamlService samlService = new SamlService(realmModel, event);
ResteasyProviderFactory.getInstance().injectProperties(samlService);
AuthenticationSessionModel authSession = samlService.getOrCreateLoginSessionForIdpInitiatedSso(session, realmModel, oClient.get(), null);
return ParsedCodeContext.clientSessionCode(new ClientSessionCode<>(session, this.realmModel, authSession));
@ -1091,16 +1099,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return Response.status(302).location(UriBuilder.fromUri(authSession.getRedirectUri()).build()).build();
}
private Response redirectToLoginPage(Throwable t, ClientSessionCode<AuthenticationSessionModel> clientCode) {
String message = t.getMessage();
if (message == null) {
message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
}
fireErrorEvent(message);
return browserAuthentication(clientCode.getClientSession(), message);
}
protected Response browserAuthentication(AuthenticationSessionModel authSession, String errorMessage) {
this.event.event(EventType.LOGIN);

View file

@ -28,22 +28,20 @@ import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.TokenVerifier;
import org.keycloak.authentication.actiontoken.*;
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
import org.keycloak.authentication.authenticators.broker.util.PostBrokerLoginConstants;
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.authentication.requiredactions.VerifyEmail;
import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.exceptions.TokenNotActiveException;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
@ -55,13 +53,10 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserModel.RequiredAction;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocol.Error;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.OIDCResponseMode;
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
@ -74,10 +69,9 @@ import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.LoginActionsServiceChecks.RestartFlowException;
import org.keycloak.services.util.CacheControlUtil;
import org.keycloak.services.util.PageExpiredRedirect;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
@ -94,6 +88,8 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
import java.net.URI;
import java.util.Map;
import javax.ws.rs.core.*;
import static org.keycloak.authentication.actiontoken.DefaultActionToken.ACTION_TOKEN_BASIC_CHECKS;
@ -185,333 +181,14 @@ public class LoginActionsService {
}
private SessionCodeChecks checksForCode(String code, String execution, String flowPath) {
SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
SessionCodeChecks res = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, code, execution, flowPath);
res.initialVerify();
return res;
}
private SessionCodeChecks checksForCodeRefreshNotAllowed(String code, String execution, String flowPath) {
SessionCodeChecks res = new SessionCodeChecks(code, execution, flowPath);
res.setAllowRefresh(false);
res.initialVerify();
return res;
}
private class SessionCodeChecks {
ClientSessionCode<AuthenticationSessionModel> clientCode;
Response response;
ClientSessionCode.ParseResult<AuthenticationSessionModel> result;
private boolean actionRequest;
private boolean allowRefresh = true;
private final String code;
private final String execution;
private final String flowPath;
public SessionCodeChecks(String code, String execution, String flowPath) {
this.code = code;
this.execution = execution;
this.flowPath = flowPath;
}
public AuthenticationSessionModel getAuthenticationSession() {
return clientCode == null ? null : clientCode.getClientSession();
}
public boolean passed() {
return response == null;
}
public boolean failed() {
return response != null;
}
public boolean isAllowRefresh() {
return allowRefresh;
}
public void setAllowRefresh(boolean allowRefresh) {
this.allowRefresh = allowRefresh;
}
boolean verifyCode(String expectedAction, ClientSessionCode.ActionType actionType) {
if (failed()) {
return false;
}
if (!isActionActive(actionType)) {
return false;
}
if (!clientCode.isValidAction(expectedAction)) {
AuthenticationSessionModel authSession = getAuthenticationSession();
if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
// TODO:mposolda debug or trace
logger.info("Incorrect flow '%s' . User authenticated already. Redirecting to requiredActions now.");
response = redirectToRequiredActions(null);
return false;
} else {
// TODO:mposolda could this happen? Doublecheck if we use other AuthenticationSession.Action besides AUTHENTICATE and REQUIRED_ACTIONS
logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
response = ErrorPage.error(session, Messages.EXPIRED_CODE);
return false;
}
}
return true;
}
private boolean isActionActive(ClientSessionCode.ActionType actionType) {
if (!clientCode.isActionActive(actionType)) {
event.client(getAuthenticationSession().getClient());
event.clone().error(Errors.EXPIRED_CODE);
AuthenticationSessionModel authSession = getAuthenticationSession();
AuthenticationProcessor.resetFlow(authSession, AUTHENTICATE_PATH);
response = processAuthentication(false, null, authSession, Messages.LOGIN_TIMEOUT);
return false;
}
return true;
}
private AuthenticationSessionModel initialVerifyAuthSession() {
// Basic realm checks
if (!checkSsl()) {
event.error(Errors.SSL_REQUIRED);
response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
return null;
}
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
return null;
}
// object retrieve
AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, AuthenticationSessionModel.class);
if (authSession != null) {
return authSession;
}
// See if we are already authenticated and userSession with same ID exists.
String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
if (sessionId != null) {
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession != null) {
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.ALREADY_LOGGED_IN);
ClientModel client = null;
String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
if (lastClientUuid != null) {
client = realm.getClientById(lastClientUuid);
}
if (client != null) {
session.getContext().setClient(client);
} else {
loginForm.setAttribute("skipLink", true);
}
response = loginForm.createInfoPage();
return null;
}
}
// Otherwise just try to restart from the cookie
response = restartAuthenticationSessionFromCookie();
return null;
}
private boolean initialVerify() {
// Basic realm checks and authenticationSession retrieve
AuthenticationSessionModel authSession = initialVerifyAuthSession();
if (authSession == null) {
return false;
}
// Check cached response from previous action request
response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
if (response != null) {
return false;
}
// Client checks
event.detail(Details.CODE_ID, authSession.getId());
ClientModel client = authSession.getClient();
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
clientCode.removeExpiredClientSession();
return false;
}
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
clientCode.removeExpiredClientSession();
return false;
}
session.getContext().setClient(client);
// Check if it's action or not
if (code == null) {
String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
// Check if we transitted between flows (eg. clicking "register" on login screen)
if (execution==null && !flowPath.equals(lastFlow)) {
logger.infof("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
if (lastFlow == null || isFlowTransitionAllowed(flowPath, lastFlow)) {
authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
lastExecFromSession = null;
}
}
if (ObjectUtil.isEqualOrBothNull(execution, lastExecFromSession)) {
// Allow refresh of previous page
clientCode = new ClientSessionCode<>(session, realm, authSession);
actionRequest = false;
return true;
} else {
response = showPageExpired(authSession);
return false;
}
} else {
result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
clientCode = result.getCode();
if (clientCode == null) {
// In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
if (allowRefresh && ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
logger.infof("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
authSession.setAuthNote(FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
response = Response.status(Response.Status.FOUND).location(redirectUri).build();
} else {
response = showPageExpired(authSession);
}
return false;
}
actionRequest = execution != null;
authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
return true;
}
}
public boolean verifyRequiredAction(String executedAction) {
if (failed()) {
return false;
}
if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
// TODO:mposolda debug or trace
logger.infof("Expected required action, but session action is '%s' . Showing expired page now.", getAuthenticationSession().getAction());
event.client(getAuthenticationSession().getClient());
event.error(Errors.INVALID_CODE);
response = showPageExpired(getAuthenticationSession());
return false;
}
if (!isActionActive(ClientSessionCode.ActionType.USER)) return false;
final AuthenticationSessionModel authSession = getAuthenticationSession();
if (actionRequest) {
String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
logger.debug("required action doesn't match current required action");
response = redirectToRequiredActions(currentRequiredAction);
return false;
}
}
return true;
}
}
public static boolean isFlowTransitionAllowed(String currentFlow, String previousFlow) {
if (currentFlow.equals(AUTHENTICATE_PATH) && (previousFlow.equals(REGISTRATION_PATH) || previousFlow.equals(RESET_CREDENTIALS_PATH))) {
return true;
}
if (currentFlow.equals(REGISTRATION_PATH) && (previousFlow.equals(AUTHENTICATE_PATH))) {
return true;
}
if (currentFlow.equals(RESET_CREDENTIALS_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(FIRST_BROKER_LOGIN_PATH))) {
return true;
}
if (currentFlow.equals(FIRST_BROKER_LOGIN_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(POST_BROKER_LOGIN_PATH))) {
return true;
}
if (currentFlow.equals(POST_BROKER_LOGIN_PATH) && (previousFlow.equals(AUTHENTICATE_PATH) || previousFlow.equals(FIRST_BROKER_LOGIN_PATH))) {
return true;
}
return false;
}
protected Response restartAuthenticationSessionFromCookie() {
logger.info("Authentication session not found. Trying to restart from cookie.");
AuthenticationSessionModel authSession = null;
try {
authSession = RestartLoginCookie.restartSession(session, realm);
} catch (Exception e) {
ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
}
if (authSession != null) {
event.clone();
event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
event.error(Errors.EXPIRED_CODE);
String warningMessage = Messages.LOGIN_TIMEOUT;
authSession.setAuthNote(FORWARDED_ERROR_MESSAGE_NOTE, warningMessage);
String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
if (flowPath == null) {
flowPath = AUTHENTICATE_PATH;
}
URI redirectUri = getLastExecutionUrl(flowPath, null);
logger.infof("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
return Response.status(Response.Status.FOUND).location(redirectUri).build();
} else {
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
}
}
protected Response showPageExpired(AuthenticationSessionModel authSession) {
return new PageExpiredRedirect(session, realm, uriInfo)
.showPageExpired(authSession);
}
protected URI getLastExecutionUrl(String flowPath, String executionId) {
return new PageExpiredRedirect(session, realm, uriInfo)
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
.getLastExecutionUrl(flowPath, executionId);
}
@ -525,11 +202,11 @@ public class LoginActionsService {
@GET
public Response restartSession() {
event.event(EventType.RESTART_AUTHENTICATION);
SessionCodeChecks checks = new SessionCodeChecks(null, null, null);
SessionCodeChecks checks = new SessionCodeChecks(realm, uriInfo, clientConnection, session, event, null, null, null);
AuthenticationSessionModel authSession = checks.initialVerifyAuthSession();
if (authSession == null) {
return checks.response;
return checks.getResponse();
}
String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
@ -539,11 +216,8 @@ public class LoginActionsService {
AuthenticationProcessor.resetFlow(authSession, flowPath);
// TODO:mposolda Check it's better to put it to AuthenticationProcessor.resetFlow (with consider of brokering)
authSession.setAction(CommonClientSessionModel.Action.AUTHENTICATE.name());
URI redirectUri = getLastExecutionUrl(flowPath, null);
logger.infof("Flow restart requested. Redirecting to %s", redirectUri);
logger.debugf("Flow restart requested. Redirecting to %s", redirectUri);
return Response.status(Response.Status.FOUND).location(redirectUri).build();
}
@ -561,12 +235,12 @@ public class LoginActionsService {
event.event(EventType.LOGIN);
SessionCodeChecks checks = checksForCode(code, execution, AUTHENTICATE_PATH);
if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.getResponse();
}
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
boolean actionRequest = checks.actionRequest;
boolean actionRequest = checks.isActionRequest();
return processAuthentication(actionRequest, execution, authSession, null);
}
@ -605,6 +279,9 @@ public class LoginActionsService {
} else {
response = processor.authenticate();
}
} catch (WebApplicationException e) {
response = e.getResponse();
authSession = processor.getAuthenticationSession();
} catch (Exception e) {
response = processor.handleBrowserException(e);
authSession = processor.getAuthenticationSession(); // Could be changed (eg. Forked flow)
@ -696,30 +373,26 @@ public class LoginActionsService {
*/
protected Response resetCredentials(String code, String execution) {
SessionCodeChecks checks = checksForCode(code, execution, RESET_CREDENTIALS_PATH);
if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.USER)) {
return checks.getResponse();
}
final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
if (!realm.isResetPasswordAllowed()) {
if (authSession != null) {
event.client(authSession.getClient());
}
event.error(Errors.NOT_ALLOWED);
return ErrorPage.error(session, Messages.RESET_CREDENTIAL_NOT_ALLOWED);
}
return processResetCredentials(checks.actionRequest, execution, authSession);
return processResetCredentials(checks.isActionRequest(), execution, authSession);
}
/**
* Handles a given token using the given token handler. If there is any {@link VerificationException} thrown
* in the handler, it is handled automatically here to reduce boilerplate code.
*
* @param tokenString Original token string
* @param eventError
* @param defaultErrorMessage
* @param key
* @param execution
* @return
*/
@Path("action-token")
@ -888,30 +561,7 @@ public class LoginActionsService {
}
protected Response processResetCredentials(boolean actionRequest, String execution, AuthenticationSessionModel authSession) {
AuthenticationProcessor authProcessor = new AuthenticationProcessor() {
@Override
protected Response authenticationComplete() {
boolean firstBrokerLoginInProgress = (authenticationSession.getAuthNote(AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE) != null);
if (firstBrokerLoginInProgress) {
UserModel linkingUser = AbstractIdpAuthenticator.getExistingUser(session, realm, authenticationSession);
if (!linkingUser.getId().equals(authenticationSession.getAuthenticatedUser().getId())) {
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_DIFFERENT_USER_MESSAGE, authenticationSession.getAuthenticatedUser().getUsername(), linkingUser.getUsername());
}
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromAuthenticationSession(authSession, AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE);
authSession.setAuthNote(AbstractIdpAuthenticator.FIRST_BROKER_LOGIN_SUCCESS, serializedCtx.getIdentityProviderId());
logger.debugf("Forget-password flow finished when authenticated user '%s' after first broker login with identity provider '%s'.",
linkingUser.getUsername(), serializedCtx.getIdentityProviderId());
return redirectToAfterBrokerLoginEndpoint(authSession, true);
} else {
return super.authenticationComplete();
}
}
};
AuthenticationProcessor authProcessor = new ResetCredentialsActionTokenHandler.ResetCredsAuthenticationProcessor();
return processFlow(actionRequest, execution, authSession, RESET_CREDENTIALS_PATH, realm.getResetCredentialsFlow(), null, authProcessor);
}
@ -958,19 +608,15 @@ public class LoginActionsService {
}
SessionCodeChecks checks = checksForCode(code, execution, REGISTRATION_PATH);
if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.getResponse();
}
ClientSessionCode<AuthenticationSessionModel> clientSessionCode = checks.clientCode;
AuthenticationSessionModel clientSession = clientSessionCode.getClientSession();
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
// TODO:mposolda any consequences to do this for POST request too?
if (!isPostRequest) {
AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
}
AuthenticationManager.expireIdentityCookie(realm, uriInfo, clientConnection);
return processRegistration(checks.actionRequest, execution, clientSession, null);
return processRegistration(checks.isActionRequest(), execution, authSession, null);
}
@ -1010,8 +656,8 @@ public class LoginActionsService {
event.event(eventType);
SessionCodeChecks checks = checksForCode(code, execution, flowPath);
if (!checks.verifyCode(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.response;
if (!checks.verifyActiveAndValidAction(ClientSessionModel.Action.AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
return checks.getResponse();
}
event.detail(Details.CODE_ID, code);
final AuthenticationSessionModel authSession = checks.getAuthenticationSession();
@ -1056,10 +702,14 @@ public class LoginActionsService {
};
return processFlow(checks.actionRequest, execution, authSession, flowPath, brokerLoginFlow, null, processor);
return processFlow(checks.isActionRequest(), execution, authSession, flowPath, brokerLoginFlow, null, processor);
}
private Response redirectToAfterBrokerLoginEndpoint(AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
return redirectToAfterBrokerLoginEndpoint(session, realm, uriInfo, authSession, firstBrokerLogin);
}
public static Response redirectToAfterBrokerLoginEndpoint(KeycloakSession session, RealmModel realm, UriInfo uriInfo, AuthenticationSessionModel authSession, boolean firstBrokerLogin) {
ClientSessionCode<AuthenticationSessionModel> accessCode = new ClientSessionCode<>(session, realm, authSession);
authSession.setTimestamp(Time.currentTime());
@ -1085,11 +735,10 @@ public class LoginActionsService {
String code = formData.getFirst("code");
SessionCodeChecks checks = checksForCode(code, null, REQUIRED_ACTION);
if (!checks.verifyRequiredAction(ClientSessionModel.Action.OAUTH_GRANT.name())) {
return checks.response;
return checks.getResponse();
}
ClientSessionCode<AuthenticationSessionModel> accessCode = checks.clientCode;
AuthenticationSessionModel authSession = accessCode.getClientSession();
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
initLoginEvent(authSession);
@ -1113,10 +762,10 @@ public class LoginActionsService {
grantedConsent = new UserConsentModel(client);
session.users().addConsent(realm, user.getId(), grantedConsent);
}
for (RoleModel role : accessCode.getRequestedRoles()) {
for (RoleModel role : ClientSessionCode.getRequestedRoles(authSession, realm)) {
grantedConsent.addGrantedRole(role);
}
for (ProtocolMapperModel protocolMapper : accessCode.getRequestedProtocolMappers()) {
for (ProtocolMapperModel protocolMapper : ClientSessionCode.getRequestedProtocolMappers(authSession.getProtocolMappers(), client)) {
if (protocolMapper.isConsentRequired() && protocolMapper.getConsentText() != null) {
grantedConsent.addGrantedProtocolMapper(protocolMapper);
}
@ -1130,23 +779,6 @@ public class LoginActionsService {
return AuthenticationManager.redirectAfterSuccessfulFlow(session, realm, clientSession.getUserSession(), clientSession, request, uriInfo, clientConnection, event, authSession.getProtocol());
}
@Path("email-verification")
@GET
public Response emailVerification(@QueryParam("code") String code, @QueryParam("execution") String execution) {
event.event(EventType.SEND_VERIFY_EMAIL);
SessionCodeChecks checks = checksForCodeRefreshNotAllowed(code, execution, REQUIRED_ACTION);
if (!checks.verifyCode(ClientSessionModel.Action.REQUIRED_ACTIONS.name(), ClientSessionCode.ActionType.USER)) {
return checks.response;
}
ClientSessionCode accessCode = checks.clientCode;
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
initLoginEvent(authSession);
event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, authSession.getAuthenticatedUser().getEmail()).success();
return VerifyEmail.sendVerifyEmail(session, accessCode.getCode(), authSession.getAuthenticatedUser(), authSession);
}
/**
* Initiated by admin, not the user on login
@ -1210,11 +842,12 @@ public class LoginActionsService {
}
event.detail(Details.REMEMBER_ME, rememberMe);
// TODO:mposolda Fix if this is called at firstBroker or postBroker login
/*
.detail(Details.IDENTITY_PROVIDER, userSession.getNote(Details.IDENTITY_PROVIDER))
.detail(Details.IDENTITY_PROVIDER_USERNAME, userSession.getNote(Details.IDENTITY_PROVIDER_USERNAME));
*/
Map<String, String> userSessionNotes = authSession.getUserSessionNotes();
String identityProvider = userSessionNotes.get(Details.IDENTITY_PROVIDER);
if (identityProvider != null) {
event.detail(Details.IDENTITY_PROVIDER, identityProvider)
.detail(Details.IDENTITY_PROVIDER_USERNAME, userSessionNotes.get(Details.IDENTITY_PROVIDER_USERNAME));
}
}
@Path(REQUIRED_ACTION)
@ -1236,11 +869,11 @@ public class LoginActionsService {
SessionCodeChecks checks = checksForCode(code, action, REQUIRED_ACTION);
if (!checks.verifyRequiredAction(action)) {
return checks.response;
return checks.getResponse();
}
AuthenticationSessionModel authSession = checks.getAuthenticationSession();
if (!checks.actionRequest) {
if (!checks.isActionRequest()) {
initLoginEvent(authSession);
event.event(EventType.CUSTOM_REQUIRED_ACTION);
return AuthenticationManager.nextActionAfterAuthentication(session, authSession, clientConnection, request, uriInfo, event);
@ -1268,7 +901,9 @@ public class LoginActionsService {
Response response;
provider.processAction(context);
authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
if (action != null) {
authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, action);
}
if (context.getStatus() == RequiredActionContext.Status.SUCCESS) {
event.clone().success();
@ -1298,15 +933,4 @@ public class LoginActionsService {
return BrowserHistoryHelper.getInstance().saveResponseAndRedirect(session, authSession, response, true);
}
private Response redirectToRequiredActions(String action) {
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
.path(LoginActionsService.REQUIRED_ACTION);
if (action != null) {
uriBuilder.queryParam("execution", action);
}
URI redirect = uriBuilder.build(realm.getName());
return Response.status(302).location(redirect).build();
}
}

View file

@ -0,0 +1,388 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services.resources;
import java.net.URI;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.jboss.logging.Logger;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.AuthorizationEndpointBase;
import org.keycloak.protocol.RestartLoginCookie;
import org.keycloak.services.ErrorPage;
import org.keycloak.services.ServicesLogger;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.sessions.AuthenticationSessionModel;
public class SessionCodeChecks {
private static final Logger logger = Logger.getLogger(SessionCodeChecks.class);
private AuthenticationSessionModel authSession;
private ClientSessionCode<AuthenticationSessionModel> clientCode;
private Response response;
private boolean actionRequest;
private final RealmModel realm;
private final UriInfo uriInfo;
private final ClientConnection clientConnection;
private final KeycloakSession session;
private final EventBuilder event;
private final String code;
private final String execution;
private final String flowPath;
public SessionCodeChecks(RealmModel realm, UriInfo uriInfo, ClientConnection clientConnection, KeycloakSession session, EventBuilder event, String code, String execution, String flowPath) {
this.realm = realm;
this.uriInfo = uriInfo;
this.clientConnection = clientConnection;
this.session = session;
this.event = event;
this.code = code;
this.execution = execution;
this.flowPath = flowPath;
}
public AuthenticationSessionModel getAuthenticationSession() {
return authSession;
}
private boolean failed() {
return response != null;
}
public Response getResponse() {
return response;
}
public ClientSessionCode<AuthenticationSessionModel> getClientCode() {
return clientCode;
}
public boolean isActionRequest() {
return actionRequest;
}
private boolean checkSsl() {
if (uriInfo.getBaseUri().getScheme().equals("https")) {
return true;
} else {
return !realm.getSslRequired().isRequired(clientConnection);
}
}
public AuthenticationSessionModel initialVerifyAuthSession() {
// Basic realm checks
if (!checkSsl()) {
event.error(Errors.SSL_REQUIRED);
response = ErrorPage.error(session, Messages.HTTPS_REQUIRED);
return null;
}
if (!realm.isEnabled()) {
event.error(Errors.REALM_DISABLED);
response = ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
return null;
}
// object retrieve
AuthenticationSessionModel authSession = ClientSessionCode.getClientSession(code, session, realm, AuthenticationSessionModel.class);
if (authSession != null) {
return authSession;
}
// See if we are already authenticated and userSession with same ID exists.
String sessionId = new AuthenticationSessionManager(session).getCurrentAuthenticationSessionId(realm);
if (sessionId != null) {
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
if (userSession != null) {
LoginFormsProvider loginForm = session.getProvider(LoginFormsProvider.class)
.setSuccess(Messages.ALREADY_LOGGED_IN);
ClientModel client = null;
String lastClientUuid = userSession.getNote(AuthenticationManager.LAST_AUTHENTICATED_CLIENT);
if (lastClientUuid != null) {
client = realm.getClientById(lastClientUuid);
}
if (client != null) {
session.getContext().setClient(client);
} else {
loginForm.setAttribute("skipLink", true);
}
response = loginForm.createInfoPage();
return null;
}
}
// Otherwise just try to restart from the cookie
response = restartAuthenticationSessionFromCookie();
return null;
}
public boolean initialVerify() {
// Basic realm checks and authenticationSession retrieve
authSession = initialVerifyAuthSession();
if (authSession == null) {
return false;
}
// Check cached response from previous action request
response = BrowserHistoryHelper.getInstance().loadSavedResponse(session, authSession);
if (response != null) {
return false;
}
// Client checks
event.detail(Details.CODE_ID, authSession.getId());
ClientModel client = authSession.getClient();
if (client == null) {
event.error(Errors.CLIENT_NOT_FOUND);
response = ErrorPage.error(session, Messages.UNKNOWN_LOGIN_REQUESTER);
clientCode.removeExpiredClientSession();
return false;
}
event.client(client);
session.getContext().setClient(client);
if (!client.isEnabled()) {
event.error(Errors.CLIENT_DISABLED);
response = ErrorPage.error(session, Messages.LOGIN_REQUESTER_NOT_ENABLED);
clientCode.removeExpiredClientSession();
return false;
}
// Check if it's action or not
if (code == null) {
String lastExecFromSession = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
String lastFlow = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
// Check if we transitted between flows (eg. clicking "register" on login screen)
if (execution==null && !flowPath.equals(lastFlow)) {
logger.debugf("Transition between flows! Current flow: %s, Previous flow: %s", flowPath, lastFlow);
// Don't allow moving to different flow if I am on requiredActions already
if (ClientSessionModel.Action.AUTHENTICATE.name().equals(authSession.getAction())) {
authSession.setAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH, flowPath);
authSession.removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
lastExecFromSession = null;
}
}
if (ObjectUtil.isEqualOrBothNull(execution, lastExecFromSession)) {
// Allow refresh of previous page
clientCode = new ClientSessionCode<>(session, realm, authSession);
actionRequest = false;
return true;
} else {
response = showPageExpired(authSession);
return false;
}
} else {
ClientSessionCode.ParseResult<AuthenticationSessionModel> result = ClientSessionCode.parseResult(code, session, realm, AuthenticationSessionModel.class);
clientCode = result.getCode();
if (clientCode == null) {
// In case that is replayed action, but sent to the same FORM like actual FORM, we just re-render the page
if (ObjectUtil.isEqualOrBothNull(execution, authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION))) {
String latestFlowPath = authSession.getAuthNote(AuthenticationProcessor.CURRENT_FLOW_PATH);
URI redirectUri = getLastExecutionUrl(latestFlowPath, execution);
logger.debugf("Invalid action code, but execution matches. So just redirecting to %s", redirectUri);
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.EXPIRED_ACTION);
response = Response.status(Response.Status.FOUND).location(redirectUri).build();
} else {
response = showPageExpired(authSession);
}
return false;
}
actionRequest = true;
if (execution != null) {
authSession.setAuthNote(AuthenticationProcessor.LAST_PROCESSED_EXECUTION, execution);
}
return true;
}
}
public boolean verifyActiveAndValidAction(String expectedAction, ClientSessionCode.ActionType actionType) {
if (failed()) {
return false;
}
if (!isActionActive(actionType)) {
return false;
}
if (!clientCode.isValidAction(expectedAction)) {
AuthenticationSessionModel authSession = getAuthenticationSession();
if (ClientSessionModel.Action.REQUIRED_ACTIONS.name().equals(authSession.getAction())) {
logger.debugf("Incorrect action '%s' . User authenticated already.", authSession.getAction());
response = showPageExpired(authSession);
return false;
} else {
logger.errorf("Bad action. Expected action '%s', current action '%s'", expectedAction, authSession.getAction());
response = ErrorPage.error(session, Messages.EXPIRED_CODE);
return false;
}
}
return true;
}
private boolean isActionActive(ClientSessionCode.ActionType actionType) {
if (!clientCode.isActionActive(actionType)) {
event.clone().error(Errors.EXPIRED_CODE);
AuthenticationProcessor.resetFlow(authSession, LoginActionsService.AUTHENTICATE_PATH);
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, Messages.LOGIN_TIMEOUT);
URI redirectUri = getLastExecutionUrl(LoginActionsService.AUTHENTICATE_PATH, null);
logger.debugf("Flow restart after timeout. Redirecting to %s", redirectUri);
response = Response.status(Response.Status.FOUND).location(redirectUri).build();
return false;
}
return true;
}
public boolean verifyRequiredAction(String executedAction) {
if (failed()) {
return false;
}
if (!clientCode.isValidAction(ClientSessionModel.Action.REQUIRED_ACTIONS.name())) {
logger.debugf("Expected required action, but session action is '%s' . Showing expired page now.", authSession.getAction());
event.error(Errors.INVALID_CODE);
response = showPageExpired(authSession);
return false;
}
if (!isActionActive(ClientSessionCode.ActionType.USER)) {
return false;
}
if (actionRequest) {
String currentRequiredAction = authSession.getAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
if (executedAction == null || !executedAction.equals(currentRequiredAction)) {
logger.debug("required action doesn't match current required action");
response = redirectToRequiredActions(currentRequiredAction);
return false;
}
}
return true;
}
private Response restartAuthenticationSessionFromCookie() {
logger.debug("Authentication session not found. Trying to restart from cookie.");
AuthenticationSessionModel authSession = null;
try {
authSession = RestartLoginCookie.restartSession(session, realm);
} catch (Exception e) {
ServicesLogger.LOGGER.failedToParseRestartLoginCookie(e);
}
if (authSession != null) {
event.clone();
event.detail(Details.RESTART_AFTER_TIMEOUT, "true");
event.error(Errors.EXPIRED_CODE);
String warningMessage = Messages.LOGIN_TIMEOUT;
authSession.setAuthNote(LoginActionsService.FORWARDED_ERROR_MESSAGE_NOTE, warningMessage);
String flowPath = authSession.getClientNote(AuthorizationEndpointBase.APP_INITIATED_FLOW);
if (flowPath == null) {
flowPath = LoginActionsService.AUTHENTICATE_PATH;
}
URI redirectUri = getLastExecutionUrl(flowPath, null);
logger.debugf("Authentication session restart from cookie succeeded. Redirecting to %s", redirectUri);
return Response.status(Response.Status.FOUND).location(redirectUri).build();
} else {
// Finally need to show error as all the fallbacks failed
event.error(Errors.INVALID_CODE);
return ErrorPage.error(session, Messages.INVALID_CODE);
}
}
private Response redirectToRequiredActions(String action) {
UriBuilder uriBuilder = LoginActionsService.loginActionsBaseUrl(uriInfo)
.path(LoginActionsService.REQUIRED_ACTION);
if (action != null) {
uriBuilder.queryParam("execution", action);
}
URI redirect = uriBuilder.build(realm.getName());
return Response.status(302).location(redirect).build();
}
private URI getLastExecutionUrl(String flowPath, String executionId) {
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
.getLastExecutionUrl(flowPath, executionId);
}
private Response showPageExpired(AuthenticationSessionModel authSession) {
return new AuthenticationFlowURLHelper(session, realm, uriInfo)
.showPageExpired(authSession);
}
}

View file

@ -25,8 +25,8 @@ import org.keycloak.common.Profile;
import org.keycloak.common.util.Time;
import org.keycloak.events.admin.OperationType;
import org.keycloak.events.admin.ResourceType;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
@ -492,8 +492,11 @@ public class ClientResource {
UserSessionRepresentation rep = ModelToRepresentation.toRepresentation(userSession);
// Update lastSessionRefresh with the timestamp from clientSession
for (ClientSessionModel clientSession : userSession.getClientSessions()) {
if (client.getId().equals(clientSession.getClient().getId())) {
for (Map.Entry<String, AuthenticatedClientSessionModel> csEntry : userSession.getAuthenticatedClientSessions().entrySet()) {
String clientUuid = csEntry.getKey();
AuthenticatedClientSessionModel clientSession = csEntry.getValue();
if (client.getId().equals(clientUuid)) {
rep.setLastAccess(Time.toMillis(clientSession.getTimestamp()));
break;
}

View file

@ -32,6 +32,7 @@ public class ClearExpiredUserSessions implements ScheduledTask {
UserSessionProvider sessions = session.sessions();
for (RealmModel realm : session.realms().getRealms()) {
sessions.removeExpired(realm);
session.authenticationSessions().removeExpired(realm);
}
}

View file

@ -35,15 +35,15 @@ import org.keycloak.sessions.AuthenticationSessionModel;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PageExpiredRedirect {
public class AuthenticationFlowURLHelper {
protected static final Logger logger = Logger.getLogger(PageExpiredRedirect.class);
protected static final Logger logger = Logger.getLogger(AuthenticationFlowURLHelper.class);
private final KeycloakSession session;
private final RealmModel realm;
private final UriInfo uriInfo;
public PageExpiredRedirect(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
public AuthenticationFlowURLHelper(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
this.session = session;
this.realm = realm;
this.uriInfo = uriInfo;
@ -53,7 +53,7 @@ public class PageExpiredRedirect {
public Response showPageExpired(AuthenticationSessionModel authSession) {
URI lastStepUrl = getLastExecutionUrl(authSession);
logger.infof("Redirecting to 'page expired' now. Will use last step URL: %s", lastStepUrl);
logger.debugf("Redirecting to 'page expired' now. Will use last step URL: %s", lastStepUrl);
return session.getProvider(LoginFormsProvider.class)
.setActionUri(lastStepUrl)

View file

@ -77,7 +77,7 @@ public abstract class BrowserHistoryHelper {
if (entity instanceof String) {
String responseString = (String) entity;
URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
URI lastExecutionURL = new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
// Inject javascript for history "replaceState"
String responseWithJavascript = responseWithJavascript(responseString, lastExecutionURL.toString());
@ -124,6 +124,7 @@ public abstract class BrowserHistoryHelper {
}
// This impl is limited ATM. Saved request doesn't save response HTTP headers, so they may not be fully restored..
private static class RedirectAfterPostHelper extends BrowserHistoryHelper {
private static final String CACHED_RESPONSE = "cached.response";
@ -141,10 +142,11 @@ public abstract class BrowserHistoryHelper {
String responseString = (String) entity;
authSession.setAuthNote(CACHED_RESPONSE, responseString);
URI lastExecutionURL = new PageExpiredRedirect(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
URI lastExecutionURL = new AuthenticationFlowURLHelper(session, session.getContext().getRealm(), session.getContext().getUri()).getLastExecutionUrl(authSession);
// TODO:mposolda trace
logger.infof("Saved response challenge and redirect to %s", lastExecutionURL);
if (logger.isTraceEnabled()) {
logger.tracef("Saved response challenge and redirect to %s", lastExecutionURL);
}
return Response.status(302).location(lastExecutionURL).build();
}
@ -160,11 +162,12 @@ public abstract class BrowserHistoryHelper {
if (savedResponse != null) {
authSession.removeAuthNote(CACHED_RESPONSE);
// TODO:mposolda trace
logger.infof("Restored previously saved request");
if (logger.isTraceEnabled()) {
logger.tracef("Restored previously saved request");
}
Response.ResponseBuilder builder = Response.status(200).type(MediaType.TEXT_HTML_UTF_8).entity(savedResponse);
BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO:mposolda cRather all the headers from the saved response should be added here.
BrowserSecurityHeaderSetup.headers(builder, session.getContext().getRealm()); // TODO rather all the headers from the saved response should be added here.
return builder.build();
}

View file

@ -119,6 +119,8 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
return callback.cancelled(state);
}
Response errorResponse = null;
try {
Twitter twitter = new TwitterFactory().getInstance();
@ -155,17 +157,21 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
return callback.authenticated(identity);
} catch (WebApplicationException e) {
sendErrorEvent();
return e.getResponse();
} catch (Exception e) {
logger.error("Could get user profile from twitter.", e);
sendErrorEvent();
return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
}
}
private void sendErrorEvent() {
EventBuilder event = new EventBuilder(realm, session, clientConnection);
event.event(EventType.LOGIN);
event.error("twitter_login_failed");
return ErrorPage.error(session, Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE);
}
}
@Override

View file

@ -163,7 +163,9 @@ public class OAuthClient {
realm = "test";
clientId = "test-app";
redirectUri = APP_ROOT + "/auth";
state = KeycloakModelUtils::generateId;
state = () -> {
return KeycloakModelUtils.generateId();
};
scope = null;
uiLocales = null;
clientSessionState = null;

View file

@ -122,9 +122,9 @@ public class AssertEvents implements TestRule {
.session(isUUID());
}
// TODO:mposolda codeId is not needed anymore
public ExpectedEvent expectCodeToToken(String codeId, String sessionId) {
return expect(EventType.CODE_TO_TOKEN)
.detail(Details.CODE_ID, codeId)
.detail(Details.TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_ID, isUUID())
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_REFRESH)

View file

@ -63,46 +63,50 @@ public class RequiredActionMultipleActionsTest extends AbstractTestRealmKeycloak
loginPage.open();
loginPage.login("test-user@localhost", "password");
String sessionId = null;
String codeId = null;
if (changePasswordPage.isCurrent()) {
sessionId = updatePassword(sessionId);
codeId = updatePassword(codeId);
updateProfilePage.assertCurrent();
updateProfile(sessionId);
updateProfile(codeId);
} else if (updateProfilePage.isCurrent()) {
sessionId = updateProfile(sessionId);
codeId = updateProfile(codeId);
changePasswordPage.assertCurrent();
updatePassword(sessionId);
updatePassword(codeId);
} else {
Assert.fail("Expected to update password and profile before login");
}
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().session(sessionId).assertEvent();
events.expectLogin().session(codeId).assertEvent();
}
public String updatePassword(String sessionId) {
public String updatePassword(String codeId) {
changePasswordPage.changePassword("new-password", "new-password");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_PASSWORD);
if (sessionId != null) {
expectedEvent.session(sessionId);
if (codeId != null) {
expectedEvent.detail(Details.CODE_ID, codeId);
}
return expectedEvent.assertEvent().getSessionId();
return expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
}
public String updateProfile(String sessionId) {
public String updateProfile(String codeId) {
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com");
if (sessionId != null) {
expectedEvent.session(sessionId);
AssertEvents.ExpectedEvent expectedEvent = events.expectRequiredAction(EventType.UPDATE_EMAIL)
.detail(Details.PREVIOUS_EMAIL, "test-user@localhost")
.detail(Details.UPDATED_EMAIL, "new@email.com");
if (codeId != null) {
expectedEvent.detail(Details.CODE_ID, codeId);
}
sessionId = expectedEvent.assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
return sessionId;
codeId = expectedEvent.assertEvent().getDetails().get(Details.CODE_ID);
events.expectRequiredAction(EventType.UPDATE_PROFILE)
.detail(Details.CODE_ID, codeId)
.assertEvent();
return codeId;
}
}

View file

@ -127,11 +127,12 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent().getSessionId();
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setuptotp").assertEvent()
.getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().user(userId).session(sessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
events.expectLogin().user(userId).session(authSessionId).detail(Details.USERNAME, "setuptotp").assertEvent();
}
@Test
@ -145,15 +146,16 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.configure(totp.generateTOTP(totpSecret));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
String authSessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
.getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().session(sessionId).assertEvent();
EventRepresentation loginEvent = events.expectLogin().session(authSessionId).assertEvent();
oauth.openLogout();
events.expectLogout(loginEvent.getSessionId()).assertEvent();
events.expectLogout(authSessionId).assertEvent();
loginPage.open();
loginPage.login("test-user@localhost", "password");
@ -229,7 +231,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
totpPage.assertCurrent();
totpPage.configure(totp.generateTOTP(totpPage.getTotpSecret()));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).user(userId).detail(Details.USERNAME, "setupTotp2").assertEvent()
.getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -260,7 +263,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
TimeBasedOTP timeBased = new TimeBasedOTP(HmacOTP.HMAC_SHA1, 8, 30, 1);
totpPage.configure(timeBased.generateTOTP(totpSecret));
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
.getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
@ -311,7 +315,8 @@ public class RequiredActionTotpSetupTest extends AbstractTestRealmKeycloakTest {
HmacOTP otpgen = new HmacOTP(6, HmacOTP.HMAC_SHA1, 1);
totpPage.configure(otpgen.generateHOTP(totpSecret, 0));
String uri = driver.getCurrentUrl();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent().getSessionId();
String sessionId = events.expectRequiredAction(EventType.UPDATE_TOTP).assertEvent()
.getDetails().get(Details.CODE_ID);
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.actions;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
@ -89,12 +90,12 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
updateProfilePage.update("New first", "New last", "new@email.com", "test-user@localhost");
String sessionId = events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent().getSessionId();
events.expectRequiredAction(EventType.UPDATE_PROFILE).session(sessionId).assertEvent();
events.expectRequiredAction(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, "test-user@localhost").detail(Details.UPDATED_EMAIL, "new@email.com").assertEvent();
events.expectRequiredAction(EventType.UPDATE_PROFILE).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().session(sessionId).assertEvent();
events.expectLogin().assertEvent();
// assert user is really updated in persistent store
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
@ -116,19 +117,17 @@ public class RequiredActionUpdateProfileTest extends AbstractTestRealmKeycloakTe
updateProfilePage.update("New first", "New last", "john-doh@localhost", "new");
String sessionId = events
.expectLogin()
events.expectLogin()
.event(EventType.UPDATE_PROFILE)
.detail(Details.USERNAME, "john-doh@localhost")
.user(userId)
.session(AssertEvents.isUUID())
.session(Matchers.nullValue(String.class))
.removeDetail(Details.CONSENT)
.assertEvent()
.getSessionId();
.assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).session(sessionId).assertEvent();
events.expectLogin().detail(Details.USERNAME, "john-doh@localhost").user(userId).assertEvent();
// assert user is really updated in persistent store
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "new");

View file

@ -16,6 +16,7 @@
*/
package org.keycloak.testsuite.actions;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Assert;
import org.junit.Before;
@ -86,11 +87,11 @@ public class TermsAndConditionsTest extends AbstractTestRealmKeycloakTest {
termsPage.acceptTerms();
String sessionId = events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent().getSessionId();
events.expectRequiredAction(EventType.CUSTOM_REQUIRED_ACTION).removeDetail(Details.REDIRECT_URI).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID).assertEvent();
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
events.expectLogin().session(sessionId).assertEvent();
events.expectLogin().assertEvent();
// assert user attribute is properly set
UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
@ -123,6 +124,7 @@ public class TermsAndConditionsTest extends AbstractTestRealmKeycloakTest {
events.expectLogin().event(EventType.CUSTOM_REQUIRED_ACTION_ERROR).detail(Details.CUSTOM_REQUIRED_ACTION, TermsAndConditions.PROVIDER_ID)
.error(Errors.REJECTED_BY_USER)
.removeDetail(Details.CONSENT)
.session(Matchers.nullValue(String.class))
.assertEvent();

View file

@ -40,6 +40,7 @@ import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.ietf.jgss.GSSCredential;
import org.jboss.arquillian.graphene.page.Page;
@ -347,7 +348,10 @@ public abstract class AbstractKerberosTest extends AbstractAuthTest {
cleanupApacheHttpClient();
}
DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder().build();
DefaultHttpClient httpClient = (DefaultHttpClient) new HttpClientBuilder()
.disableCookieCache(false)
.build();
httpClient.getAuthSchemes().register(AuthPolicy.SPNEGO, spnegoSchemeFactory);
if (useSpnego) {

View file

@ -17,18 +17,23 @@
package org.keycloak.testsuite.federation.kerberos;
import java.net.URI;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.constants.KerberosConstants;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.KerberosConfig;
@ -148,15 +153,24 @@ public class KerberosStandaloneTest extends AbstractKerberosTest {
Response spnegoResponse = spnegoLogin("hnelson", "secret");
String context = spnegoResponse.readEntity(String.class);
spnegoResponse.close();
Assert.assertTrue(context.contains("Log in to test"));
Pattern pattern = Pattern.compile("action=\"([^\"]+)\"");
Matcher m = pattern.matcher(context);
Assert.assertTrue(m.find());
String url = m.group(1);
driver.navigate().to(url);
Assert.assertTrue(loginPage.isCurrent());
loginPage.login("test-user@localhost", "password");
String pageSource = driver.getPageSource();
assertAuthenticationSuccess(driver.getCurrentUrl());
// Follow login with HttpClient. Improve if needed
MultivaluedMap<String, String> params = new javax.ws.rs.core.MultivaluedHashMap<>();
params.putSingle("username", "test-user@localhost");
params.putSingle("password", "password");
Response response = client.target(url).request()
.post(Entity.form(params));
URI redirectUri = response.getLocation();
assertAuthenticationSuccess(redirectUri.toString());
events.clear();
testRealmResource().components().add(kerberosProvider);

View file

@ -254,7 +254,7 @@ public class BrowserButtonsTest extends AbstractTestRealmKeycloakTest {
// KEYCLOAK-4670 - Flow 5
@Test
public void clickBackButtonAfterReturnFromRegister() {
public void clickBackButtonAfterReturnFromRegister() throws Exception {
loginPage.open();
loginPage.clickRegister();
registerPage.assertCurrent();

View file

@ -21,6 +21,8 @@ import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.events.Details;
import org.keycloak.models.Constants;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -28,6 +30,7 @@ import org.keycloak.testsuite.AbstractTestRealmKeycloakTest;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.AuthServerTestEnricher;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.InfoPage;
@ -43,7 +46,7 @@ import org.keycloak.testsuite.util.GreenMailRule;
import org.keycloak.testsuite.util.UserBuilder;
/**
* Tries to test multiple browser tabs
* Tries to simulate testing with multiple browser tabs
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ -245,4 +248,43 @@ public class MultipleTabsLoginTest extends AbstractTestRealmKeycloakTest {
}
@Test
public void loginActionWithoutExecution() throws Exception {
oauth.openLoginForm();
// Manually remove execution from the URL and try to simulate the request just with "code" parameter
String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
actionUrl = actionUrl.replaceFirst("&execution=.*", "");
driver.navigate().to(actionUrl);
loginExpiredPage.assertCurrent();
}
// Same like "loginActionWithoutExecution", but AuthenticationSession is in REQUIRED_ACTIONS action
@Test
public void loginActionWithoutExecutionInRequiredActions() throws Exception {
oauth.openLoginForm();
loginPage.assertCurrent();
loginPage.login("login-test", "password");
updatePasswordPage.assertCurrent();
// Manually remove execution from the URL and try to simulate the request just with "code" parameter
String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
actionUrl = actionUrl.replaceFirst("&execution=.*", "");
driver.navigate().to(actionUrl);
// Back on updatePasswordPage now
updatePasswordPage.assertCurrent();
updatePasswordPage.changePassword("password", "password");
updateProfilePage.update("John", "Doe3", "john@doe3.com");
appPage.assertCurrent();
}
}

View file

@ -210,12 +210,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
oauth.redirectUri(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth/admin/test/console/nosuch.html");
oauth.openLoginForm();
String actionUrl = driver.getPageSource().split("action=\"")[1].split("\"")[0].replaceAll("&amp;", "&");
actionUrl = actionUrl.replaceFirst("&execution=.*", "");
String loginPageCode = actionUrl.split("code=")[1].split("&")[0];
driver.navigate().to(actionUrl);
String loginPageCode = driver.getPageSource().split("code=")[1].split("&")[0].split("\"")[0];
oauth.fillLoginForm("test-user@localhost", "password");
@ -452,7 +447,7 @@ public class AccessTokenTest extends AbstractKeycloakTest {
Assert.assertEquals(400, response.getStatusCode());
EventRepresentation event = events.poll();
assertNotNull(event.getDetails().get(Details.CODE_ID));
assertNull(event.getDetails().get(Details.CODE_ID));
UserManager.realm(adminClient.realm("test")).user(user).removeRequiredAction(UserModel.RequiredAction.UPDATE_PROFILE.toString());
}

View file

@ -155,7 +155,7 @@ public class AuthorizationCodeTest extends AbstractKeycloakTest {
}
private void assertCode(String expectedCodeId, String actualCode) {
assertEquals(expectedCodeId, actualCode.split("\\.")[1]);
assertEquals(expectedCodeId, actualCode.split("\\.")[2]);
}
}

View file

@ -16,8 +16,8 @@
*/
package org.keycloak.testsuite.oauth;
import org.hamcrest.Matchers;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@ -155,6 +155,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
.client(THIRD_PARTY_APP)
.error("rejected_by_user")
.removeDetail(Details.CONSENT)
.session(Matchers.nullValue(String.class))
.assertEvent();
}
@ -309,6 +310,7 @@ public class OAuthGrantTest extends AbstractKeycloakTest {
.client(THIRD_PARTY_APP)
.error("rejected_by_user")
.removeDetail(Details.CONSENT)
.session(Matchers.nullValue(String.class))
.assertEvent();
oauth.scope("foo-role third-party/bar-role");

View file

@ -304,6 +304,31 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
}
@Test
public void promptLoginDifferentUser() throws Exception {
String sss = oauth.getLoginFormUrl();
System.out.println(sss);
// Login user
loginPage.open();
loginPage.login("test-user@localhost", "password");
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
EventRepresentation loginEvent = events.expectLogin().detail(Details.USERNAME, "test-user@localhost").assertEvent();
IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
// Assert need to re-authenticate with prompt=login
driver.navigate().to(oauth.getLoginFormUrl() + "&prompt=login");
// Authenticate as different user
loginPage.assertCurrent();
loginPage.login("john-doh@localhost", "password");
errorPage.assertCurrent();
Assert.assertTrue(errorPage.getError().startsWith("You are already authenticated as different user"));
}
// DISPLAY & OTHERS
@Test

View file

@ -12,7 +12,7 @@ function authenticate(context) {
return;
}
if (clientSession.getAuthMethod() != "${authMethod}") {
if (clientSession.getProtocol() != "${authMethod}") {
context.failure(AuthenticationFlowError.INVALID_CLIENT_SESSION);
return;
}

View file

@ -35,6 +35,7 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
@ -69,7 +70,9 @@ public class OAuthClient {
private String redirectUri = "http://localhost:8081/app/auth";
private String state = "mystate";
private StateParamProvider state = () -> {
return KeycloakModelUtils.generateId();
};
private String scope;
@ -438,7 +441,7 @@ public class OAuthClient {
b.queryParam(OAuth2Constants.REDIRECT_URI, redirectUri);
}
if (state != null) {
b.queryParam(OAuth2Constants.STATE, state);
b.queryParam(OAuth2Constants.STATE, state.getState());
}
if(uiLocales != null){
b.queryParam(OAuth2Constants.UI_LOCALES_PARAM, uiLocales);
@ -509,8 +512,17 @@ public class OAuthClient {
return this;
}
public OAuthClient state(String state) {
this.state = state;
public OAuthClient stateParamHardcoded(String value) {
this.state = () -> {
return value;
};
return this;
}
public OAuthClient stateParamRandom() {
this.state = () -> {
return KeycloakModelUtils.generateId();
};
return this;
}
@ -639,4 +651,10 @@ public class OAuthClient {
}
}
private interface StateParamProvider {
String getState();
}
}

View file

@ -33,12 +33,16 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.pages.IdpConfirmLinkPage;
import org.keycloak.testsuite.pages.IdpLinkEmailPage;
import org.keycloak.testsuite.pages.InfoPage;
import org.keycloak.testsuite.pages.LoginExpiredPage;
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import javax.mail.internet.MimeMessage;
@ -71,6 +75,9 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
@WebResource
protected LoginPasswordUpdatePage passwordUpdatePage;
@WebResource
protected LoginExpiredPage loginExpiredPage;
/**
@ -360,6 +367,101 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
}
/**
* Variation of previous test, which uses browser buttons (back, refresh etc)
*/
@Test
public void testLinkAccountByReauthenticationWithPassword_browserButtons() throws Exception {
// Remove smtp config. The reauthentication by username+password screen will be automatically used then
final Map<String, String> smtpConfig = new HashMap<>();
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
smtpConfig.putAll(realmWithBroker.getSmtpConfig());
realmWithBroker.setSmtpConfig(Collections.<String, String>emptyMap());
}
}, APP_REALM_ID);
// Use invalid username for the first time
loginIDP("foo");
assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
this.loginPage.login("pedroigor", "password");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
// Click browser 'back' and then 'forward' and then continue
driver.navigate().back();
Assert.assertTrue(driver.getPageSource().contains("You are already logged in."));
driver.navigate().forward();
this.loginExpiredPage.assertCurrent();
this.loginExpiredPage.clickLoginContinueLink();
this.idpConfirmLinkPage.assertCurrent();
// Click browser 'back' on review profile page
this.idpConfirmLinkPage.clickReviewProfile();
this.updateProfilePage.assertCurrent();
driver.navigate().back();
this.loginExpiredPage.assertCurrent();
this.loginExpiredPage.clickLoginContinueLink();
this.updateProfilePage.assertCurrent();
this.updateProfilePage.update("Pedro", "Igor", "psilva@redhat.com");
this.idpConfirmLinkPage.assertCurrent();
this.idpConfirmLinkPage.clickLinkAccount();
// Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
Assert.assertEquals("pedroigor", this.loginPage.getUsername());
Assert.assertFalse(this.loginPage.isUsernameInputEnabled());
Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getInfoMessage());
try {
this.loginPage.findSocialButton(getProviderId());
Assert.fail("Not expected to see social button with " + getProviderId());
} catch (NoSuchElementException expected) {
}
try {
this.loginPage.clickRegister();
Assert.fail("Not expected to see register link");
} catch (NoSuchElementException expected) {
}
// Use bad password first
this.loginPage.login("password1");
Assert.assertEquals("Invalid username or password.", this.loginPage.getError());
// Click browser 'back' and then continue
this.driver.navigate().back();
this.loginExpiredPage.assertCurrent();
this.loginExpiredPage.clickLoginContinueLink();
// Use correct password now
this.loginPage.login("password");
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Restore smtp config
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
realmWithBroker.setSmtpConfig(smtpConfig);
}
}, APP_REALM_ID);
}
/**
* Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen)
* and additionally he goes through "forget password"
@ -418,6 +520,93 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi
}
/**
* Same like above, but "forget password" link is opened in different browser
*/
@Test
public void testLinkAccountByReauthentication_forgetPassword_differentBrowser() throws Throwable {
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED);
setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF);
}
}, APP_REALM_ID);
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
// Click "Forget password" on login page. Email sent directly because username is known
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
this.loginPage.resetPassword();
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage());
// Click on link from email
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
MimeMessage message = greenMail.getReceivedMessages()[0];
String linkFromMail = getVerificationEmailLink(message);
// Simulate 2nd browser
WebRule webRule2 = new WebRule(this);
webRule2.before();
WebDriver driver2 = webRule2.getDriver();
LoginPasswordUpdatePage passwordUpdatePage2 = webRule2.getPage(LoginPasswordUpdatePage.class);
InfoPage infoPage2 = webRule2.getPage(InfoPage.class);
driver2.navigate().to(linkFromMail.trim());
// Need to update password now
passwordUpdatePage2.assertCurrent();
passwordUpdatePage2.changePassword("password", "password");
// authenticated, but not redirected to app. Just seeing info page.
infoPage2.assertCurrent();
Assert.assertEquals("Your account has been updated.", infoPage2.getInfo());
// User is not yet linked with identity provider. He needs to authenticate again in 1st browser
RealmModel realmWithBroker = getRealm();
Set<FederatedIdentityModel> federatedIdentities = this.session.users().getFederatedIdentities(this.session.users().getUserByUsername("pedroigor", realmWithBroker), realmWithBroker);
assertEquals(0, federatedIdentities.size());
// Continue with 1st browser
loginIDP("pedroigor");
this.idpConfirmLinkPage.assertCurrent();
Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage());
this.idpConfirmLinkPage.clickLinkAccount();
Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle());
this.loginPage.login("password");
// authenticated and redirected to app. User is linked with identity provider
assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor");
// Revert everything
webRule2.after();
brokerServerRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) {
setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW,
IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE);
}
}, APP_REALM_ID);
}
protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) {
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app"));
UserModel federatedUser = getFederatedUser();

View file

@ -74,8 +74,6 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
brokerServerRule.stopSession(session, true);
Thread.sleep(10000000);
driver.navigate().to("http://localhost:8081/test-app");
loginPage.clickSocial(getProviderId());
loginPage.login("test-user", "password");

View file

@ -117,11 +117,9 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
grantPage.assertCurrent();
grantPage.cancel();
// Assert error page with backToApplication link displayed
errorPage.assertCurrent();
errorPage.clickBackToApplication();
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
// Assert login page with "You took too long to login..." message
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/login-actions/authenticate"));
Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
} finally {
Time.setOffset(0);
@ -152,14 +150,9 @@ public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityPro
grantPage.assertCurrent();
grantPage.cancel();
// Assert error page without backToApplication link (clientSession expired and was removed on the server)
errorPage.assertCurrent();
try {
errorPage.clickBackToApplication();
fail("Not expected to have link backToApplication available");
} catch (NoSuchElementException nsee) {
// Expected;
}
// Assert login page with "You took too long to login..." message
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/login-actions/authenticate"));
Assert.assertEquals("You took too long to login. Login process starting from beginning.", loginPage.getError());
} finally {
Time.setOffset(0);

View file

@ -22,15 +22,21 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.CommonClientSessionModel;
import org.keycloak.testsuite.rule.KeycloakRule;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
@ -53,7 +59,6 @@ public class AuthenticationSessionProviderTest {
@After
public void after() {
resetSession();
session.sessions().removeUserSessions(realm);
UserModel user1 = session.users().getUserByUsername("user1", realm);
UserModel user2 = session.users().getUserByUsername("user2", realm);
@ -87,7 +92,7 @@ public class AuthenticationSessionProviderTest {
// Ensure session is here
authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
testLoginSession(authSession, client1.getId(), null, "foo");
testAuthenticationSession(authSession, client1.getId(), null, "foo");
Assert.assertEquals(100, authSession.getTimestamp());
// Update and commit
@ -99,7 +104,7 @@ public class AuthenticationSessionProviderTest {
// Ensure session was updated
authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
testLoginSession(authSession, client1.getId(), user1.getId(), "foo-updated");
testAuthenticationSession(authSession, client1.getId(), user1.getId(), "foo-updated");
Assert.assertEquals(200, authSession.getTimestamp());
// Remove and commit
@ -113,7 +118,7 @@ public class AuthenticationSessionProviderTest {
}
@Test
public void testLoginSessionRestart() {
public void testAuthenticationSessionRestart() {
ClientModel client1 = realm.getClientByClientId("test-app");
UserModel user1 = session.users().getUserByUsername("user1", realm);
@ -136,7 +141,7 @@ public class AuthenticationSessionProviderTest {
resetSession();
authSession = session.authenticationSessions().getAuthenticationSession(realm, authSession.getId());
testLoginSession(authSession, client1.getId(), null, null);
testAuthenticationSession(authSession, client1.getId(), null, null);
Assert.assertTrue(authSession.getTimestamp() > 0);
Assert.assertTrue(authSession.getClientNotes().isEmpty());
@ -145,7 +150,120 @@ public class AuthenticationSessionProviderTest {
}
private void testLoginSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
@Test
public void testExpiredAuthSessions() {
try {
realm.setAccessCodeLifespan(10);
realm.setAccessCodeLifespanUserAction(10);
realm.setAccessCodeLifespanLogin(30);
// Login lifespan is largest
String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
resetSession();
Time.setOffset(25);
session.authenticationSessions().removeExpired(realm);
resetSession();
assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
Time.setOffset(35);
session.authenticationSessions().removeExpired(realm);
resetSession();
assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
// User action is largest
realm.setAccessCodeLifespanUserAction(40);
Time.setOffset(0);
authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
resetSession();
Time.setOffset(35);
session.authenticationSessions().removeExpired(realm);
resetSession();
assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
Time.setOffset(45);
session.authenticationSessions().removeExpired(realm);
resetSession();
assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
// Access code is largest
realm.setAccessCodeLifespan(50);
Time.setOffset(0);
authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
resetSession();
Time.setOffset(45);
session.authenticationSessions().removeExpired(realm);
resetSession();
assertNotNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
Time.setOffset(55);
session.authenticationSessions().removeExpired(realm);
resetSession();
assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId));
} finally {
Time.setOffset(0);
realm.setAccessCodeLifespan(60);
realm.setAccessCodeLifespanUserAction(300);
realm.setAccessCodeLifespanLogin(1800);
}
}
@Test
public void testOnRealmRemoved() {
RealmModel fooRealm = session.realms().createRealm("foo-realm");
ClientModel fooClient = fooRealm.addClient("foo-client");
String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
String authSessionId2 = session.authenticationSessions().createAuthenticationSession(fooRealm, fooClient).getId();
resetSession();
new RealmManager(session).removeRealm(session.realms().getRealmByName("foo-realm"));
resetSession();
AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
testAuthenticationSession(authSession, realm.getClientByClientId("test-app").getId(), null, null);
Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
}
@Test
public void testOnClientRemoved() {
String authSessionId = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("test-app")).getId();
String authSessionId2 = session.authenticationSessions().createAuthenticationSession(realm, realm.getClientByClientId("third-party")).getId();
String testAppClientUUID = realm.getClientByClientId("test-app").getId();
resetSession();
new ClientManager(new RealmManager(session)).removeClient(realm, realm.getClientByClientId("third-party"));
resetSession();
AuthenticationSessionModel authSession = session.authenticationSessions().getAuthenticationSession(realm, authSessionId);
testAuthenticationSession(authSession, testAppClientUUID, null, null);
Assert.assertNull(session.authenticationSessions().getAuthenticationSession(realm, authSessionId2));
// Revert client
realm.addClient("third-party");
}
private void testAuthenticationSession(AuthenticationSessionModel authSession, String expectedClientId, String expectedUserId, String expectedAction) {
Assert.assertEquals(expectedClientId, authSession.getClient().getId());
if (expectedUserId == null) {

View file

@ -25,14 +25,15 @@ import org.junit.Test;
import org.keycloak.cluster.ClusterProvider;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.UserSessionProviderFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.models.UserManager;
import org.keycloak.services.managers.UserSessionManager;
@ -47,129 +48,129 @@ import java.util.Set;
*/
public class UserSessionInitializerTest {
// TODO:mposolda
// @ClassRule
// public static KeycloakRule kc = new KeycloakRule();
//
// private KeycloakSession session;
// private RealmModel realm;
// private UserSessionManager sessionManager;
//
// @Before
// public void before() {
// session = kc.startSession();
// realm = session.realms().getRealm("test");
// session.users().addUser(realm, "user1").setEmail("user1@localhost");
// session.users().addUser(realm, "user2").setEmail("user2@localhost");
// sessionManager = new UserSessionManager(session);
// }
//
// @After
// public void after() {
// resetSession();
// session.sessions().removeUserSessions(realm);
// UserModel user1 = session.users().getUserByUsername("user1", realm);
// UserModel user2 = session.users().getUserByUsername("user2", realm);
//
// UserManager um = new UserManager(session);
// um.removeUser(realm, user1);
// um.removeUser(realm, user2);
// kc.stopSession(session, true);
// }
//
// @Test
// public void testUserSessionInitializer() {
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// // Create and persist offline sessions
// int started = Time.currentTime();
// int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
//
// for (UserSessionModel origSession : origSessions) {
// UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
// for (ClientSessionModel clientSession : userSession.getClientSessions()) {
// sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
// }
// }
//
// resetSession();
//
// // Delete cache (persisted sessions are still kept)
// session.sessions().onRealmRemoved(realm);
//
// // Clear ispn cache to ensure initializerState is removed as well
// InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
// infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
//
// resetSession();
//
// ClientModel testApp = realm.getClientByClientId("test-app");
// ClientModel thirdparty = realm.getClientByClientId("third-party");
// Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
// Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
//
// // Load sessions from persister into infinispan/memory
// UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
// userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
//
// resetSession();
//
// // Assert sessions are in
// testApp = realm.getClientByClientId("test-app");
// Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
//
// List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
// UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
//
// UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
// UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
// UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
// }
//
// private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
// ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
// if (userSession != null) clientSession.setUserSession(userSession);
// clientSession.setRedirectUri(redirect);
// if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
// if (roles != null) clientSession.setRoles(roles);
// if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
// return clientSession;
// }
//
// private UserSessionModel[] createSessions() {
// UserSessionModel[] sessions = new UserSessionModel[3];
// sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
//
// Set<String> roles = new HashSet<String>();
// roles.add("one");
// roles.add("two");
//
// Set<String> protocolMappers = new HashSet<String>();
// protocolMappers.add("mapper-one");
// protocolMappers.add("mapper-two");
//
// createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
// createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
// createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
// createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// resetSession();
//
// return sessions;
// }
//
// private void resetSession() {
// kc.stopSession(session, true);
// session = kc.startSession();
// realm = session.realms().getRealm("test");
// sessionManager = new UserSessionManager(session);
// }
@ClassRule
public static KeycloakRule kc = new KeycloakRule();
private KeycloakSession session;
private RealmModel realm;
private UserSessionManager sessionManager;
@Before
public void before() {
session = kc.startSession();
realm = session.realms().getRealm("test");
session.users().addUser(realm, "user1").setEmail("user1@localhost");
session.users().addUser(realm, "user2").setEmail("user2@localhost");
sessionManager = new UserSessionManager(session);
}
@After
public void after() {
resetSession();
session.sessions().removeUserSessions(realm);
UserModel user1 = session.users().getUserByUsername("user1", realm);
UserModel user2 = session.users().getUserByUsername("user2", realm);
UserManager um = new UserManager(session);
um.removeUser(realm, user1);
um.removeUser(realm, user2);
kc.stopSession(session, true);
}
@Test
public void testUserSessionInitializer() {
UserSessionModel[] origSessions = createSessions();
resetSession();
// Create and persist offline sessions
int started = Time.currentTime();
int serverStartTime = session.getProvider(ClusterProvider.class).getClusterStartupTime();
for (UserSessionModel origSession : origSessions) {
UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
}
}
resetSession();
// Delete cache (persisted sessions are still kept)
session.sessions().onRealmRemoved(realm);
// Clear ispn cache to ensure initializerState is removed as well
InfinispanConnectionProvider infinispan = session.getProvider(InfinispanConnectionProvider.class);
infinispan.getCache(InfinispanConnectionProvider.WORK_CACHE_NAME).clear();
resetSession();
ClientModel testApp = realm.getClientByClientId("test-app");
ClientModel thirdparty = realm.getClientByClientId("third-party");
Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, testApp));
Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(realm, thirdparty));
// Load sessions from persister into infinispan/memory
UserSessionProviderFactory userSessionFactory = (UserSessionProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserSessionProvider.class);
userSessionFactory.loadPersistentSessions(session.getKeycloakSessionFactory(), 1, 2);
resetSession();
// Assert sessions are in
testApp = realm.getClientByClientId("test-app");
Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
List<UserSessionModel> loadedSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, serverStartTime, "test-app", "third-party");
UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, serverStartTime, "test-app");
UserSessionPersisterProviderTest.assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, serverStartTime, "test-app");
}
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
return clientSession;
}
private UserSessionModel[] createSessions() {
UserSessionModel[] sessions = new UserSessionModel[3];
sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
Set<String> roles = new HashSet<String>();
roles.add("one");
roles.add("two");
Set<String> protocolMappers = new HashSet<String>();
protocolMappers.add("mapper-one");
protocolMappers.add("mapper-two");
createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
return sessions;
}
private void resetSession() {
kc.stopSession(session, true);
session = kc.startSession();
realm = session.realms().getRealm("test");
sessionManager = new UserSessionManager(session);
}
}

View file

@ -23,13 +23,14 @@ import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
@ -45,398 +46,398 @@ import java.util.Set;
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserSessionPersisterProviderTest {
// TODO:mposolda
// @ClassRule
// public static KeycloakRule kc = new KeycloakRule();
//
// private KeycloakSession session;
// private RealmModel realm;
// private UserSessionPersisterProvider persister;
//
// @Before
// public void before() {
// session = kc.startSession();
// realm = session.realms().getRealm("test");
// session.users().addUser(realm, "user1").setEmail("user1@localhost");
// session.users().addUser(realm, "user2").setEmail("user2@localhost");
// persister = session.getProvider(UserSessionPersisterProvider.class);
// }
//
// @After
// public void after() {
// resetSession();
// session.sessions().removeUserSessions(realm);
// UserModel user1 = session.users().getUserByUsername("user1", realm);
// UserModel user2 = session.users().getUserByUsername("user2", realm);
//
// UserManager um = new UserManager(session);
// if (user1 != null) {
// um.removeUser(realm, user1);
// }
// if (user2 != null) {
// um.removeUser(realm, user2);
// }
// kc.stopSession(session, true);
// }
//
// @Test
// public void testPersistenceWithLoad() {
// // Create some sessions in infinispan
// int started = Time.currentTime();
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// // Persist 3 created userSessions and clientSessions as offline
// ClientModel testApp = realm.getClientByClientId("test-app");
// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
// for (UserSessionModel userSession : userSessions) {
// persistUserSession(userSession, true);
// }
//
// // Persist 1 online session
// UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
// persistUserSession(userSession, false);
//
// resetSession();
//
// // Assert online session
// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
// UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
//
// // Assert offline sessions
// loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
// UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
//
// assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
// assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
// assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
// }
//
// @Test
// public void testUpdateTimestamps() {
// // Create some sessions in infinispan
// int started = Time.currentTime();
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// // Persist 3 created userSessions and clientSessions as offline
// ClientModel testApp = realm.getClientByClientId("test-app");
// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
// for (UserSessionModel userSession : userSessions) {
// persistUserSession(userSession, true);
// }
//
// // Persist 1 online session
// UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
// persistUserSession(userSession, false);
//
// resetSession();
//
// // update timestamps
// int newTime = started + 50;
// persister.updateAllTimestamps(newTime);
//
// // Assert online session
// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
// Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
//
// // Assert offline sessions
// loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
// Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
// }
//
// private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
// int clientSessionsCount = 0;
// for (UserSessionModel loadedSession : loadedSessions) {
// Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
// for (ClientSessionModel clientSession : loadedSession.getClientSessions()) {
// Assert.assertEquals(expectedTime, clientSession.getTimestamp());
// clientSessionsCount++;
// }
// }
// return clientSessionsCount;
// }
//
// @Test
// public void testUpdateAndRemove() {
// // Create some sessions in infinispan
// int started = Time.currentTime();
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// // Persist 1 offline session
// UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
// persistUserSession(userSession, true);
//
// resetSession();
//
// // Load offline session
// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
// UserSessionModel persistedSession = loadedSessions.get(0);
// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
//
// // Update userSession
// Time.setOffset(10);
// try {
// persistedSession.setLastSessionRefresh(Time.currentTime());
// persistedSession.setNote("foo", "bar");
// persistedSession.setState(UserSessionModel.State.LOGGING_IN);
// persister.updateUserSession(persistedSession, true);
//
// // create new clientSession
// ClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
// "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
// persister.createClientSession(clientSession, true);
//
// resetSession();
//
// // Assert session updated
// loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
// persistedSession = loadedSessions.get(0);
// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
// Assert.assertEquals("bar", persistedSession.getNote("foo"));
// Assert.assertEquals(UserSessionModel.State.LOGGING_IN, persistedSession.getState());
//
// // Remove clientSession
// persister.removeClientSession(clientSession.getId(), true);
//
// resetSession();
//
// // Assert clientSession removed
// loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
// persistedSession = loadedSessions.get(0);
// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
//
// // Remove userSession
// persister.removeUserSession(persistedSession.getId(), true);
//
// resetSession();
//
// // Assert nothing found
// loadPersistedSessionsPaginated(true, 10, 0, 0);
// } finally {
// Time.setOffset(0);
// }
// }
//
// @Test
// public void testOnRealmRemoved() {
// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
// fooRealm.addClient("foo-app");
// session.users().addUser(fooRealm, "user3");
//
// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
// createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// resetSession();
//
// // Persist offline session
// fooRealm = session.realms().getRealm("foo");
// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
// persistUserSession(userSession, true);
//
// resetSession();
//
// // Assert session was persisted
// loadPersistedSessionsPaginated(true, 10, 1, 1);
//
// // Remove realm
// RealmManager realmMgr = new RealmManager(session);
// realmMgr.removeRealm(realmMgr.getRealm("foo"));
//
// resetSession();
//
// // Assert nothing loaded
// loadPersistedSessionsPaginated(true, 10, 0, 0);
// }
//
// @Test
// public void testOnClientRemoved() {
// int started = Time.currentTime();
//
// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
// fooRealm.addClient("foo-app");
// fooRealm.addClient("bar-app");
// session.users().addUser(fooRealm, "user3");
//
// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
// createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
// createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// resetSession();
//
// // Persist offline session
// fooRealm = session.realms().getRealm("foo");
// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
// persistUserSession(userSession, true);
//
// resetSession();
//
// RealmManager realmMgr = new RealmManager(session);
// ClientManager clientMgr = new ClientManager(realmMgr);
// fooRealm = realmMgr.getRealm("foo");
//
// // Assert session was persisted with both clientSessions
// UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
//
// // Remove foo-app client
// ClientModel client = fooRealm.getClientByClientId("foo-app");
// clientMgr.removeClient(fooRealm, client);
//
// resetSession();
//
// realmMgr = new RealmManager(session);
// clientMgr = new ClientManager(realmMgr);
// fooRealm = realmMgr.getRealm("foo");
//
// // Assert just one bar-app clientSession persisted now
// persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
//
// // Remove bar-app client
// client = fooRealm.getClientByClientId("bar-app");
// clientMgr.removeClient(fooRealm, client);
//
// resetSession();
//
// // Assert nothing loaded - userSession was removed as well because it was last userSession
// loadPersistedSessionsPaginated(true, 10, 0, 0);
//
// // Cleanup
// realmMgr = new RealmManager(session);
// realmMgr.removeRealm(realmMgr.getRealm("foo"));
// }
//
// @Test
// public void testOnUserRemoved() {
// // Create some sessions in infinispan
// int started = Time.currentTime();
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// // Persist 2 offline sessions of 2 users
// UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
// UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
// persistUserSession(userSession1, true);
// persistUserSession(userSession2, true);
//
// resetSession();
//
// // Load offline sessions
// List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
//
// // Properly delete user and assert his offlineSession removed
// UserModel user1 = session.users().getUserByUsername("user1", realm);
// new UserManager(session).removeUser(realm, user1);
//
// resetSession();
//
// Assert.assertEquals(1, persister.getUserSessionsCount(true));
// loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
// UserSessionModel persistedSession = loadedSessions.get(0);
// UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
//
// // KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
// UserModel user2 = session.users().getUserByUsername("user2", realm);
// session.users().removeUser(realm, user2);
//
// loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
//
// }
//
// // KEYCLOAK-1999
// @Test
// public void testNoSessions() {
// UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
// List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
// Assert.assertEquals(0, sessions.size());
// }
//
//
// private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
// ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
// if (userSession != null) clientSession.setUserSession(userSession);
// clientSession.setRedirectUri(redirect);
// if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
// if (roles != null) clientSession.setRoles(roles);
// if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
// return clientSession;
// }
//
// private UserSessionModel[] createSessions() {
// UserSessionModel[] sessions = new UserSessionModel[3];
// sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
//
// Set<String> roles = new HashSet<String>();
// roles.add("one");
// roles.add("two");
//
// Set<String> protocolMappers = new HashSet<String>();
// protocolMappers.add("mapper-one");
// protocolMappers.add("mapper-two");
//
// createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
// createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
// createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
// createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// return sessions;
// }
//
// private void persistUserSession(UserSessionModel userSession, boolean offline) {
// persister.createUserSession(userSession, offline);
// for (ClientSessionModel clientSession : userSession.getClientSessions()) {
// persister.createClientSession(clientSession, offline);
// }
// }
//
// private void resetSession() {
// kc.stopSession(session, true);
// session = kc.startSession();
// realm = session.realms().getRealm("test");
// persister = session.getProvider(UserSessionPersisterProvider.class);
// }
//
// public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
// for (UserSessionModel session : sessions) {
// if (session.getId().equals(id)) {
// UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
// return;
// }
// }
// Assert.fail("Session with ID " + id + " not found in the list");
// }
//
// private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
// int count = persister.getUserSessionsCount(offline);
//
// int start = 0;
// int pageCount = 0;
// boolean next = true;
// List<UserSessionModel> result = new ArrayList<>();
// while (next && start < count) {
// List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
// if (sess.size() == 0) {
// next = false;
// } else {
// pageCount++;
// start += sess.size();
// result.addAll(sess);
// }
// }
//
// Assert.assertEquals(pageCount, expectedPageCount);
// Assert.assertEquals(result.size(), expectedSessionsCount);
// return result;
// }
@ClassRule
public static KeycloakRule kc = new KeycloakRule();
private KeycloakSession session;
private RealmModel realm;
private UserSessionPersisterProvider persister;
@Before
public void before() {
session = kc.startSession();
realm = session.realms().getRealm("test");
session.users().addUser(realm, "user1").setEmail("user1@localhost");
session.users().addUser(realm, "user2").setEmail("user2@localhost");
persister = session.getProvider(UserSessionPersisterProvider.class);
}
@After
public void after() {
resetSession();
session.sessions().removeUserSessions(realm);
UserModel user1 = session.users().getUserByUsername("user1", realm);
UserModel user2 = session.users().getUserByUsername("user2", realm);
UserManager um = new UserManager(session);
if (user1 != null) {
um.removeUser(realm, user1);
}
if (user2 != null) {
um.removeUser(realm, user2);
}
kc.stopSession(session, true);
}
@Test
public void testPersistenceWithLoad() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
for (UserSessionModel userSession : userSessions) {
persistUserSession(userSession, true);
}
// Persist 1 online session
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
persistUserSession(userSession, false);
resetSession();
// Assert online session
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
UserSessionProviderTest.assertSession(loadedSessions.get(0), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
// Assert offline sessions
loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
UserSessionProviderTest.assertSessions(loadedSessions, origSessions);
assertSessionLoaded(loadedSessions, origSessions[0].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
assertSessionLoaded(loadedSessions, origSessions[1].getId(), session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
assertSessionLoaded(loadedSessions, origSessions[2].getId(), session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
}
@Test
public void testUpdateTimestamps() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
for (UserSessionModel userSession : userSessions) {
persistUserSession(userSession, true);
}
// Persist 1 online session
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[0].getId());
persistUserSession(userSession, false);
resetSession();
// update timestamps
int newTime = started + 50;
persister.updateAllTimestamps(newTime);
// Assert online session
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(false, 1, 1, 1);
Assert.assertEquals(2, assertTimestampsUpdated(loadedSessions, newTime));
// Assert offline sessions
loadedSessions = loadPersistedSessionsPaginated(true, 2, 2, 3);
Assert.assertEquals(4, assertTimestampsUpdated(loadedSessions, newTime));
}
private int assertTimestampsUpdated(List<UserSessionModel> loadedSessions, int expectedTime) {
int clientSessionsCount = 0;
for (UserSessionModel loadedSession : loadedSessions) {
Assert.assertEquals(expectedTime, loadedSession.getLastSessionRefresh());
for (AuthenticatedClientSessionModel clientSession : loadedSession.getAuthenticatedClientSessions().values()) {
Assert.assertEquals(expectedTime, clientSession.getTimestamp());
clientSessionsCount++;
}
}
return clientSessionsCount;
}
@Test
public void testUpdateAndRemove() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 1 offline session
UserSessionModel userSession = session.sessions().getUserSession(realm, origSessions[1].getId());
persistUserSession(userSession, true);
resetSession();
// Load offline session
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
UserSessionModel persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started, "test-app");
// Update userSession
Time.setOffset(10);
try {
persistedSession.setLastSessionRefresh(Time.currentTime());
persistedSession.setNote("foo", "bar");
persistedSession.setState(UserSessionModel.State.LOGGED_IN);
persister.updateUserSession(persistedSession, true);
// create new clientSession
AuthenticatedClientSessionModel clientSession = createClientSession(realm.getClientByClientId("third-party"), session.sessions().getUserSession(realm, persistedSession.getId()),
"http://redirect", "state", new HashSet<String>(), new HashSet<String>());
persister.createClientSession(clientSession, true);
resetSession();
// Assert session updated
loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started+10, "test-app", "third-party");
Assert.assertEquals("bar", persistedSession.getNote("foo"));
Assert.assertEquals(UserSessionModel.State.LOGGED_IN, persistedSession.getState());
// Remove clientSession
persister.removeClientSession(userSession.getId(), realm.getClientByClientId("third-party").getId(), true);
resetSession();
// Assert clientSession removed
loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user1", realm), "127.0.0.2", started, started + 10, "test-app");
// Remove userSession
persister.removeUserSession(persistedSession.getId(), true);
resetSession();
// Assert nothing found
loadPersistedSessionsPaginated(true, 10, 0, 0);
} finally {
Time.setOffset(0);
}
}
@Test
public void testOnRealmRemoved() {
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Persist offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
persistUserSession(userSession, true);
resetSession();
// Assert session was persisted
loadPersistedSessionsPaginated(true, 10, 1, 1);
// Remove realm
RealmManager realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
resetSession();
// Assert nothing loaded
loadPersistedSessionsPaginated(true, 10, 0, 0);
}
@Test
public void testOnClientRemoved() {
int started = Time.currentTime();
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
fooRealm.addClient("bar-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Persist offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
persistUserSession(userSession, true);
resetSession();
RealmManager realmMgr = new RealmManager(session);
ClientManager clientMgr = new ClientManager(realmMgr);
fooRealm = realmMgr.getRealm("foo");
// Assert session was persisted with both clientSessions
UserSessionModel persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
// Remove foo-app client
ClientModel client = fooRealm.getClientByClientId("foo-app");
clientMgr.removeClient(fooRealm, client);
resetSession();
realmMgr = new RealmManager(session);
clientMgr = new ClientManager(realmMgr);
fooRealm = realmMgr.getRealm("foo");
// Assert just one bar-app clientSession persisted now
persistedSession = loadPersistedSessionsPaginated(true, 10, 1, 1).get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "bar-app");
// Remove bar-app client
client = fooRealm.getClientByClientId("bar-app");
clientMgr.removeClient(fooRealm, client);
resetSession();
// Assert nothing loaded - userSession was removed as well because it was last userSession
loadPersistedSessionsPaginated(true, 10, 0, 0);
// Cleanup
realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
}
@Test
public void testOnUserRemoved() {
// Create some sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Persist 2 offline sessions of 2 users
UserSessionModel userSession1 = session.sessions().getUserSession(realm, origSessions[1].getId());
UserSessionModel userSession2 = session.sessions().getUserSession(realm, origSessions[2].getId());
persistUserSession(userSession1, true);
persistUserSession(userSession2, true);
resetSession();
// Load offline sessions
List<UserSessionModel> loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 2);
// Properly delete user and assert his offlineSession removed
UserModel user1 = session.users().getUserByUsername("user1", realm);
new UserManager(session).removeUser(realm, user1);
resetSession();
Assert.assertEquals(1, persister.getUserSessionsCount(true));
loadedSessions = loadPersistedSessionsPaginated(true, 10, 1, 1);
UserSessionModel persistedSession = loadedSessions.get(0);
UserSessionProviderTest.assertSession(persistedSession, session.users().getUserByUsername("user2", realm), "127.0.0.3", started, started, "test-app");
// KEYCLOAK-2431 Assert that userSessionPersister is resistent even to situation, when users are deleted "directly"
UserModel user2 = session.users().getUserByUsername("user2", realm);
session.users().removeUser(realm, user2);
loadedSessions = loadPersistedSessionsPaginated(true, 10, 0, 0);
}
// KEYCLOAK-1999
@Test
public void testNoSessions() {
UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
List<UserSessionModel> sessions = persister.loadUserSessions(0, 1, true);
Assert.assertEquals(0, sessions.size());
}
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
return clientSession;
}
private UserSessionModel[] createSessions() {
UserSessionModel[] sessions = new UserSessionModel[3];
sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
Set<String> roles = new HashSet<String>();
roles.add("one");
roles.add("two");
Set<String> protocolMappers = new HashSet<String>();
protocolMappers.add("mapper-one");
protocolMappers.add("mapper-two");
createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
return sessions;
}
private void persistUserSession(UserSessionModel userSession, boolean offline) {
persister.createUserSession(userSession, offline);
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
persister.createClientSession(clientSession, offline);
}
}
private void resetSession() {
kc.stopSession(session, true);
session = kc.startSession();
realm = session.realms().getRealm("test");
persister = session.getProvider(UserSessionPersisterProvider.class);
}
public static void assertSessionLoaded(List<UserSessionModel> sessions, String id, UserModel user, String ipAddress, int started, int lastRefresh, String... clients) {
for (UserSessionModel session : sessions) {
if (session.getId().equals(id)) {
UserSessionProviderTest.assertSession(session, user, ipAddress, started, lastRefresh, clients);
return;
}
}
Assert.fail("Session with ID " + id + " not found in the list");
}
private List<UserSessionModel> loadPersistedSessionsPaginated(boolean offline, int sessionsPerPage, int expectedPageCount, int expectedSessionsCount) {
int count = persister.getUserSessionsCount(offline);
int start = 0;
int pageCount = 0;
boolean next = true;
List<UserSessionModel> result = new ArrayList<>();
while (next && start < count) {
List<UserSessionModel> sess = persister.loadUserSessions(start, sessionsPerPage, offline);
if (sess.size() == 0) {
next = false;
} else {
pageCount++;
start += sess.size();
result.addAll(sess);
}
}
Assert.assertEquals(pageCount, expectedPageCount);
Assert.assertEquals(result.size(), expectedSessionsCount);
return result;
}
}

View file

@ -24,13 +24,14 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.session.UserSessionPersisterProvider;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
@ -41,7 +42,6 @@ import org.keycloak.testsuite.rule.LoggingRule;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -51,410 +51,380 @@ import java.util.Set;
*/
public class UserSessionProviderOfflineTest {
// TODO:mposolda
// @ClassRule
// public static KeycloakRule kc = new KeycloakRule();
//
// @Rule
// public LoggingRule loggingRule = new LoggingRule(this);
//
// private KeycloakSession session;
// private RealmModel realm;
// private UserSessionManager sessionManager;
// private UserSessionPersisterProvider persister;
//
// @Before
// public void before() {
// session = kc.startSession();
// realm = session.realms().getRealm("test");
// session.users().addUser(realm, "user1").setEmail("user1@localhost");
// session.users().addUser(realm, "user2").setEmail("user2@localhost");
// sessionManager = new UserSessionManager(session);
// persister = session.getProvider(UserSessionPersisterProvider.class);
// }
//
// @After
// public void after() {
// resetSession();
// session.sessions().removeUserSessions(realm);
// UserModel user1 = session.users().getUserByUsername("user1", realm);
// UserModel user2 = session.users().getUserByUsername("user2", realm);
//
// UserManager um = new UserManager(session);
// um.removeUser(realm, user1);
// um.removeUser(realm, user2);
// kc.stopSession(session, true);
// }
//
//
// @Test
// public void testOfflineSessionsCrud() {
// // Create some online sessions in infinispan
// int started = Time.currentTime();
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// Map<String, String> offlineSessions = new HashMap<>();
//
// // Persist 3 created userSessions and clientSessions as offline
// ClientModel testApp = realm.getClientByClientId("test-app");
// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
// for (UserSessionModel userSession : userSessions) {
// offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
// }
//
// resetSession();
//
// // Assert all previously saved offline sessions found
// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
//
// UserSessionModel offlineSession = session.sessions().getUserSession(realm, entry.getValue());
// boolean found = false;
// for (ClientSessionModel clientSession : offlineSession.getClientSessions()) {
// if (clientSession.getId().equals(entry.getKey())) {
// found = true;
// }
// }
// Assert.assertTrue(found);
// }
//
// // Find clients with offline token
// UserModel user1 = session.users().getUserByUsername("user1", realm);
// Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
// Assert.assertEquals(clients.size(), 2);
// for (ClientModel client : clients) {
// Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
// }
//
// UserModel user2 = session.users().getUserByUsername("user2", realm);
// clients = sessionManager.findClientsWithOfflineToken(realm, user2);
// Assert.assertEquals(clients.size(), 1);
// Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app"));
//
// // Test count
// testApp = realm.getClientByClientId("test-app");
// ClientModel thirdparty = realm.getClientByClientId("third-party");
// Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
//
// // Revoke "test-app" for user1
// sessionManager.revokeOfflineToken(user1, testApp);
//
// resetSession();
//
// // Assert userSession revoked
// testApp = realm.getClientByClientId("test-app");
// thirdparty = realm.getClientByClientId("third-party");
// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
// Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
//
// List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
// List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
// Assert.assertEquals(1, testAppSessions.size());
// Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
// Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
// Assert.assertEquals(1, thirdpartySessions.size());
// Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
// Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
//
// user1 = session.users().getUserByUsername("user1", realm);
// user2 = session.users().getUserByUsername("user2", realm);
// clients = sessionManager.findClientsWithOfflineToken(realm, user1);
// Assert.assertEquals(1, clients.size());
// Assert.assertEquals("third-party", clients.iterator().next().getClientId());
// clients = sessionManager.findClientsWithOfflineToken(realm, user2);
// Assert.assertEquals(1, clients.size());
// Assert.assertEquals("test-app", clients.iterator().next().getClientId());
// }
//
// @Test
// public void testOnRealmRemoved() {
// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
// fooRealm.addClient("foo-app");
// session.users().addUser(fooRealm, "user3");
//
// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
// ClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// resetSession();
//
// // Persist offline session
// fooRealm = session.realms().getRealm("foo");
// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
// clientSession = session.sessions().getClientSession(fooRealm, clientSession.getId());
// sessionManager.createOrUpdateOfflineSession(userSession.getClientSessions().get(0), userSession);
//
// resetSession();
//
// ClientSessionModel offlineClientSession = sessionManager.findOfflineClientSession(fooRealm, clientSession.getId());
// Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
// Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
// Assert.assertEquals(offlineClientSession.getId(), offlineClientSession.getUserSession().getClientSessions().get(0).getId());
//
// // Remove realm
// RealmManager realmMgr = new RealmManager(session);
// realmMgr.removeRealm(realmMgr.getRealm("foo"));
//
// resetSession();
//
// fooRealm = session.realms().createRealm("foo", "foo");
// fooRealm.addClient("foo-app");
// session.users().addUser(fooRealm, "user3");
//
// resetSession();
//
// // Assert nothing loaded
// fooRealm = session.realms().getRealm("foo");
// Assert.assertNull(sessionManager.findOfflineClientSession(fooRealm, clientSession.getId()));
// Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
//
// // Cleanup
// realmMgr = new RealmManager(session);
// realmMgr.removeRealm(realmMgr.getRealm("foo"));
// }
//
// @Test
// public void testOnClientRemoved() {
// int started = Time.currentTime();
//
// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
// fooRealm.addClient("foo-app");
// fooRealm.addClient("bar-app");
// session.users().addUser(fooRealm, "user3");
//
// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
// createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
// createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// resetSession();
//
// // Create offline session
// fooRealm = session.realms().getRealm("foo");
// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
// createOfflineSessionIncludeClientSessions(userSession);
//
// resetSession();
//
// RealmManager realmMgr = new RealmManager(session);
// ClientManager clientMgr = new ClientManager(realmMgr);
// fooRealm = realmMgr.getRealm("foo");
//
// // Assert session was persisted with both clientSessions
// UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
// UserSessionProviderTest.assertSession(offlineSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
//
// // Remove foo-app client
// ClientModel client = fooRealm.getClientByClientId("foo-app");
// clientMgr.removeClient(fooRealm, client);
//
// resetSession();
//
// realmMgr = new RealmManager(session);
// clientMgr = new ClientManager(realmMgr);
// fooRealm = realmMgr.getRealm("foo");
//
// // Assert just one bar-app clientSession persisted now
// offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
// Assert.assertEquals(1, offlineSession.getClientSessions().size());
// Assert.assertEquals("bar-app", offlineSession.getClientSessions().get(0).getClient().getClientId());
//
// // Remove bar-app client
// client = fooRealm.getClientByClientId("bar-app");
// clientMgr.removeClient(fooRealm, client);
//
// resetSession();
//
// // Assert nothing loaded - userSession was removed as well because it was last userSession
// offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
// Assert.assertEquals(0, offlineSession.getClientSessions().size());
//
// // Cleanup
// realmMgr = new RealmManager(session);
// realmMgr.removeRealm(realmMgr.getRealm("foo"));
// }
//
// @Test
// public void testOnUserRemoved() {
// int started = Time.currentTime();
//
// RealmModel fooRealm = session.realms().createRealm("foo", "foo");
// fooRealm.addClient("foo-app");
// session.users().addUser(fooRealm, "user3");
//
// UserSessionModel userSession = session.sessions().createUserSession(fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
// ClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// resetSession();
//
// // Create offline session
// fooRealm = session.realms().getRealm("foo");
// userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
// createOfflineSessionIncludeClientSessions(userSession);
//
// resetSession();
//
// RealmManager realmMgr = new RealmManager(session);
// fooRealm = realmMgr.getRealm("foo");
// UserModel user3 = session.users().getUserByUsername("user3", fooRealm);
//
// // Assert session was persisted with both clientSessions
// UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
// UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
//
// // Remove user3
// new UserManager(session).removeUser(fooRealm, user3);
//
// resetSession();
//
// // Assert userSession removed as well
// Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId()));
// Assert.assertNull(session.sessions().getOfflineClientSession(fooRealm, clientSession.getId()));
//
// // Cleanup
// realmMgr = new RealmManager(session);
// realmMgr.removeRealm(realmMgr.getRealm("foo"));
//
// }
//
// @Test
// public void testExpired() {
// // Create some online sessions in infinispan
// int started = Time.currentTime();
// UserSessionModel[] origSessions = createSessions();
//
// resetSession();
//
// Map<String, String> offlineSessions = new HashMap<>();
//
// // Persist 3 created userSessions and clientSessions as offline
// ClientModel testApp = realm.getClientByClientId("test-app");
// List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
// for (UserSessionModel userSession : userSessions) {
// offlineSessions.putAll(createOfflineSessionIncludeClientSessions(userSession));
// }
//
// resetSession();
//
// // Assert all previously saved offline sessions found
// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
// }
//
// UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
// Assert.assertNotNull(session0);
// List<String> clientSessions = new LinkedList<>();
// for (ClientSessionModel clientSession : session0.getClientSessions()) {
// clientSessions.add(clientSession.getId());
// Assert.assertNotNull(session.sessions().getOfflineClientSession(realm, clientSession.getId()));
// }
//
// UserSessionModel session1 = session.sessions().getOfflineUserSession(realm, origSessions[1].getId());
// Assert.assertEquals(1, session1.getClientSessions().size());
// ClientSessionModel cls1 = session1.getClientSessions().get(0);
//
// // sessions are in persister too
// Assert.assertEquals(3, persister.getUserSessionsCount(true));
//
// // Set lastSessionRefresh to session[0] to 0
// session0.setLastSessionRefresh(0);
//
// // Set timestamp to cls1 to 0
// cls1.setTimestamp(0);
//
// resetSession();
//
// session.sessions().removeExpired(realm);
//
// resetSession();
//
// // assert session0 not found now
// Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
// for (String clientSession : clientSessions) {
// Assert.assertNull(session.sessions().getOfflineClientSession(realm, origSessions[0].getId()));
// offlineSessions.remove(clientSession);
// }
//
// // Assert cls1 not found too
// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
// String userSessionId = entry.getValue();
// if (userSessionId.equals(session1.getId())) {
// Assert.assertFalse(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
// } else {
// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) != null);
// }
// }
// Assert.assertEquals(1, persister.getUserSessionsCount(true));
//
// // Expire everything and assert nothing found
// Time.setOffset(3000000);
// try {
// session.sessions().removeExpired(realm);
//
// resetSession();
//
// for (Map.Entry<String, String> entry : offlineSessions.entrySet()) {
// Assert.assertTrue(sessionManager.findOfflineClientSession(realm, entry.getKey()) == null);
// }
// Assert.assertEquals(0, persister.getUserSessionsCount(true));
//
// } finally {
// Time.setOffset(0);
// }
// }
//
// private Map<String, String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
// Map<String, String> offlineSessions = new HashMap<>();
//
// for (ClientSessionModel clientSession : userSession.getClientSessions()) {
// sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
// offlineSessions.put(clientSession.getId(), userSession.getId());
// }
// return offlineSessions;
// }
//
//
//
// private void resetSession() {
// kc.stopSession(session, true);
// session = kc.startSession();
// realm = session.realms().getRealm("test");
// sessionManager = new UserSessionManager(session);
// persister = session.getProvider(UserSessionPersisterProvider.class);
// }
//
// private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
// ClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client);
// if (userSession != null) clientSession.setUserSession(userSession);
// clientSession.setRedirectUri(redirect);
// if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
// if (roles != null) clientSession.setRoles(roles);
// if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
// return clientSession;
// }
//
// private UserSessionModel[] createSessions() {
// UserSessionModel[] sessions = new UserSessionModel[3];
// sessions[0] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
//
// Set<String> roles = new HashSet<String>();
// roles.add("one");
// roles.add("two");
//
// Set<String> protocolMappers = new HashSet<String>();
// protocolMappers.add("mapper-one");
// protocolMappers.add("mapper-two");
//
// createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
// createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// sessions[1] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
// createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// sessions[2] = session.sessions().createUserSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
// createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
//
// return sessions;
// }
@ClassRule
public static KeycloakRule kc = new KeycloakRule();
@Rule
public LoggingRule loggingRule = new LoggingRule(this);
private KeycloakSession session;
private RealmModel realm;
private UserSessionManager sessionManager;
private UserSessionPersisterProvider persister;
@Before
public void before() {
session = kc.startSession();
realm = session.realms().getRealm("test");
session.users().addUser(realm, "user1").setEmail("user1@localhost");
session.users().addUser(realm, "user2").setEmail("user2@localhost");
sessionManager = new UserSessionManager(session);
persister = session.getProvider(UserSessionPersisterProvider.class);
}
@After
public void after() {
resetSession();
session.sessions().removeUserSessions(realm);
UserModel user1 = session.users().getUserByUsername("user1", realm);
UserModel user2 = session.users().getUserByUsername("user2", realm);
UserManager um = new UserManager(session);
um.removeUser(realm, user1);
um.removeUser(realm, user2);
kc.stopSession(session, true);
}
@Test
public void testOfflineSessionsCrud() {
// Create some online sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Key is userSession ID, values are client UUIDS
Map<String, Set<String>> offlineSessions = new HashMap<>();
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
for (UserSessionModel userSession : userSessions) {
offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession));
}
resetSession();
// Assert all previously saved offline sessions found
for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
UserSessionModel offlineSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
Assert.assertNotNull(offlineSession);
Assert.assertEquals(offlineSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
}
// Find clients with offline token
UserModel user1 = session.users().getUserByUsername("user1", realm);
Set<ClientModel> clients = sessionManager.findClientsWithOfflineToken(realm, user1);
Assert.assertEquals(clients.size(), 2);
for (ClientModel client : clients) {
Assert.assertTrue(client.getClientId().equals("test-app") || client.getClientId().equals("third-party"));
}
UserModel user2 = session.users().getUserByUsername("user2", realm);
clients = sessionManager.findClientsWithOfflineToken(realm, user2);
Assert.assertEquals(clients.size(), 1);
Assert.assertTrue(clients.iterator().next().getClientId().equals("test-app"));
// Test count
testApp = realm.getClientByClientId("test-app");
ClientModel thirdparty = realm.getClientByClientId("third-party");
Assert.assertEquals(3, session.sessions().getOfflineSessionsCount(realm, testApp));
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
// Revoke "test-app" for user1
sessionManager.revokeOfflineToken(user1, testApp);
resetSession();
// Assert userSession revoked
testApp = realm.getClientByClientId("test-app");
thirdparty = realm.getClientByClientId("third-party");
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, testApp));
Assert.assertEquals(1, session.sessions().getOfflineSessionsCount(realm, thirdparty));
List<UserSessionModel> testAppSessions = session.sessions().getOfflineUserSessions(realm, testApp, 0, 10);
List<UserSessionModel> thirdpartySessions = session.sessions().getOfflineUserSessions(realm, thirdparty, 0, 10);
Assert.assertEquals(1, testAppSessions.size());
Assert.assertEquals("127.0.0.3", testAppSessions.get(0).getIpAddress());
Assert.assertEquals("user2", testAppSessions.get(0).getUser().getUsername());
Assert.assertEquals(1, thirdpartySessions.size());
Assert.assertEquals("127.0.0.1", thirdpartySessions.get(0).getIpAddress());
Assert.assertEquals("user1", thirdpartySessions.get(0).getUser().getUsername());
user1 = session.users().getUserByUsername("user1", realm);
user2 = session.users().getUserByUsername("user2", realm);
clients = sessionManager.findClientsWithOfflineToken(realm, user1);
Assert.assertEquals(1, clients.size());
Assert.assertEquals("third-party", clients.iterator().next().getClientId());
clients = sessionManager.findClientsWithOfflineToken(realm, user2);
Assert.assertEquals(1, clients.size());
Assert.assertEquals("test-app", clients.iterator().next().getClientId());
}
@Test
public void testOnRealmRemoved() {
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Persist offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
createOfflineSessionIncludeClientSessions(userSession);
resetSession();
UserSessionModel offlineUserSession = sessionManager.findOfflineUserSession(fooRealm, userSession.getId());
Assert.assertEquals(offlineUserSession.getAuthenticatedClientSessions().size(), 1);
AuthenticatedClientSessionModel offlineClientSession = offlineUserSession.getAuthenticatedClientSessions().values().iterator().next();
Assert.assertEquals("foo-app", offlineClientSession.getClient().getClientId());
Assert.assertEquals("user3", offlineClientSession.getUserSession().getUser().getUsername());
// Remove realm
RealmManager realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
resetSession();
fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
session.users().addUser(fooRealm, "user3");
resetSession();
// Assert nothing loaded
fooRealm = session.realms().getRealm("foo");
Assert.assertNull(sessionManager.findOfflineUserSession(fooRealm, userSession.getId()));
Assert.assertEquals(0, session.sessions().getOfflineSessionsCount(fooRealm, fooRealm.getClientByClientId("foo-app")));
// Cleanup
realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
}
@Test
public void testOnClientRemoved() {
int started = Time.currentTime();
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
fooRealm.addClient("bar-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
createClientSession(fooRealm.getClientByClientId("bar-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Create offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
createOfflineSessionIncludeClientSessions(userSession);
resetSession();
RealmManager realmMgr = new RealmManager(session);
ClientManager clientMgr = new ClientManager(realmMgr);
fooRealm = realmMgr.getRealm("foo");
// Assert session was persisted with both clientSessions
UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
UserSessionProviderTest.assertSession(offlineSession, session.users().getUserByUsername("user3", fooRealm), "127.0.0.1", started, started, "foo-app", "bar-app");
// Remove foo-app client
ClientModel client = fooRealm.getClientByClientId("foo-app");
clientMgr.removeClient(fooRealm, client);
resetSession();
realmMgr = new RealmManager(session);
clientMgr = new ClientManager(realmMgr);
fooRealm = realmMgr.getRealm("foo");
// Assert just one bar-app clientSession persisted now
offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
Assert.assertEquals(1, offlineSession.getAuthenticatedClientSessions().size());
Assert.assertEquals("bar-app", offlineSession.getAuthenticatedClientSessions().values().iterator().next().getClient().getClientId());
// Remove bar-app client
client = fooRealm.getClientByClientId("bar-app");
clientMgr.removeClient(fooRealm, client);
resetSession();
// Assert nothing loaded - userSession was removed as well because it was last userSession
realmMgr = new RealmManager(session);
fooRealm = realmMgr.getRealm("foo");
offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
Assert.assertEquals(0, offlineSession.getAuthenticatedClientSessions().size());
// Cleanup
realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
}
@Test
public void testOnUserRemoved() {
int started = Time.currentTime();
RealmModel fooRealm = session.realms().createRealm("foo", "foo");
fooRealm.addClient("foo-app");
session.users().addUser(fooRealm, "user3");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), fooRealm, session.users().getUserByUsername("user3", fooRealm), "user3", "127.0.0.1", "form", true, null, null);
AuthenticatedClientSessionModel clientSession = createClientSession(fooRealm.getClientByClientId("foo-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
resetSession();
// Create offline session
fooRealm = session.realms().getRealm("foo");
userSession = session.sessions().getUserSession(fooRealm, userSession.getId());
createOfflineSessionIncludeClientSessions(userSession);
resetSession();
RealmManager realmMgr = new RealmManager(session);
fooRealm = realmMgr.getRealm("foo");
UserModel user3 = session.users().getUserByUsername("user3", fooRealm);
// Assert session was persisted with both clientSessions
UserSessionModel offlineSession = session.sessions().getOfflineUserSession(fooRealm, userSession.getId());
UserSessionProviderTest.assertSession(offlineSession, user3, "127.0.0.1", started, started, "foo-app");
// Remove user3
new UserManager(session).removeUser(fooRealm, user3);
resetSession();
// Assert userSession removed as well
Assert.assertNull(session.sessions().getOfflineUserSession(fooRealm, userSession.getId()));
// Cleanup
realmMgr = new RealmManager(session);
realmMgr.removeRealm(realmMgr.getRealm("foo"));
}
@Test
public void testExpired() {
// Create some online sessions in infinispan
int started = Time.currentTime();
UserSessionModel[] origSessions = createSessions();
resetSession();
// Key is userSessionId, value is set of client UUIDS
Map<String, Set<String>> offlineSessions = new HashMap<>();
// Persist 3 created userSessions and clientSessions as offline
ClientModel testApp = realm.getClientByClientId("test-app");
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, testApp);
for (UserSessionModel userSession : userSessions) {
offlineSessions.put(userSession.getId(), createOfflineSessionIncludeClientSessions(userSession));
}
resetSession();
// Assert all previously saved offline sessions found
for (Map.Entry<String, Set<String>> entry : offlineSessions.entrySet()) {
UserSessionModel foundSession = sessionManager.findOfflineUserSession(realm, entry.getKey());
Assert.assertEquals(foundSession.getAuthenticatedClientSessions().keySet(), entry.getValue());
}
UserSessionModel session0 = session.sessions().getOfflineUserSession(realm, origSessions[0].getId());
Assert.assertNotNull(session0);
// sessions are in persister too
Assert.assertEquals(3, persister.getUserSessionsCount(true));
// Set lastSessionRefresh to session[0] to 0
session0.setLastSessionRefresh(0);
resetSession();
session.sessions().removeExpired(realm);
resetSession();
// assert session0 not found now
Assert.assertNull(session.sessions().getOfflineUserSession(realm, origSessions[0].getId()));
Assert.assertEquals(2, persister.getUserSessionsCount(true));
// Expire everything and assert nothing found
Time.setOffset(3000000);
try {
session.sessions().removeExpired(realm);
resetSession();
for (String userSessionId : offlineSessions.keySet()) {
Assert.assertNull(sessionManager.findOfflineUserSession(realm, userSessionId));
}
Assert.assertEquals(0, persister.getUserSessionsCount(true));
} finally {
Time.setOffset(0);
}
}
private Set<String> createOfflineSessionIncludeClientSessions(UserSessionModel userSession) {
Set<String> offlineSessions = new HashSet<>();
for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
sessionManager.createOrUpdateOfflineSession(clientSession, userSession);
offlineSessions.add(clientSession.getClient().getId());
}
return offlineSessions;
}
private void resetSession() {
kc.stopSession(session, true);
session = kc.startSession();
realm = session.realms().getRealm("test");
sessionManager = new UserSessionManager(session);
persister = session.getProvider(UserSessionPersisterProvider.class);
}
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(client.getRealm(), client, userSession);
if (userSession != null) clientSession.setUserSession(userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
if (protocolMappers != null) clientSession.setProtocolMappers(protocolMappers);
return clientSession;
}
private UserSessionModel[] createSessions() {
UserSessionModel[] sessions = new UserSessionModel[3];
sessions[0] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
Set<String> roles = new HashSet<String>();
roles.add("one");
roles.add("two");
Set<String> protocolMappers = new HashSet<String>();
protocolMappers.add("mapper-one");
protocolMappers.add("mapper-two");
createClientSession(realm.getClientByClientId("test-app"), sessions[0], "http://redirect", "state", roles, protocolMappers);
createClientSession(realm.getClientByClientId("third-party"), sessions[0], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[1] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[1], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
sessions[2] = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.3", "form", true, null, null);
createClientSession(realm.getClientByClientId("test-app"), sessions[2], "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
return sessions;
}
}

View file

@ -25,7 +25,6 @@ import org.junit.Test;
import org.keycloak.common.util.Time;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserLoginFailureModel;
@ -37,8 +36,8 @@ import org.keycloak.models.UserManager;
import org.keycloak.testsuite.rule.KeycloakRule;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -106,22 +105,36 @@ public class UserSessionProviderTest {
assertEquals(1000, session.sessions().getUserSession(realm, sessions[0].getId()).getLastSessionRefresh());
}
@Test
public void testRestartSession() {
int started = Time.currentTime();
UserSessionModel[] sessions = createSessions();
Time.setOffset(100);
UserSessionModel userSession = session.sessions().getUserSession(realm, sessions[0].getId());
assertSession(userSession, session.users().getUserByUsername("user1", realm), "127.0.0.1", started, started, "test-app", "third-party");
userSession.restartSession(realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.6", "form", true, null, null);
resetSession();
userSession = session.sessions().getUserSession(realm, sessions[0].getId());
assertSession(userSession, session.users().getUserByUsername("user2", realm), "127.0.0.6", started + 100, started + 100);
Time.setOffset(0);
}
@Test
public void testCreateClientSession() {
UserSessionModel[] sessions = createSessions();
List<ClientSessionModel> clientSessions = session.sessions().getUserSession(realm, sessions[0].getId()).getClientSessions();
Map<String, AuthenticatedClientSessionModel> clientSessions = session.sessions().getUserSession(realm, sessions[0].getId()).getAuthenticatedClientSessions();
assertEquals(2, clientSessions.size());
String client1 = realm.getClientByClientId("test-app").getId();
String clientUUID = realm.getClientByClientId("test-app").getId();
ClientSessionModel session1;
if (clientSessions.get(0).getClient().getId().equals(client1)) {
session1 = clientSessions.get(0);
} else {
session1 = clientSessions.get(1);
}
AuthenticatedClientSessionModel session1 = clientSessions.get(clientUUID);
assertEquals(null, session1.getAction());
assertEquals(realm.getClientByClientId("test-app").getClientId(), session1.getClient().getClientId());
@ -140,21 +153,22 @@ public class UserSessionProviderTest {
public void testUpdateClientSession() {
UserSessionModel[] sessions = createSessions();
String id = sessions[0].getClientSessions().get(0).getId();
String userSessionId = sessions[0].getId();
String clientUUID = realm.getClientByClientId("test-app").getId();
ClientSessionModel clientSession = session.sessions().getClientSession(realm, id);
AuthenticatedClientSessionModel clientSession = sessions[0].getAuthenticatedClientSessions().get(clientUUID);
int time = clientSession.getTimestamp();
assertEquals(null, clientSession.getAction());
clientSession.setAction(ClientSessionModel.Action.CODE_TO_TOKEN.name());
clientSession.setAction(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name());
clientSession.setTimestamp(time + 10);
kc.stopSession(session, true);
session = kc.startSession();
ClientSessionModel updated = session.sessions().getClientSession(realm, id);
assertEquals(ClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
AuthenticatedClientSessionModel updated = session.sessions().getUserSession(realm, userSessionId).getAuthenticatedClientSessions().get(clientUUID);
assertEquals(AuthenticatedClientSessionModel.Action.CODE_TO_TOKEN.name(), updated.getAction());
assertEquals(time + 10, updated.getTimestamp());
}
@ -170,17 +184,12 @@ public class UserSessionProviderTest {
public void testRemoveUserSessionsByUser() {
UserSessionModel[] sessions = createSessions();
List<String> clientSessionsRemoved = new LinkedList<String>();
List<String> clientSessionsKept = new LinkedList<String>();
Map<String, Integer> clientSessionsKept = new HashMap<>();
for (UserSessionModel s : sessions) {
s = session.sessions().getUserSession(realm, s.getId());
for (ClientSessionModel c : s.getClientSessions()) {
if (c.getUserSession().getUser().getUsername().equals("user1")) {
clientSessionsRemoved.add(c.getId());
} else {
clientSessionsKept.add(c.getId());
}
if (!s.getUser().getUsername().equals("user1")) {
clientSessionsKept.put(s.getId(), s.getAuthenticatedClientSessions().keySet().size());
}
}
@ -188,13 +197,12 @@ public class UserSessionProviderTest {
resetSession();
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
assertFalse(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
List<UserSessionModel> userSessions = session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm));
assertFalse(userSessions.isEmpty());
for (String c : clientSessionsRemoved) {
assertNull(session.sessions().getClientSession(realm, c));
}
for (String c : clientSessionsKept) {
assertNotNull(session.sessions().getClientSession(realm, c));
Assert.assertEquals(userSessions.size(), clientSessionsKept.size());
for (UserSessionModel userSession : userSessions) {
Assert.assertEquals((int) clientSessionsKept.get(userSession.getId()), userSession.getAuthenticatedClientSessions().size());
}
}
@ -202,76 +210,47 @@ public class UserSessionProviderTest {
public void testRemoveUserSession() {
UserSessionModel userSession = createSessions()[0];
List<String> clientSessionsRemoved = new LinkedList<String>();
for (ClientSessionModel c : userSession.getClientSessions()) {
clientSessionsRemoved.add(c.getId());
}
session.sessions().removeUserSession(realm, userSession);
resetSession();
assertNull(session.sessions().getUserSession(realm, userSession.getId()));
for (String c : clientSessionsRemoved) {
assertNull(session.sessions().getClientSession(realm, c));
}
}
@Test
public void testRemoveUserSessionsByRealm() {
UserSessionModel[] sessions = createSessions();
List<ClientSessionModel> clientSessions = new LinkedList<ClientSessionModel>();
for (UserSessionModel s : sessions) {
clientSessions.addAll(s.getClientSessions());
}
session.sessions().removeUserSessions(realm);
resetSession();
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user1", realm)).isEmpty());
assertTrue(session.sessions().getUserSessions(realm, session.users().getUserByUsername("user2", realm)).isEmpty());
for (ClientSessionModel c : clientSessions) {
assertNull(session.sessions().getClientSession(realm, c.getId()));
}
}
@Test
public void testOnClientRemoved() {
UserSessionModel[] sessions = createSessions();
List<String> clientSessionsRemoved = new LinkedList<String>();
List<String> clientSessionsKept = new LinkedList<String>();
String thirdPartyClientUUID = realm.getClientByClientId("third-party").getId();
Map<String, Set<String>> clientSessionsKept = new HashMap<>();
for (UserSessionModel s : sessions) {
Set<String> clientUUIDS = new HashSet<>(s.getAuthenticatedClientSessions().keySet());
clientUUIDS.remove(thirdPartyClientUUID); // This client will be later removed, hence his clientSessions too
clientSessionsKept.put(s.getId(), clientUUIDS);
}
realm.removeClient(thirdPartyClientUUID);
resetSession();
for (UserSessionModel s : sessions) {
s = session.sessions().getUserSession(realm, s.getId());
for (ClientSessionModel c : s.getClientSessions()) {
if (c.getClient().getClientId().equals("third-party")) {
clientSessionsRemoved.add(c.getId());
} else {
clientSessionsKept.add(c.getId());
}
}
Set<String> clientUUIDS = s.getAuthenticatedClientSessions().keySet();
assertEquals(clientUUIDS, clientSessionsKept.get(s.getId()));
}
session.sessions().onClientRemoved(realm, realm.getClientByClientId("third-party"));
resetSession();
for (String c : clientSessionsRemoved) {
assertNull(session.sessions().getClientSession(realm, c));
}
for (String c : clientSessionsKept) {
assertNotNull(session.sessions().getClientSession(realm, c));
}
session.sessions().onClientRemoved(realm, realm.getClientByClientId("test-app"));
resetSession();
for (String c : clientSessionsRemoved) {
assertNull(session.sessions().getClientSession(realm, c));
}
for (String c : clientSessionsKept) {
assertNull(session.sessions().getClientSession(realm, c));
}
// Revert client
realm.addClient("third-party");
}
@Test
@ -281,11 +260,12 @@ public class UserSessionProviderTest {
try {
Set<String> expired = new HashSet<String>();
Set<String> expiredClientSessions = new HashSet<String>();
Time.setOffset(-(realm.getSsoSessionMaxLifespan() + 1));
expired.add(session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
expiredClientSessions.add(session.sessions().createClientSession(realm, client).getId());
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
expired.add(userSession.getId());
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
Assert.assertEquals(userSession, clientSession.getUserSession());
Time.setOffset(0);
UserSessionModel s = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user2", realm), "user2", "127.0.0.1", "form", true, null, null);
@ -293,15 +273,12 @@ public class UserSessionProviderTest {
s.setLastSessionRefresh(0);
expired.add(s.getId());
ClientSessionModel clSession = session.sessions().createClientSession(realm, client);
clSession.setUserSession(s);
expiredClientSessions.add(clSession.getId());
Set<String> valid = new HashSet<String>();
Set<String> validClientSessions = new HashSet<String>();
valid.add(session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null).getId());
validClientSessions.add(session.sessions().createClientSession(realm, client).getId());
userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.1", "form", true, null, null);
valid.add(userSession.getId());
validClientSessions.add(session.sessions().createClientSession(realm, client, userSession).getId());
resetSession();
@ -311,91 +288,18 @@ public class UserSessionProviderTest {
for (String e : expired) {
assertNull(session.sessions().getUserSession(realm, e));
}
for (String e : expiredClientSessions) {
assertNull(session.sessions().getClientSession(realm, e));
}
for (String v : valid) {
assertNotNull(session.sessions().getUserSession(realm, v));
}
for (String e : validClientSessions) {
assertNotNull(session.sessions().getClientSession(realm, e));
UserSessionModel userSessionLoaded = session.sessions().getUserSession(realm, v);
assertNotNull(userSessionLoaded);
Assert.assertEquals(1, userSessionLoaded.getAuthenticatedClientSessions().size());
Assert.assertNotNull(userSessionLoaded.getAuthenticatedClientSessions().get(client.getId()));
}
} finally {
Time.setOffset(0);
}
}
@Test
public void testExpireDetachedClientSessions() {
try {
realm.setAccessCodeLifespan(10);
realm.setAccessCodeLifespanUserAction(10);
realm.setAccessCodeLifespanLogin(30);
// Login lifespan is largest
String clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
resetSession();
Time.setOffset(25);
session.sessions().removeExpired(realm);
resetSession();
assertNotNull(session.sessions().getClientSession(clientSessionId));
Time.setOffset(35);
session.sessions().removeExpired(realm);
resetSession();
assertNull(session.sessions().getClientSession(clientSessionId));
// User action is largest
realm.setAccessCodeLifespanUserAction(40);
Time.setOffset(0);
clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
resetSession();
Time.setOffset(35);
session.sessions().removeExpired(realm);
resetSession();
assertNotNull(session.sessions().getClientSession(clientSessionId));
Time.setOffset(45);
session.sessions().removeExpired(realm);
resetSession();
assertNull(session.sessions().getClientSession(clientSessionId));
// Access code is largest
realm.setAccessCodeLifespan(50);
Time.setOffset(0);
clientSessionId = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app")).getId();
resetSession();
Time.setOffset(45);
session.sessions().removeExpired(realm);
resetSession();
assertNotNull(session.sessions().getClientSession(clientSessionId));
Time.setOffset(55);
session.sessions().removeExpired(realm);
resetSession();
assertNull(session.sessions().getClientSession(clientSessionId));
} finally {
Time.setOffset(0);
realm.setAccessCodeLifespan(60);
realm.setAccessCodeLifespanUserAction(300);
realm.setAccessCodeLifespanLogin(1800);
}
}
// KEYCLOAK-2508
@Test
public void testRemovingExpiredSession() {
@ -429,12 +333,13 @@ public class UserSessionProviderTest {
for (int i = 0; i < 25; i++) {
Time.setOffset(i);
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0." + i, "form", false, null, null);
ClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"));
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, realm.getClientByClientId("test-app"), userSession);
clientSession.setUserSession(userSession);
clientSession.setRedirectUri("http://redirect");
clientSession.setRoles(new HashSet<String>());
clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, "state");
clientSession.setTimestamp(userSession.getStarted());
userSession.setLastSessionRefresh(userSession.getStarted());
}
} finally {
Time.setOffset(0);
@ -451,19 +356,21 @@ public class UserSessionProviderTest {
@Test
public void testCreateAndGetInSameTransaction() {
ClientModel client = realm.getClientByClientId("test-app");
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
ClientSessionModel clientSession = createClientSession(realm.getClientByClientId("test-app"), userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
AuthenticatedClientSessionModel clientSession = createClientSession(client, userSession, "http://redirect", "state", new HashSet<String>(), new HashSet<String>());
Assert.assertNotNull(session.sessions().getUserSession(realm, userSession.getId()));
Assert.assertNotNull(session.sessions().getClientSession(realm, clientSession.getId()));
UserSessionModel userSessionLoaded = session.sessions().getUserSession(realm, userSession.getId());
AuthenticatedClientSessionModel clientSessionLoaded = userSessionLoaded.getAuthenticatedClientSessions().get(client.getId());
Assert.assertNotNull(userSessionLoaded);
Assert.assertNotNull(clientSessionLoaded);
Assert.assertEquals(userSession.getId(), clientSession.getUserSession().getId());
Assert.assertEquals(1, userSession.getClientSessions().size());
Assert.assertEquals(clientSession.getId(), userSession.getClientSessions().get(0).getId());
Assert.assertEquals(userSession.getId(), clientSessionLoaded.getUserSession().getId());
Assert.assertEquals(1, userSessionLoaded.getAuthenticatedClientSessions().size());
}
@Test
public void testClientLoginSessions() {
public void testAuthenticatedClientSessions() {
UserSessionModel userSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, session.users().getUserByUsername("user1", realm), "user1", "127.0.0.2", "form", true, null, null);
ClientModel client1 = realm.getClientByClientId("test-app");
@ -486,8 +393,8 @@ public class UserSessionProviderTest {
userSession = session.sessions().getUserSession(realm, userSession.getId());
Map<String, AuthenticatedClientSessionModel> clientSessions = userSession.getAuthenticatedClientSessions();
Assert.assertEquals(2, clientSessions.size());
testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
testClientLoginSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1", 100);
testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2", 200);
// Update session1
clientSessions.get(client1.getId()).setAction("foo1-updated");
@ -498,7 +405,7 @@ public class UserSessionProviderTest {
// Ensure updated
userSession = session.sessions().getUserSession(realm, userSession.getId());
clientSessions = userSession.getAuthenticatedClientSessions();
testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
// Rewrite session2
clientSession2 = session.sessions().createClientSession(realm, client2, userSession);
@ -512,8 +419,8 @@ public class UserSessionProviderTest {
userSession = session.sessions().getUserSession(realm, userSession.getId());
clientSessions = userSession.getAuthenticatedClientSessions();
Assert.assertEquals(2, clientSessions.size());
testClientLoginSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
testClientLoginSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
testAuthenticatedClientSession(clientSessions.get(client1.getId()), "test-app", userSession.getId(), "foo1-updated", 100);
testAuthenticatedClientSession(clientSessions.get(client2.getId()), "third-party", userSession.getId(), "foo2-rewrited", 300);
// remove session
clientSession1 = userSession.getAuthenticatedClientSessions().get(client1.getId());
@ -549,11 +456,11 @@ public class UserSessionProviderTest {
}
private void testClientLoginSession(AuthenticatedClientSessionModel clientLoginSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) {
Assert.assertEquals(expectedClientId, clientLoginSession.getClient().getClientId());
Assert.assertEquals(expectedUserSessionId, clientLoginSession.getUserSession().getId());
Assert.assertEquals(expectedAction, clientLoginSession.getAction());
Assert.assertEquals(expectedTimestamp, clientLoginSession.getTimestamp());
private void testAuthenticatedClientSession(AuthenticatedClientSessionModel clientSession, String expectedClientId, String expectedUserSessionId, String expectedAction, int expectedTimestamp) {
Assert.assertEquals(expectedClientId, clientSession.getClient().getClientId());
Assert.assertEquals(expectedUserSessionId, clientSession.getUserSession().getId());
Assert.assertEquals(expectedAction, clientSession.getAction());
Assert.assertEquals(expectedTimestamp, clientSession.getTimestamp());
}
private void assertPaginatedSession(RealmModel realm, ClientModel client, int start, int max, int expectedSize) {
@ -642,9 +549,8 @@ public class UserSessionProviderTest {
assertNotNull(session.sessions().getUserLoginFailure(realm, "user2"));
}
private ClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
ClientSessionModel clientSession = session.sessions().createClientSession(realm, client);
if (userSession != null) clientSession.setUserSession(userSession);
private AuthenticatedClientSessionModel createClientSession(ClientModel client, UserSessionModel userSession, String redirect, String state, Set<String> roles, Set<String> protocolMappers) {
AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, client, userSession);
clientSession.setRedirectUri(redirect);
if (state != null) clientSession.setNote(OIDCLoginProtocol.STATE_PARAM, state);
if (roles != null) clientSession.setRoles(roles);
@ -710,9 +616,14 @@ public class UserSessionProviderTest {
assertTrue(session.getStarted() >= started - 1 && session.getStarted() <= started + 1);
assertTrue(session.getLastSessionRefresh() >= lastRefresh - 1 && session.getLastSessionRefresh() <= lastRefresh + 1);
String[] actualClients = new String[session.getClientSessions().size()];
for (int i = 0; i < actualClients.length; i++) {
actualClients[i] = session.getClientSessions().get(i).getClient().getClientId();
String[] actualClients = new String[session.getAuthenticatedClientSessions().size()];
int i = 0;
for (Map.Entry<String, AuthenticatedClientSessionModel> entry : session.getAuthenticatedClientSessions().entrySet()) {
String clientUUID = entry.getKey();
AuthenticatedClientSessionModel clientSession = entry.getValue();
Assert.assertEquals(clientUUID, clientSession.getClient().getId());
actualClients[i] = clientSession.getClient().getClientId();
i++;
}
Arrays.sort(clients);

View file

@ -0,0 +1,51 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.testsuite.pages;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class LoginExpiredPage extends AbstractPage {
@FindBy(id = "loginRestartLink")
private WebElement loginRestartLink;
@FindBy(id = "loginContinueLink")
private WebElement loginContinueLink;
public void clickLoginRestartLink() {
loginRestartLink.click();
}
public void clickLoginContinueLink() {
loginContinueLink.click();
}
public boolean isCurrent() {
return driver.getTitle().equals("Page has expired");
}
public void open() {
throw new UnsupportedOperationException();
}
}

View file

@ -47,9 +47,9 @@ public abstract class AbstractOfflineCacheCommand extends AbstractCommand {
}
protected String toString(UserSessionEntity userSession) {
int clientSessionsSize = userSession.getClientSessions()==null ? 0 : userSession.getClientSessions().size();
int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
", clientSessions: " + clientSessionsSize;
", authenticatedClientSessions: " + clientSessionsSize;
}
protected abstract void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache);

Some files were not shown because too many files have changed in this diff Show more