Invalidate user session when associated IdP is missing (previously removed)
Closes #31724 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
parent
731274f39e
commit
7d8ff710c2
2 changed files with 77 additions and 6 deletions
|
@ -17,6 +17,7 @@
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.cookie.CookieProvider;
|
import org.keycloak.cookie.CookieProvider;
|
||||||
import org.keycloak.cookie.CookieType;
|
import org.keycloak.cookie.CookieType;
|
||||||
import org.keycloak.http.HttpRequest;
|
import org.keycloak.http.HttpRequest;
|
||||||
|
@ -178,6 +179,13 @@ public class AuthenticationManager {
|
||||||
logger.debug("No user session");
|
logger.debug("No user session");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (userSession.getNote(Details.IDENTITY_PROVIDER) != null) {
|
||||||
|
String brokerAlias = userSession.getNote(Details.IDENTITY_PROVIDER);
|
||||||
|
if (realm.getIdentityProviderByAlias(brokerAlias) == null) {
|
||||||
|
// associated idp was removed, invalidate the session.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
long currentTime = Time.currentTimeMillis();
|
long currentTime = Time.currentTimeMillis();
|
||||||
long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(userSession.isOffline(),
|
long lifespan = SessionExpirationUtils.calculateUserSessionMaxLifespanTimestamp(userSession.isOffline(),
|
||||||
userSession.isRememberMe(), TimeUnit.SECONDS.toMillis(userSession.getStarted()), realm);
|
userSession.isRememberMe(), TimeUnit.SECONDS.toMillis(userSession.getStarted()), realm);
|
||||||
|
@ -417,12 +425,19 @@ public class AuthenticationManager {
|
||||||
if (logoutBroker) {
|
if (logoutBroker) {
|
||||||
String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
|
String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
|
||||||
if (brokerId != null) {
|
if (brokerId != null) {
|
||||||
IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId);
|
IdentityProvider identityProvider = null;
|
||||||
try {
|
try {
|
||||||
identityProvider.backchannelLogout(session, userSession, uriInfo, realm);
|
identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId);
|
||||||
} catch (Exception e) {
|
} catch (IdentityBrokerException e) {
|
||||||
logger.warn("Exception at broker backchannel logout for broker " + brokerId, e);
|
logger.warn("Skipping backchannel logout for broker " + brokerId + " - not found");
|
||||||
backchannelLogoutResponse.setLocalLogoutSucceeded(false);
|
}
|
||||||
|
if (identityProvider != null) {
|
||||||
|
try {
|
||||||
|
identityProvider.backchannelLogout(session, userSession, uriInfo, realm);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Exception at broker backchannel logout for broker " + brokerId, e);
|
||||||
|
backchannelLogoutResponse.setLocalLogoutSucceeded(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1516,7 +1531,18 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userSession != null) backchannelLogout(session, realm, userSession, uriInfo, connection, headers, true);
|
|
||||||
|
if (userSession != null) {
|
||||||
|
String userSessionId = userSession.getId();
|
||||||
|
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), session.getContext(), newSession -> {
|
||||||
|
RealmModel realmModel = newSession.realms().getRealm(realm.getId());
|
||||||
|
UserSessionModel userSessionModel = newSession.sessions().getUserSession(realmModel, userSessionId);
|
||||||
|
backchannelLogout(newSession, realmModel, userSessionModel, uriInfo, connection, headers, true);
|
||||||
|
});
|
||||||
|
// remove the user session here so that the external persistent session tx becomes aware of the removal that happened
|
||||||
|
// during the backchannel logout.
|
||||||
|
session.sessions().removeUserSession(realm, userSession);
|
||||||
|
}
|
||||||
logger.debug("User session not active");
|
logger.debug("User session not active");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,13 @@ import org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper;
|
||||||
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
|
import org.keycloak.broker.oidc.mappers.UserAttributeMapper;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.crypto.Algorithm;
|
import org.keycloak.crypto.Algorithm;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.IdentityProviderSyncMode;
|
import org.keycloak.models.IdentityProviderSyncMode;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
import org.keycloak.protocol.ProtocolMapperUtils;
|
import org.keycloak.protocol.ProtocolMapperUtils;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
|
@ -40,6 +43,7 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
|
import org.keycloak.testsuite.broker.util.SimpleHttpDefault;
|
||||||
|
@ -49,6 +53,7 @@ import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.WaitUtils;
|
import org.keycloak.testsuite.util.WaitUtils;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -59,6 +64,7 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.hasItems;
|
import static org.hamcrest.Matchers.hasItems;
|
||||||
|
import static org.hamcrest.Matchers.hasSize;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
@ -457,6 +463,45 @@ public final class KcOidcBrokerTest extends AbstractAdvancedBrokerTest {
|
||||||
errorPage.assertCurrent();
|
errorPage.assertCurrent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdpRemovedAfterLoginInvalidatesUserSession() {
|
||||||
|
loginUser();
|
||||||
|
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
|
||||||
|
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||||
|
|
||||||
|
oauth.clientId("broker-app");
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
assertThat(loginPage.isSocialButtonPresent(bc.getIDPAlias()), is(true));
|
||||||
|
logInWithBroker(bc);
|
||||||
|
|
||||||
|
// remove the IDP while the user is logged in
|
||||||
|
adminClient.realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias()).remove();
|
||||||
|
|
||||||
|
// user session should still be active, but checking if it is valid should fail as the associated IDP was removed
|
||||||
|
testingClient.server(bc.consumerRealmName()).run(session -> {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
ClientModel client = session.clients().getClientByClientId(realm, "broker-app");
|
||||||
|
List<UserSessionModel> userSessions = session.sessions().getUserSessionsStream(realm, client).toList();
|
||||||
|
assertThat(userSessions, hasSize(1));
|
||||||
|
UserSessionModel userSession = userSessions.get(0);
|
||||||
|
assertThat(AuthenticationManager.isSessionValid(realm, userSession), is(false));
|
||||||
|
});
|
||||||
|
|
||||||
|
// logout should work even after the IDP was removed
|
||||||
|
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
|
||||||
|
|
||||||
|
// session should have been removed now
|
||||||
|
testingClient.server(bc.consumerRealmName()).run(session -> {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
ClientModel client = session.clients().getClientByClientId(realm, "broker-app");
|
||||||
|
List<UserSessionModel> userSessions = session.sessions().getUserSessionsStream(realm, client).toList();
|
||||||
|
assertThat(userSessions, hasSize(0));
|
||||||
|
});
|
||||||
|
|
||||||
|
loginPage.open(bc.consumerRealmName());
|
||||||
|
assertThat(loginPage.isSocialButtonPresent(bc.getIDPAlias()), is(false));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInvalidAudience() {
|
public void testInvalidAudience() {
|
||||||
loginUser();
|
loginUser();
|
||||||
|
|
Loading…
Reference in a new issue