DeleteAccount.java
/*
* 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.authentication.requiredactions;
import java.util.Objects;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.authentication.InitiatedActionSupport;
import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakContext;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserManager;
import org.keycloak.models.UserModel;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.messages.Messages;
public class DeleteAccount implements RequiredActionProvider, RequiredActionFactory {
public static final String PROVIDER_ID = "delete_account";
private static final String TRIGGERED_FROM_AIA = "triggered_from_aia";
private static final Logger logger = Logger.getLogger(DeleteAccount.class);
@Override
public String getDisplayText() {
return "Delete Account";
}
@Override
public void evaluateTriggers(RequiredActionContext context) {
}
@Override
public void requiredActionChallenge(RequiredActionContext context) {
if (!clientHasDeleteAccountRole(context)) {
context.challenge(context.form().setError(Messages.DELETE_ACCOUNT_LACK_PRIVILEDGES).createForm("error.ftl"));
return;
}
context.challenge(context.form().setAttribute(TRIGGERED_FROM_AIA, isCurrentActionTriggeredFromAIA(context)).createForm("delete-account-confirm.ftl"));
}
@Override
public void processAction(RequiredActionContext context) {
KeycloakSession session = context.getSession();
EventBuilder eventBuilder = context.getEvent();
KeycloakContext keycloakContext = session.getContext();
RealmModel realm = keycloakContext.getRealm();
UserModel user = keycloakContext.getAuthenticationSession().getAuthenticatedUser();
try {
if(!clientHasDeleteAccountRole(context)) {
throw new ForbiddenException();
}
boolean removed = new UserManager(session).removeUser(realm, user);
if (removed) {
eventBuilder.event(EventType.DELETE_ACCOUNT)
.client(keycloakContext.getClient())
.user(user)
.detail(Details.USERNAME, user.getUsername())
.success();
cleanSession(context, RequiredActionContext.KcActionStatus.SUCCESS);
context.challenge(context.form()
.setAttribute("messageHeader", "")
.setInfo("userDeletedSuccessfully")
.createForm("info.ftl"));
} else {
eventBuilder.event(EventType.DELETE_ACCOUNT)
.client(keycloakContext.getClient())
.user(user)
.detail(Details.USERNAME, user.getUsername())
.error("User could not be deleted");
cleanSession(context, RequiredActionContext.KcActionStatus.ERROR);
context.failure();
}
} catch (ForbiddenException forbidden) {
logger.error("account client does not have the required roles for user deletion");
eventBuilder.event(EventType.DELETE_ACCOUNT_ERROR)
.client(keycloakContext.getClient())
.user(keycloakContext.getAuthenticationSession().getAuthenticatedUser())
.detail(Details.REASON, "does not have the required roles for user deletion")
.error(Errors.USER_DELETE_ERROR);
//deletingAccountForbidden
context.challenge(context.form().setAttribute(TRIGGERED_FROM_AIA, isCurrentActionTriggeredFromAIA(context)).setError(Messages.DELETE_ACCOUNT_LACK_PRIVILEDGES).createForm("delete-account-confirm.ftl"));
} catch (Exception exception) {
logger.error("unexpected error happened during account deletion", exception);
eventBuilder.event(EventType.DELETE_ACCOUNT_ERROR)
.client(keycloakContext.getClient())
.user(keycloakContext.getAuthenticationSession().getAuthenticatedUser())
.detail(Details.REASON, exception.getMessage())
.error(Errors.USER_DELETE_ERROR);
context.challenge(context.form().setError(Messages.DELETE_ACCOUNT_ERROR).createForm("delete-account-confirm.ftl"));
}
}
private void cleanSession(RequiredActionContext context, RequiredActionContext.KcActionStatus status) {
context.getAuthenticationSession().removeRequiredAction(PROVIDER_ID);
context.getAuthenticationSession().removeAuthNote(AuthenticationProcessor.CURRENT_AUTHENTICATION_EXECUTION);
AuthenticationManager.setKcActionStatus(PROVIDER_ID, status, context.getAuthenticationSession());
}
private boolean clientHasDeleteAccountRole(RequiredActionContext context) {
RoleModel deleteAccountRole = context.getRealm().getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(AccountRoles.DELETE_ACCOUNT);
return deleteAccountRole != null && context.getUser().hasRole(deleteAccountRole);
}
private boolean isCurrentActionTriggeredFromAIA(RequiredActionContext context) {
return Objects.equals(context.getAuthenticationSession().getClientNote(Constants.KC_ACTION), PROVIDER_ID);
}
@Override
public RequiredActionProvider create(KeycloakSession session) {
return this;
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public InitiatedActionSupport initiatedActionSupport() {
return InitiatedActionSupport.SUPPORTED;
}
@Override
public boolean isOneTimeAction() {
return true;
}
@Override
public int getMaxAuthAge() {
return 0;
}
}