Invalidating offline token is not working from client sessions tab
Closes #27275 Signed-off-by: Martin Kanis <mkanis@redhat.com>
This commit is contained in:
parent
0542554984
commit
4154d27941
11 changed files with 55 additions and 36 deletions
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.admin.client.resource;
|
package org.keycloak.admin.client.resource;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.DefaultValue;
|
||||||
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
import org.keycloak.representations.adapters.action.GlobalRequestResult;
|
||||||
import org.keycloak.representations.idm.AdminEventRepresentation;
|
import org.keycloak.representations.idm.AdminEventRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -268,7 +269,7 @@ public interface RealmResource {
|
||||||
|
|
||||||
@Path("sessions/{session}")
|
@Path("sessions/{session}")
|
||||||
@DELETE
|
@DELETE
|
||||||
void deleteSession(@PathParam("session") String sessionId);
|
void deleteSession(@PathParam("session") String sessionId, @DefaultValue("false") @QueryParam("isOffline") boolean offline);
|
||||||
|
|
||||||
@Path("components")
|
@Path("components")
|
||||||
ComponentsResource components();
|
ComponentsResource components();
|
||||||
|
|
|
@ -96,11 +96,11 @@ describe("Sessions test", () => {
|
||||||
sidebarPage.waitForPageLoad();
|
sidebarPage.waitForPageLoad();
|
||||||
|
|
||||||
// Now check that offline session exists (online one has been logged off above)
|
// Now check that offline session exists (online one has been logged off above)
|
||||||
// and that it is not possible to sign it out
|
// and that it is possible to revoke it
|
||||||
commonPage
|
commonPage
|
||||||
.tableUtils()
|
.tableUtils()
|
||||||
.checkRowItemExists(username)
|
.checkRowItemExists(username)
|
||||||
.assertRowItemActionDoesNotExist(username, "Sign out");
|
.selectRowItemAction(username, "Revoke");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -53,24 +53,6 @@ export default class TablePage extends CommonElements {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
assertRowItemActionDoesNotExist(itemName: string, actionItemName: string) {
|
|
||||||
cy.get(
|
|
||||||
(this.#tableInModal ? ".pf-c-modal-box.pf-m-md " : "") +
|
|
||||||
this.#tableRowItem,
|
|
||||||
)
|
|
||||||
.contains(itemName)
|
|
||||||
.parentsUntil("tbody")
|
|
||||||
.then(($tbody) => {
|
|
||||||
if ($tbody.find(".pf-c-dropdown__toggle").length > 0) {
|
|
||||||
$tbody.find(".pf-c-dropdown__toggle").click();
|
|
||||||
cy.get(this.dropdownMenuItem)
|
|
||||||
.contains(actionItemName)
|
|
||||||
.should("not.exist");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#getRowItemAction(itemName: string, actionItemName: string) {
|
#getRowItemAction(itemName: string, actionItemName: string) {
|
||||||
return cy
|
return cy
|
||||||
.get(
|
.get(
|
||||||
|
|
|
@ -146,13 +146,32 @@ export default function SessionsTable({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function onClickRevoke(
|
||||||
|
event: MouseEvent,
|
||||||
|
rowIndex: number,
|
||||||
|
rowData: IRowData,
|
||||||
|
) {
|
||||||
|
const session = rowData.data as UserSessionRepresentation;
|
||||||
|
await adminClient.realms.deleteSession({
|
||||||
|
realm,
|
||||||
|
session: session.id!,
|
||||||
|
isOffline: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
async function onClickSignOut(
|
async function onClickSignOut(
|
||||||
event: MouseEvent,
|
event: MouseEvent,
|
||||||
rowIndex: number,
|
rowIndex: number,
|
||||||
rowData: IRowData,
|
rowData: IRowData,
|
||||||
) {
|
) {
|
||||||
const session = rowData.data as UserSessionRepresentation;
|
const session = rowData.data as UserSessionRepresentation;
|
||||||
await adminClient.realms.deleteSession({ realm, session: session.id! });
|
await adminClient.realms.deleteSession({
|
||||||
|
realm,
|
||||||
|
session: session.id!,
|
||||||
|
isOffline: false,
|
||||||
|
});
|
||||||
|
|
||||||
if (session.userId === whoAmI.getUserId()) {
|
if (session.userId === whoAmI.getUserId()) {
|
||||||
await keycloak.logout({ redirectUri: "" });
|
await keycloak.logout({ redirectUri: "" });
|
||||||
|
@ -185,8 +204,16 @@ export default function SessionsTable({
|
||||||
}
|
}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
actionResolver={(rowData: IRowData) => {
|
actionResolver={(rowData: IRowData) => {
|
||||||
if (rowData.data.type === "OFFLINE") {
|
if (
|
||||||
return [];
|
rowData.data.type === "Offline" ||
|
||||||
|
rowData.data.type === "OFFLINE"
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
title: t("revoke"),
|
||||||
|
onClick: onClickRevoke,
|
||||||
|
} as Action<UserSessionRepresentation>,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|
|
@ -311,12 +311,13 @@ export class Realms extends Resource {
|
||||||
});
|
});
|
||||||
|
|
||||||
public deleteSession = this.makeRequest<
|
public deleteSession = this.makeRequest<
|
||||||
{ realm: string; session: string },
|
{ realm: string; session: string; isOffline: boolean },
|
||||||
void
|
void
|
||||||
>({
|
>({
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
path: "/{realm}/sessions/{session}",
|
path: "/{realm}/sessions/{session}",
|
||||||
urlParamKeys: ["realm", "session"],
|
urlParamKeys: ["realm", "session"],
|
||||||
|
queryParamKeys: ["isOffline"],
|
||||||
});
|
});
|
||||||
|
|
||||||
public pushRevocation = this.makeRequest<
|
public pushRevocation = this.makeRequest<
|
||||||
|
|
|
@ -251,7 +251,8 @@ public class AuthenticationManager {
|
||||||
UserSessionModel userSession, UriInfo uriInfo,
|
UserSessionModel userSession, UriInfo uriInfo,
|
||||||
ClientConnection connection, HttpHeaders headers,
|
ClientConnection connection, HttpHeaders headers,
|
||||||
boolean logoutBroker) {
|
boolean logoutBroker) {
|
||||||
return backchannelLogout(session, realm, userSession, uriInfo, connection, headers, logoutBroker, false);
|
|
||||||
|
return backchannelLogout(session, realm, userSession, uriInfo, connection, headers, logoutBroker, userSession == null ? false : userSession.isOffline());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,6 +30,8 @@ import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import jakarta.enterprise.inject.Default;
|
||||||
|
import jakarta.ws.rs.DefaultValue;
|
||||||
import org.eclipse.microprofile.openapi.annotations.Operation;
|
import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||||
|
@ -625,14 +627,19 @@ public class RealmAdminResource {
|
||||||
@DELETE
|
@DELETE
|
||||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.REALMS_ADMIN)
|
@Tag(name = KeycloakOpenAPI.Admin.Tags.REALMS_ADMIN)
|
||||||
@Operation( summary = "Remove a specific user session.", description = "Any client that has an admin url will also be told to invalidate this particular session.")
|
@Operation( summary = "Remove a specific user session.", description = "Any client that has an admin url will also be told to invalidate this particular session.")
|
||||||
public void deleteSession(@PathParam("session") String sessionId) {
|
public void deleteSession(@PathParam("session") String sessionId, @DefaultValue("false") @QueryParam("isOffline") boolean offline) {
|
||||||
auth.users().requireManage();
|
auth.users().requireManage();
|
||||||
|
|
||||||
UserSessionModel userSession = session.sessions().getUserSession(realm, sessionId);
|
UserSessionModel userSession = offline ? session.sessions().getOfflineUserSession(realm, sessionId) : session.sessions().getUserSession(realm, sessionId);
|
||||||
if (userSession == null) throw new NotFoundException("Sesssion not found");
|
if (userSession == null) {
|
||||||
AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), connection, headers, true);
|
throw new NotFoundException("Sesssion not found");
|
||||||
adminEvent.operation(OperationType.DELETE).resource(ResourceType.USER_SESSION).resourcePath(session.getContext().getUri()).success();
|
}
|
||||||
|
|
||||||
|
AuthenticationManager.backchannelLogout(session, realm, userSession, session.getContext().getUri(), connection, headers, true);
|
||||||
|
|
||||||
|
Map<String, Object> eventRep = new HashMap<>();
|
||||||
|
eventRep.put("offline", offline);
|
||||||
|
adminEvent.operation(OperationType.DELETE).resource(ResourceType.USER_SESSION).resourcePath(session.getContext().getUri()).representation(eventRep).success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -336,7 +336,7 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
}, Resource.REALM, true);
|
}, Resource.REALM, true);
|
||||||
invoke(new Invocation() {
|
invoke(new Invocation() {
|
||||||
public void invoke(RealmResource realm) {
|
public void invoke(RealmResource realm) {
|
||||||
realm.deleteSession("nosuch");
|
realm.deleteSession("nosuch", false);
|
||||||
}
|
}
|
||||||
}, Resource.USER, true);
|
}, Resource.USER, true);
|
||||||
invoke(new Invocation() {
|
invoke(new Invocation() {
|
||||||
|
|
|
@ -947,10 +947,10 @@ public class RealmTest extends AbstractAdminTest {
|
||||||
EventRepresentation event = events.poll();
|
EventRepresentation event = events.poll();
|
||||||
assertNotNull(event);
|
assertNotNull(event);
|
||||||
|
|
||||||
realm.deleteSession(event.getSessionId());
|
realm.deleteSession(event.getSessionId(), false);
|
||||||
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.deleteSessionPath(event.getSessionId()), ResourceType.USER_SESSION);
|
assertAdminEvents.assertEvent(realmId, OperationType.DELETE, AdminEventPaths.deleteSessionPath(event.getSessionId()), ResourceType.USER_SESSION);
|
||||||
try {
|
try {
|
||||||
realm.deleteSession(event.getSessionId());
|
realm.deleteSession(event.getSessionId(), false);
|
||||||
fail("Expected 404");
|
fail("Expected 404");
|
||||||
} catch (NotFoundException e) {
|
} catch (NotFoundException e) {
|
||||||
// Expected
|
// Expected
|
||||||
|
|
|
@ -425,7 +425,7 @@ public class SessionExpirationCrossDCTest extends AbstractAdminCrossDCTest {
|
||||||
// Logout single session of user first
|
// Logout single session of user first
|
||||||
UserResource user = ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test");
|
UserResource user = ApiUtil.findUserByUsernameId(getAdminClient().realm(REALM_NAME), "login-test");
|
||||||
UserSessionRepresentation userSession = user.getUserSessions().get(0);
|
UserSessionRepresentation userSession = user.getUserSessions().get(0);
|
||||||
getAdminClient().realm(REALM_NAME).deleteSession(userSession.getId());
|
getAdminClient().realm(REALM_NAME).deleteSession(userSession.getId(), false);
|
||||||
|
|
||||||
// Just one session expired.
|
// Just one session expired.
|
||||||
assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
assertStatisticsExpected("After logout single session", InfinispanConnectionProvider.USER_SESSION_CACHE_NAME, InfinispanConnectionProvider.CLIENT_SESSION_CACHE_NAME,
|
||||||
|
|
|
@ -123,7 +123,7 @@ public class UserInfoEndpointCorsTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
// remove the session in keycloak
|
// remove the session in keycloak
|
||||||
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
|
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
|
||||||
adminClient.realm("test").deleteSession(accessToken.getSessionState());
|
adminClient.realm("test").deleteSession(accessToken.getSessionState(), false);
|
||||||
|
|
||||||
try (ResteasyClient resteasyClient = AdminClientUtil.createResteasyClient()) {
|
try (ResteasyClient resteasyClient = AdminClientUtil.createResteasyClient()) {
|
||||||
WebTarget userInfoTarget = UserInfoClientUtil.getUserInfoWebTarget(resteasyClient);
|
WebTarget userInfoTarget = UserInfoClientUtil.getUserInfoWebTarget(resteasyClient);
|
||||||
|
|
Loading…
Reference in a new issue