Search in sources :

Example 1 with BackchannelLogoutResponse

use of org.keycloak.protocol.oidc.BackchannelLogoutResponse in project keycloak by keycloak.

the class AuthenticationManager method backchannelLogout.

/**
 * @param session
 * @param realm
 * @param userSession
 * @param uriInfo
 * @param connection
 * @param headers
 * @param logoutBroker
 * @param offlineSession
 *
 * @return BackchannelLogoutResponse with logout information
 */
public static BackchannelLogoutResponse backchannelLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers, boolean logoutBroker, boolean offlineSession) {
    BackchannelLogoutResponse backchannelLogoutResponse = new BackchannelLogoutResponse();
    if (userSession == null) {
        backchannelLogoutResponse.setLocalLogoutSucceeded(true);
        return backchannelLogoutResponse;
    }
    UserModel user = userSession.getUser();
    if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
        userSession.setState(UserSessionModel.State.LOGGING_OUT);
    }
    logger.debugv("Logging out: {0} ({1}) offline: {2}", user.getUsername(), userSession.getId(), userSession.isOffline());
    boolean expireUserSessionCookieSucceeded = expireUserSessionCookie(session, userSession, realm, uriInfo, headers, connection);
    final AuthenticationSessionManager asm = new AuthenticationSessionManager(session);
    AuthenticationSessionModel logoutAuthSession = createOrJoinLogoutSession(session, realm, asm, userSession, false);
    boolean userSessionOnlyHasLoggedOutClients = false;
    try {
        backchannelLogoutResponse = backchannelLogoutAll(session, realm, userSession, logoutAuthSession, uriInfo, headers, logoutBroker);
        userSessionOnlyHasLoggedOutClients = checkUserSessionOnlyHasLoggedOutClients(realm, userSession, logoutAuthSession);
    } finally {
        RootAuthenticationSessionModel rootAuthSession = logoutAuthSession.getParentSession();
        rootAuthSession.removeAuthenticationSessionByTabId(logoutAuthSession.getTabId());
    }
    userSession.setState(UserSessionModel.State.LOGGED_OUT);
    if (offlineSession) {
        new UserSessionManager(session).revokeOfflineUserSession(userSession);
        // Check if "online" session still exists and remove it too
        String onlineUserSessionId = userSession.getNote(CORRESPONDING_SESSION_ID);
        UserSessionModel onlineUserSession = (onlineUserSessionId != null) ? session.sessions().getUserSession(realm, onlineUserSessionId) : session.sessions().getUserSession(realm, userSession.getId());
        if (onlineUserSession != null) {
            session.sessions().removeUserSession(realm, onlineUserSession);
        }
    } else {
        session.sessions().removeUserSession(realm, userSession);
    }
    backchannelLogoutResponse.setLocalLogoutSucceeded(expireUserSessionCookieSucceeded && userSessionOnlyHasLoggedOutClients);
    return backchannelLogoutResponse;
}
Also used : UserModel(org.keycloak.models.UserModel) AuthenticationSessionModel(org.keycloak.sessions.AuthenticationSessionModel) RootAuthenticationSessionModel(org.keycloak.sessions.RootAuthenticationSessionModel) UserSessionModel(org.keycloak.models.UserSessionModel) RootAuthenticationSessionModel(org.keycloak.sessions.RootAuthenticationSessionModel) BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse)

Example 2 with BackchannelLogoutResponse

use of org.keycloak.protocol.oidc.BackchannelLogoutResponse in project keycloak by keycloak.

the class LogoutEndpoint method backchannelLogout.

/**
 * Backchannel logout endpoint implementation for Keycloak, which tries to logout the user from all sessions via
 * POST with a valid LogoutToken.
 *
 * Logout a session via a non-browser invocation. Will be implemented as a backchannel logout based on the
 * specification
 * https://openid.net/specs/openid-connect-backchannel-1_0.html
 *
 * @return
 */
@Path("/backchannel-logout")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response backchannelLogout() {
    MultivaluedMap<String, String> form = request.getDecodedFormParameters();
    event.event(EventType.LOGOUT);
    String encodedLogoutToken = form.getFirst(OAuth2Constants.LOGOUT_TOKEN);
    if (encodedLogoutToken == null) {
        event.error(Errors.INVALID_TOKEN);
        throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, "No logout token", Response.Status.BAD_REQUEST);
    }
    LogoutTokenValidationCode validationCode = tokenManager.verifyLogoutToken(session, realm, encodedLogoutToken);
    if (!validationCode.equals(LogoutTokenValidationCode.VALIDATION_SUCCESS)) {
        event.error(Errors.INVALID_TOKEN);
        throw new ErrorResponseException(OAuthErrorException.INVALID_REQUEST, validationCode.getErrorMessage(), Response.Status.BAD_REQUEST);
    }
    LogoutToken logoutToken = tokenManager.toLogoutToken(encodedLogoutToken).get();
    Stream<String> identityProviderAliases = tokenManager.getValidOIDCIdentityProvidersForBackchannelLogout(realm, session, encodedLogoutToken, logoutToken).map(idp -> idp.getConfig().getAlias());
    boolean logoutOfflineSessions = Boolean.parseBoolean(logoutToken.getEvents().getOrDefault(TokenUtil.TOKEN_BACKCHANNEL_LOGOUT_EVENT_REVOKE_OFFLINE_TOKENS, false).toString());
    BackchannelLogoutResponse backchannelLogoutResponse;
    if (logoutToken.getSid() != null) {
        backchannelLogoutResponse = backchannelLogoutWithSessionId(logoutToken.getSid(), identityProviderAliases, logoutOfflineSessions, logoutToken.getSubject());
    } else {
        backchannelLogoutResponse = backchannelLogoutFederatedUserId(logoutToken.getSubject(), identityProviderAliases, logoutOfflineSessions);
    }
    if (!backchannelLogoutResponse.getLocalLogoutSucceeded()) {
        event.error(Errors.LOGOUT_FAILED);
        throw new ErrorResponseException(OAuthErrorException.SERVER_ERROR, "There was an error in the local logout", Response.Status.NOT_IMPLEMENTED);
    }
    session.getProvider(SecurityHeadersProvider.class).options().allowEmptyContentType();
    if (oneOrMoreDownstreamLogoutsFailed(backchannelLogoutResponse)) {
        return Cors.add(request).auth().builder(Response.status(Response.Status.GATEWAY_TIMEOUT).type(MediaType.APPLICATION_JSON_TYPE)).build();
    }
    return Cors.add(request).auth().builder(Response.ok().type(MediaType.APPLICATION_JSON_TYPE)).build();
}
Also used : LogoutTokenValidationCode(org.keycloak.protocol.oidc.LogoutTokenValidationCode) LogoutToken(org.keycloak.representations.LogoutToken) ErrorResponseException(org.keycloak.services.ErrorResponseException) CorsErrorResponseException(org.keycloak.services.CorsErrorResponseException) BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse) Path(javax.ws.rs.Path) POST(javax.ws.rs.POST) Consumes(javax.ws.rs.Consumes)

Example 3 with BackchannelLogoutResponse

use of org.keycloak.protocol.oidc.BackchannelLogoutResponse in project keycloak by keycloak.

the class AuthenticationManager method backchannelLogoutAll.

private static BackchannelLogoutResponse backchannelLogoutAll(KeycloakSession session, RealmModel realm, UserSessionModel userSession, AuthenticationSessionModel logoutAuthSession, UriInfo uriInfo, HttpHeaders headers, boolean logoutBroker) {
    BackchannelLogoutResponse backchannelLogoutResponse = new BackchannelLogoutResponse();
    for (AuthenticatedClientSessionModel clientSession : userSession.getAuthenticatedClientSessions().values()) {
        Response clientSessionLogoutResponse = backchannelLogoutClientSession(session, realm, clientSession, logoutAuthSession, uriInfo, headers);
        String backchannelLogoutUrl = OIDCAdvancedConfigWrapper.fromClientModel(clientSession.getClient()).getBackchannelLogoutUrl();
        BackchannelLogoutResponse.DownStreamBackchannelLogoutResponse downStreamBackchannelLogoutResponse = new BackchannelLogoutResponse.DownStreamBackchannelLogoutResponse();
        downStreamBackchannelLogoutResponse.setWithBackchannelLogoutUrl(backchannelLogoutUrl != null);
        if (clientSessionLogoutResponse != null) {
            downStreamBackchannelLogoutResponse.setResponseCode(clientSessionLogoutResponse.getStatus());
        } else {
            downStreamBackchannelLogoutResponse.setResponseCode(null);
        }
        backchannelLogoutResponse.addClientResponses(downStreamBackchannelLogoutResponse);
    }
    if (logoutBroker) {
        String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER);
        if (brokerId != null) {
            IdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, realm, brokerId);
            try {
                identityProvider.backchannelLogout(session, userSession, uriInfo, realm);
            } catch (Exception e) {
                logger.warn("Exception at broker backchannel logout for broker " + brokerId, e);
                backchannelLogoutResponse.setLocalLogoutSucceeded(false);
            }
        }
    }
    return backchannelLogoutResponse;
}
Also used : BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse) Response(javax.ws.rs.core.Response) AuthenticatedClientSessionModel(org.keycloak.models.AuthenticatedClientSessionModel) IdentityProvider(org.keycloak.broker.provider.IdentityProvider) BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse) ErrorResponseException(org.keycloak.services.ErrorResponseException) AuthenticationFlowException(org.keycloak.authentication.AuthenticationFlowException) ClientPolicyException(org.keycloak.services.clientpolicy.ClientPolicyException) VerificationException(org.keycloak.common.VerificationException) UnsupportedEncodingException(java.io.UnsupportedEncodingException)

Example 4 with BackchannelLogoutResponse

use of org.keycloak.protocol.oidc.BackchannelLogoutResponse in project keycloak by keycloak.

the class LogoutEndpoint method backchannelLogoutWithSessionId.

private BackchannelLogoutResponse backchannelLogoutWithSessionId(String sessionId, Stream<String> identityProviderAliases, boolean logoutOfflineSessions, String federatedUserId) {
    AtomicReference<BackchannelLogoutResponse> backchannelLogoutResponse = new AtomicReference<>(new BackchannelLogoutResponse());
    backchannelLogoutResponse.get().setLocalLogoutSucceeded(true);
    identityProviderAliases.forEach(identityProviderAlias -> {
        UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, identityProviderAlias + "." + sessionId);
        if (logoutOfflineSessions) {
            if (offlineSessionsLazyLoadingEnabled) {
                logoutOfflineUserSessionByBrokerUserId(identityProviderAlias + "." + federatedUserId, identityProviderAlias + "." + sessionId);
            } else {
                logoutOfflineUserSession(identityProviderAlias + "." + sessionId);
            }
        }
        if (userSession != null) {
            backchannelLogoutResponse.set(logoutUserSession(userSession));
        }
    });
    return backchannelLogoutResponse.get();
}
Also used : UserSessionModel(org.keycloak.models.UserSessionModel) AtomicReference(java.util.concurrent.atomic.AtomicReference) BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse)

Example 5 with BackchannelLogoutResponse

use of org.keycloak.protocol.oidc.BackchannelLogoutResponse in project keycloak by keycloak.

the class LogoutEndpoint method backchannelLogoutFederatedUserId.

private BackchannelLogoutResponse backchannelLogoutFederatedUserId(String federatedUserId, Stream<String> identityProviderAliases, boolean logoutOfflineSessions) {
    BackchannelLogoutResponse backchannelLogoutResponse = new BackchannelLogoutResponse();
    backchannelLogoutResponse.setLocalLogoutSucceeded(true);
    identityProviderAliases.forEach(identityProviderAlias -> {
        if (logoutOfflineSessions) {
            logoutOfflineUserSessions(identityProviderAlias + "." + federatedUserId);
        }
        session.sessions().getUserSessionByBrokerUserIdStream(realm, identityProviderAlias + "." + federatedUserId).collect(// collect to avoid concurrent modification as backchannelLogout removes the user sessions.
        Collectors.toList()).forEach(userSession -> {
            BackchannelLogoutResponse userBackchannelLogoutResponse = this.logoutUserSession(userSession);
            backchannelLogoutResponse.setLocalLogoutSucceeded(backchannelLogoutResponse.getLocalLogoutSucceeded() && userBackchannelLogoutResponse.getLocalLogoutSucceeded());
            userBackchannelLogoutResponse.getClientResponses().forEach(backchannelLogoutResponse::addClientResponses);
        });
    });
    return backchannelLogoutResponse;
}
Also used : BackchannelLogoutResponse(org.keycloak.protocol.oidc.BackchannelLogoutResponse)

Aggregations

BackchannelLogoutResponse (org.keycloak.protocol.oidc.BackchannelLogoutResponse)5 UserSessionModel (org.keycloak.models.UserSessionModel)2 ErrorResponseException (org.keycloak.services.ErrorResponseException)2 UnsupportedEncodingException (java.io.UnsupportedEncodingException)1 AtomicReference (java.util.concurrent.atomic.AtomicReference)1 Consumes (javax.ws.rs.Consumes)1 POST (javax.ws.rs.POST)1 Path (javax.ws.rs.Path)1 Response (javax.ws.rs.core.Response)1 AuthenticationFlowException (org.keycloak.authentication.AuthenticationFlowException)1 IdentityProvider (org.keycloak.broker.provider.IdentityProvider)1 VerificationException (org.keycloak.common.VerificationException)1 AuthenticatedClientSessionModel (org.keycloak.models.AuthenticatedClientSessionModel)1 UserModel (org.keycloak.models.UserModel)1 LogoutTokenValidationCode (org.keycloak.protocol.oidc.LogoutTokenValidationCode)1 LogoutToken (org.keycloak.representations.LogoutToken)1 CorsErrorResponseException (org.keycloak.services.CorsErrorResponseException)1 ClientPolicyException (org.keycloak.services.clientpolicy.ClientPolicyException)1 AuthenticationSessionModel (org.keycloak.sessions.AuthenticationSessionModel)1 RootAuthenticationSessionModel (org.keycloak.sessions.RootAuthenticationSessionModel)1