Support for transient sessions via lightweight users

Part-of: Add support for not importing brokered user into Keycloak database

Closes: #11334
This commit is contained in:
Hynek Mlnarik 2023-10-11 13:51:47 +02:00 committed by Hynek Mlnařík
parent 1ec2a97f92
commit 26328a7c1e
11 changed files with 152 additions and 31 deletions

View file

@ -28,8 +28,11 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity; import org.keycloak.models.sessions.infinispan.entities.AuthenticationSessionEntity;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.sessions.AuthenticationSessionModel; import org.keycloak.sessions.AuthenticationSessionModel;
import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.sessions.RootAuthenticationSessionModel;
import static org.keycloak.models.Constants.SESSION_NOTE_LIGHTWEIGHT_USER;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
/** /**
* NOTE: Calling setter doesn't automatically enlist for update * NOTE: Calling setter doesn't automatically enlist for update
@ -282,12 +285,49 @@ public class AuthenticationSessionAdapter implements AuthenticationSessionModel
@Override @Override
public UserModel getAuthenticatedUser() { public UserModel getAuthenticatedUser() {
return entity.getAuthUserId() == null ? null : session.users().getUserById(getRealm(), entity.getAuthUserId()); } if (entity.getAuthUserId() == null) {
return null;
}
if (getUserSessionNotes().containsKey(SESSION_NOTE_LIGHTWEIGHT_USER)) {
LightweightUserAdapter cachedUser = session.getAttribute("authSession.user." + parent.getId(), LightweightUserAdapter.class);
if (cachedUser != null) {
return cachedUser;
}
LightweightUserAdapter lua = LightweightUserAdapter.fromString(session, parent.getRealm(), getUserSessionNotes().get(SESSION_NOTE_LIGHTWEIGHT_USER));
session.setAttribute("authSession.user." + parent.getId(), lua);
lua.setUpdateHandler(lua1 -> {
if (lua == lua1) { // Ensure there is no conflicting user model, only the latest lightweight user can be used
setUserSessionNote(SESSION_NOTE_LIGHTWEIGHT_USER, lua1.serialize());
}
});
return lua;
} else {
return session.users().getUserById(getRealm(), entity.getAuthUserId());
}
}
@Override @Override
public void setAuthenticatedUser(UserModel user) { public void setAuthenticatedUser(UserModel user) {
if (user == null) entity.setAuthUserId(null); if (user == null) {
else entity.setAuthUserId(user.getId()); entity.setAuthUserId(null);
setUserSessionNote(SESSION_NOTE_LIGHTWEIGHT_USER, null);
} else {
entity.setAuthUserId(user.getId());
if (isLightweightUser(user)) {
LightweightUserAdapter lua = (LightweightUserAdapter) user;
setUserSessionNote(SESSION_NOTE_LIGHTWEIGHT_USER, lua.serialize());
lua.setUpdateHandler(lua1 -> {
if (lua == lua1) { // Ensure there is no conflicting user model, only the latest lightweight user can be used
setUserSessionNote(SESSION_NOTE_LIGHTWEIGHT_USER, lua1.serialize());
}
});
}
}
update(); update();
} }

View file

@ -53,13 +53,13 @@ import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent; import org.keycloak.models.sessions.infinispan.events.RealmRemovedSessionEvent;
import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent; import org.keycloak.models.sessions.infinispan.events.RemoveUserSessionsEvent;
import org.keycloak.models.sessions.infinispan.events.SessionEventsSenderTransaction; import org.keycloak.models.sessions.infinispan.events.SessionEventsSenderTransaction;
import org.keycloak.models.sessions.infinispan.stream.Comparators;
import org.keycloak.models.sessions.infinispan.stream.Mappers; import org.keycloak.models.sessions.infinispan.stream.Mappers;
import org.keycloak.models.sessions.infinispan.stream.SessionPredicate; import org.keycloak.models.sessions.infinispan.stream.SessionPredicate;
import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate; import org.keycloak.models.sessions.infinispan.stream.UserSessionPredicate;
import org.keycloak.models.sessions.infinispan.util.FuturesHelper; import org.keycloak.models.sessions.infinispan.util.FuturesHelper;
import org.keycloak.models.sessions.infinispan.util.InfinispanKeyGenerator; import org.keycloak.models.sessions.infinispan.util.InfinispanKeyGenerator;
import org.keycloak.connections.infinispan.InfinispanUtil; import org.keycloak.connections.infinispan.InfinispanUtil;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.models.sessions.infinispan.util.SessionTimeouts; import org.keycloak.models.sessions.infinispan.util.SessionTimeouts;
import java.io.Serializable; import java.io.Serializable;
@ -83,6 +83,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import static org.keycloak.models.Constants.SESSION_NOTE_LIGHTWEIGHT_USER;
import static org.keycloak.utils.StreamsUtil.paginatedStream; import static org.keycloak.utils.StreamsUtil.paginatedStream;
/** /**
@ -231,7 +232,9 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
SessionUpdateTask<UserSessionEntity> createSessionTask = Tasks.addIfAbsentSync(); SessionUpdateTask<UserSessionEntity> createSessionTask = Tasks.addIfAbsentSync();
sessionTx.addTask(id, createSessionTask, entity, persistenceState); sessionTx.addTask(id, createSessionTask, entity, persistenceState);
UserSessionAdapter adapter = wrap(realm, entity, false); UserSessionAdapter adapter = user instanceof LightweightUserAdapter
? wrap(realm, entity, false, user)
: wrap(realm, entity, false);
adapter.setPersistenceState(persistenceState); adapter.setPersistenceState(persistenceState);
return adapter; return adapter;
} }
@ -736,7 +739,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
userSessionUpdateTx.addTask(sessionEntity.getId(), removeTask); userSessionUpdateTx.addTask(sessionEntity.getId(), removeTask);
} }
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) { UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline, UserModel user) {
InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline); InfinispanChangelogBasedTransaction<String, UserSessionEntity> userSessionUpdateTx = getTransaction(offline);
InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline); InfinispanChangelogBasedTransaction<UUID, AuthenticatedClientSessionEntity> clientSessionUpdateTx = getClientSessionTransaction(offline);
@ -744,12 +747,29 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
return null; return null;
} }
UserModel user = session.users().getUserById(realm, entity.getUser()); return new UserSessionAdapter(session, user, this, userSessionUpdateTx, clientSessionUpdateTx, realm, entity, offline);
}
UserSessionAdapter wrap(RealmModel realm, UserSessionEntity entity, boolean offline) {
UserModel user = null;
if (entity.getNotes().containsKey(SESSION_NOTE_LIGHTWEIGHT_USER)) {
LightweightUserAdapter lua = LightweightUserAdapter.fromString(session, realm, entity.getNotes().get(SESSION_NOTE_LIGHTWEIGHT_USER));
final UserSessionAdapter us = wrap(realm, entity, offline, lua);
lua.setUpdateHandler(lua1 -> {
if (lua == lua1) { // Ensure there is no conflicting user model, only the latest lightweight user can be used
us.setNote(SESSION_NOTE_LIGHTWEIGHT_USER, lua1.serialize());
}
});
return us;
}
user = session.users().getUserById(realm, entity.getUser());
if (user == null) { if (user == null) {
return null; return null;
} }
return new UserSessionAdapter(session, user, this, userSessionUpdateTx, clientSessionUpdateTx, realm, entity, offline); return wrap(realm, entity, offline, user);
} }
AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, boolean offline) { AuthenticatedClientSessionAdapter wrap(UserSessionModel userSession, ClientModel client, AuthenticatedClientSessionEntity entity, boolean offline) {

View file

@ -152,4 +152,6 @@ public final class Constants {
public static final Boolean REALM_ATTR_USERNAME_CASE_SENSITIVE_DEFAULT = Boolean.FALSE; public static final Boolean REALM_ATTR_USERNAME_CASE_SENSITIVE_DEFAULT = Boolean.FALSE;
public static final String REALM_ATTR_USERNAME_CASE_SENSITIVE = "keycloak.username-search.case-sensitive"; public static final String REALM_ATTR_USERNAME_CASE_SENSITIVE = "keycloak.username-search.case-sensitive";
public static final String SESSION_NOTE_LIGHTWEIGHT_USER = "keycloak.userModel";
} }

View file

@ -41,6 +41,7 @@ import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.admin.AuthDetails; import org.keycloak.events.admin.AuthDetails;
import org.keycloak.models.*; import org.keycloak.models.*;
import org.keycloak.models.credential.OTPCredentialModel; import org.keycloak.models.credential.OTPCredentialModel;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.account.CredentialMetadataRepresentation; import org.keycloak.representations.account.CredentialMetadataRepresentation;
import org.keycloak.representations.idm.*; import org.keycloak.representations.idm.*;
@ -63,6 +64,7 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -275,7 +277,7 @@ public class ModelToRepresentation {
rep.setDisableableCredentialTypes(user.credentialManager() rep.setDisableableCredentialTypes(user.credentialManager()
.getDisableableCredentialTypesStream().collect(Collectors.toSet())); .getDisableableCredentialTypesStream().collect(Collectors.toSet()));
rep.setFederationLink(user.getFederationLink()); rep.setFederationLink(user.getFederationLink());
rep.setNotBefore(session.users().getNotBeforeOfUser(realm, user)); rep.setNotBefore(isLightweightUser(user) ? ((LightweightUserAdapter) user).getCreatedTimestamp().intValue() : session.users().getNotBeforeOfUser(realm, user));
rep.setRequiredActions(user.getRequiredActionsStream().collect(Collectors.toList())); rep.setRequiredActions(user.getRequiredActionsStream().collect(Collectors.toList()));
Map<String, List<String>> attributes = user.getAttributes(); Map<String, List<String>> attributes = user.getAttributes();

View file

@ -39,6 +39,7 @@ public class IdentityProviderModel implements Serializable {
public static final String FILTERED_BY_CLAIMS = "filteredByClaim"; public static final String FILTERED_BY_CLAIMS = "filteredByClaim";
public static final String CLAIM_FILTER_NAME = "claimFilterName"; public static final String CLAIM_FILTER_NAME = "claimFilterName";
public static final String CLAIM_FILTER_VALUE = "claimFilterValue"; public static final String CLAIM_FILTER_VALUE = "claimFilterValue";
public static final String DO_NOT_STORE_USERS = "doNotStoreUsers";
private String internalId; private String internalId;
@ -259,6 +260,23 @@ public class IdentityProviderModel implements Serializable {
getConfig().put(HIDE_ON_LOGIN, String.valueOf(hideOnLogin)); getConfig().put(HIDE_ON_LOGIN, String.valueOf(hideOnLogin));
} }
/**
* Returns flag whether the users withing this IdP should be transient, ie. not stored in Keycloak database.
* Default value: {@code false}.
* @return
*/
public boolean isTransientUsers() {
return Boolean.valueOf(getConfig().get(DO_NOT_STORE_USERS));
}
/**
* Configures the IdP to not store users in Keycloak database. Default value: {@code false}.
* @return
*/
public void setTransientUsers(boolean transientUsers) {
getConfig().put(DO_NOT_STORE_USERS, String.valueOf(transientUsers));
}
public boolean isFilteredByClaims() { public boolean isFilteredByClaims() {
return Boolean.valueOf(getConfig().getOrDefault(FILTERED_BY_CLAIMS, Boolean.toString(false))); return Boolean.valueOf(getConfig().getOrDefault(FILTERED_BY_CLAIMS, Boolean.toString(false)));
} }

View file

@ -71,6 +71,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
/** /**
@ -504,6 +505,9 @@ public class AuthenticationProcessor {
@Override @Override
public void attachUserSession(UserSessionModel userSession) { public void attachUserSession(UserSessionModel userSession) {
AuthenticationProcessor.this.userSession = userSession; AuthenticationProcessor.this.userSession = userSession;
if (isLightweightUser(userSession.getUser())) {
AuthenticationProcessor.this.authenticationSession.setAuthenticatedUser(userSession.getUser());
}
} }
@Override @Override

View file

@ -28,12 +28,14 @@ import org.keycloak.models.AuthenticatorConfigModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.services.ServicesLogger; import org.keycloak.services.ServicesLogger;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID;
/** /**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -66,13 +68,23 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
return; return;
} }
ExistingUserInfo duplication = checkExistingUser(context, username, serializedCtx, brokerContext); ExistingUserInfo duplication = brokerContext.getIdpConfig().isTransientUsers() ? null : checkExistingUser(context, username, serializedCtx, brokerContext);
if (duplication == null) { UserModel federatedUser = null;
if (brokerContext.getIdpConfig().isTransientUsers()) {
logger.debugf("Transient brokering requested. Recording user details for account '%s' and from identity provider '%s' .",
username, brokerContext.getIdpConfig().getAlias());
federatedUser = new LightweightUserAdapter(session, brokerContext.getBrokerSessionId());
federatedUser.setUsername(username);
} else if (duplication == null) {
logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .", logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .",
username, brokerContext.getIdpConfig().getAlias()); username, brokerContext.getIdpConfig().getAlias());
UserModel federatedUser = session.users().addUser(realm, username); federatedUser = session.users().addUser(realm, username);
}
if (federatedUser != null) {
federatedUser.setEnabled(true); federatedUser.setEnabled(true);
for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet()) { for (Map.Entry<String, List<String>> attr : serializedCtx.getAttributes().entrySet()) {

View file

@ -45,6 +45,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory; import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint.TokenExchangeSamlProtocol; import org.keycloak.protocol.oidc.endpoints.TokenEndpoint.TokenExchangeSamlProtocol;
@ -85,6 +86,7 @@ import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.util.UUID;
/** /**
* Default token exchange implementation * Default token exchange implementation
@ -535,12 +537,15 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
target.preprocessFederatedIdentity(session, realm, mapper, context); target.preprocessFederatedIdentity(session, realm, mapper, context);
} }
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(), UserModel user = null;
context.getUsername(), context.getToken()); if (! context.getIdpConfig().isTransientUsers()) {
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(),
context.getUsername(), context.getToken());
UserModel user = this.session.users().getUserByFederatedIdentity(realm, federatedIdentityModel); user = this.session.users().getUserByFederatedIdentity(realm, federatedIdentityModel);
}
if (user == null) { if (user == null || context.getIdpConfig().isTransientUsers()) {
logger.debugf("Federated user not found for provider '%s' and broker username '%s'.", providerId, context.getUsername()); logger.debugf("Federated user not found for provider '%s' and broker username '%s'.", providerId, context.getUsername());
@ -570,17 +575,22 @@ public class DefaultTokenExchangeProvider implements TokenExchangeProvider {
throw new CorsErrorResponseException(cors, Errors.INVALID_TOKEN, "User already exists", Response.Status.BAD_REQUEST); throw new CorsErrorResponseException(cors, Errors.INVALID_TOKEN, "User already exists", Response.Status.BAD_REQUEST);
} }
if (context.getIdpConfig().isTransientUsers()) {
user = session.users().addUser(realm, username); user = new LightweightUserAdapter(session, UUID.randomUUID().toString());
} else {
user = session.users().addUser(realm, username);
}
user.setEnabled(true); user.setEnabled(true);
user.setEmail(context.getEmail()); user.setEmail(context.getEmail());
user.setFirstName(context.getFirstName()); user.setFirstName(context.getFirstName());
user.setLastName(context.getLastName()); user.setLastName(context.getLastName());
federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(), if (! context.getIdpConfig().isTransientUsers()) {
context.getUsername(), context.getToken()); FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
session.users().addFederatedIdentity(realm, user, federatedIdentityModel); context.getUsername(), context.getToken());
session.users().addFederatedIdentity(realm, user, federatedIdentityModel);
}
context.getIdp().importNewUser(session, realm, user, context); context.getIdp().importNewUser(session, realm, user, context);

View file

@ -56,6 +56,7 @@ import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.UserSessionProvider; import org.keycloak.models.UserSessionProvider;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.SessionExpirationUtils; import org.keycloak.models.utils.SessionExpirationUtils;
import org.keycloak.models.utils.RoleUtils; import org.keycloak.models.utils.RoleUtils;
@ -112,6 +113,7 @@ import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
import static org.keycloak.representations.IDToken.NONCE; import static org.keycloak.representations.IDToken.NONCE;
import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification; import static org.keycloak.utils.LockObjectsForModification.lockUserSessionsForModification;
@ -1234,8 +1236,11 @@ public class TokenManager {
int notBefore = realm.getNotBefore(); int notBefore = realm.getNotBefore();
if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore(); if (client.getNotBefore() > notBefore) notBefore = client.getNotBefore();
int userNotBefore = session.users().getNotBeforeOfUser(realm, userSession.getUser()); final UserModel user = userSession.getUser();
if (userNotBefore > notBefore) notBefore = userNotBefore; if (! isLightweightUser(user)) {
int userNotBefore = session.users().getNotBeforeOfUser(realm, user);
if (userNotBefore > notBefore) notBefore = userNotBefore;
}
res.setNotBeforePolicy(notBefore); res.setNotBeforePolicy(notBefore);
res = transformAccessTokenResponse(session, res, userSession, clientSessionCtx); res = transformAccessTokenResponse(session, res, userSession, clientSessionCtx);
@ -1307,7 +1312,9 @@ public class TokenManager {
} }
public static NotBeforeCheck forModel(KeycloakSession session, RealmModel realmModel, UserModel userModel) { public static NotBeforeCheck forModel(KeycloakSession session, RealmModel realmModel, UserModel userModel) {
return new NotBeforeCheck(session.users().getNotBeforeOfUser(realmModel, userModel)); return isLightweightUser(userModel)
? new NotBeforeCheck(((LightweightUserAdapter) userModel).getCreatedTimestamp().intValue())
: new NotBeforeCheck(session.users().getNotBeforeOfUser(realmModel, userModel));
} }
} }

View file

@ -111,6 +111,7 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue; import static org.keycloak.common.util.ServerCookie.SameSiteAttributeValue;
import static org.keycloak.models.light.LightweightUserAdapter.isLightweightUser;
import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID; import static org.keycloak.models.UserSessionModel.CORRESPONDING_SESSION_ID;
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow; import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.isOAuth2DeviceVerificationFlow;
import static org.keycloak.services.util.CookieHelper.getCookie; import static org.keycloak.services.util.CookieHelper.getCookie;
@ -1542,10 +1543,12 @@ public class AuthenticationManager {
return false; return false;
} }
int userNotBefore = session.users().getNotBeforeOfUser(realm, user); if (! isLightweightUser(user)) {
if (token.getIssuedAt() < userNotBefore) { int userNotBefore = session.users().getNotBeforeOfUser(realm, user);
logger.debug("User notBefore newer than token"); if (token.getIssuedAt() < userNotBefore) {
return false; logger.debug("User notBefore newer than token");
return false;
}
} }
return true; return true;

View file

@ -60,6 +60,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel; import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.models.light.LightweightUserAdapter;
import org.keycloak.models.utils.AuthenticationFlowResolver; import org.keycloak.models.utils.AuthenticationFlowResolver;
import org.keycloak.models.utils.FormMessage; import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
@ -686,9 +687,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
} }
// Add federated identity link here // Add federated identity link here
FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(), if (! (federatedUser instanceof LightweightUserAdapter)) {
context.getUsername(), context.getToken()); FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(context.getIdpConfig().getAlias(), context.getId(),
session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel); context.getUsername(), context.getToken());
session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel);
}
String isRegisteredNewUser = authSession.getAuthNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER); String isRegisteredNewUser = authSession.getAuthNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER);