Added `delete-account` sub-module waiting for the official support in KC upcoming version. Theme resources are included in the module and are to moved in the specific theme project/repository when tested in DEV.
This commit is contained in:
parent
a0bf4ee541
commit
3914ca5194
|
@ -0,0 +1,30 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.gcube</groupId>
|
||||||
|
<artifactId>keycloak-d4science-spi-parent</artifactId>
|
||||||
|
<version>0.2.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>delete-account</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.gcube.common</groupId>
|
||||||
|
<artifactId>event-publisher-library</artifactId>
|
||||||
|
<version>[1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT)</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.gcube</groupId>
|
||||||
|
<artifactId>event-listener-provider</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.gcube.keycloak.account;
|
||||||
|
|
||||||
|
import org.gcube.event.publisher.Event;
|
||||||
|
import org.gcube.keycloak.event.KeycloakEvent;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
|
||||||
|
public class DeleteAccountEvent extends Event {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -4519708230339210727L;
|
||||||
|
|
||||||
|
public static final String NAME = "delete-user-account";
|
||||||
|
|
||||||
|
public static final String REALM = "realm";
|
||||||
|
public static final String USER_ID = "userid";
|
||||||
|
|
||||||
|
public DeleteAccountEvent(UserModel userModel, RealmModel realmModel) {
|
||||||
|
super(NAME, KeycloakEvent.TYPE, KeycloakEvent.HOST_NAME);
|
||||||
|
setUserId(userModel.getId());
|
||||||
|
setRealm(realmModel.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUserId(String userid) {
|
||||||
|
set(USER_ID, userid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return (String) get(USER_ID);
|
||||||
|
}
|
||||||
|
public void setRealm(String realm) {
|
||||||
|
set(KeycloakEvent.REALM, realm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRealm() {
|
||||||
|
return (String) get(KeycloakEvent.REALM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.gcube.keycloak.account;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.services.resource.RealmResourceProvider;
|
||||||
|
|
||||||
|
public class DeleteAccountRealmResourceProvider implements RealmResourceProvider {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(DeleteAccountRealmResourceProvider.class);
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
|
||||||
|
public DeleteAccountRealmResourceProvider(KeycloakSession session) {
|
||||||
|
logger.info("Created new DeleteAccountRealmResourceProvider object");
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getResource() {
|
||||||
|
return new DeleteAccountResource(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package org.gcube.keycloak.account;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config.Scope;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.services.resource.RealmResourceProviderFactory;
|
||||||
|
|
||||||
|
public class DeleteAccountRealmResourceProviderFactory implements RealmResourceProviderFactory {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(DeleteAccountRealmResourceProviderFactory.class);
|
||||||
|
|
||||||
|
public static final String ID = "delete-account";
|
||||||
|
|
||||||
|
public DeleteAccountRealmResourceProviderFactory() {
|
||||||
|
logger.info("Created new DeleteAccountRealmResourceProviderFactory object");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeleteAccountRealmResourceProvider create(KeycloakSession session) {
|
||||||
|
return new DeleteAccountRealmResourceProvider(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.gcube.keycloak.account;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import javax.ws.rs.ForbiddenException;
|
||||||
|
import javax.ws.rs.FormParam;
|
||||||
|
import javax.ws.rs.NotAuthorizedException;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import org.gcube.keycloak.event.OrchestratorEventPublisherProviderFactory;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
|
||||||
|
public class DeleteAccountResource {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(DeleteAccountResource.class);
|
||||||
|
|
||||||
|
public static final String STATE_CHECKER_ATTRIBUTE = "state_checker";
|
||||||
|
public static final String STATE_CHECKER_PARAMETER = "stateChecker";
|
||||||
|
|
||||||
|
private final KeycloakSession session;
|
||||||
|
private final AuthenticationManager.AuthResult auth;
|
||||||
|
|
||||||
|
public DeleteAccountResource(KeycloakSession session) {
|
||||||
|
logger.info("Created new DeleteAccountResource object");
|
||||||
|
this.session = session;
|
||||||
|
auth = new AppAuthManager().authenticateIdentityCookie(session, session.getContext().getRealm());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NoCache
|
||||||
|
@POST()
|
||||||
|
@Path("delete")
|
||||||
|
public Response performDeleteAccount(@FormParam(STATE_CHECKER_PARAMETER) String stateChecker) {
|
||||||
|
if (auth == null) {
|
||||||
|
logger.debug("Invoked DELETE without authorization");
|
||||||
|
throw new NotAuthorizedException("Cookie");
|
||||||
|
}
|
||||||
|
String requiredStateChecker = session.getAttribute(STATE_CHECKER_ATTRIBUTE, String.class);
|
||||||
|
if (!requiredStateChecker.equals(stateChecker)) {
|
||||||
|
throw new ForbiddenException("State");
|
||||||
|
}
|
||||||
|
logger.info("Invoked perform delete account");
|
||||||
|
RealmModel realm = auth.getSession().getRealm();
|
||||||
|
logger.debug("Sending delete account event to the orchestrator");
|
||||||
|
new OrchestratorEventPublisherProviderFactory().create(session)
|
||||||
|
.publish(new DeleteAccountEvent(auth.getUser(), realm));
|
||||||
|
|
||||||
|
logger.debug("Forcing logout from all active sessions");
|
||||||
|
session.sessions().removeUserSessions(realm);
|
||||||
|
|
||||||
|
|
||||||
|
URI auccountLoginUri = session.getContext().getUri().getBaseUriBuilder().path(RealmsResource.class)
|
||||||
|
.path("{realm}/account").build(realm.getName());
|
||||||
|
|
||||||
|
logger.debugf("Finally redirecting to the account form login: %s", auccountLoginUri);
|
||||||
|
return Response.status(302).location(auccountLoginUri).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"themes": [
|
||||||
|
{
|
||||||
|
"name": "delete-account",
|
||||||
|
"types": [
|
||||||
|
"account"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.gcube.keycloak.account.DeleteAccountRealmResourceProviderFactory
|
|
@ -0,0 +1,91 @@
|
||||||
|
<#import "template.ftl" as layout>
|
||||||
|
<@layout.mainLayout active='account' bodyClass='user'; section>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<h2>${msg("editAccountHtmlTitle")}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 subtitle">
|
||||||
|
<span class="subtitle"><span class="required">*</span> ${msg("requiredFields")}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="${url.accountUrl}" class="form-horizontal" method="post">
|
||||||
|
|
||||||
|
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
|
||||||
|
|
||||||
|
<#if !realm.registrationEmailAsUsername>
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('username','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="username" class="control-label">${msg("username")}</label> <#if realm.editUsernameAllowed><span class="required">*</span></#if>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="username" name="username" <#if !realm.editUsernameAllowed>disabled="disabled"</#if> value="${(account.username!'')}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('email','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="email" class="control-label">${msg("email")}</label> <span class="required">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="email" name="email" autofocus value="${(account.email!'')}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('firstName','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="firstName" class="control-label">${msg("firstName")}</label> <span class="required">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="firstName" name="firstName" value="${(account.firstName!'')}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group ${messagesPerField.printIfExists('lastName','has-error')}">
|
||||||
|
<div class="col-sm-2 col-md-2">
|
||||||
|
<label for="lastName" class="control-label">${msg("lastName")}</label> <span class="required">*</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-10 col-md-10">
|
||||||
|
<input type="text" class="form-control" id="lastName" name="lastName" value="${(account.lastName!'')}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||||
|
<div class="">
|
||||||
|
<#if url.referrerURI??><a href="${url.referrerURI}">${kcSanitize(msg("backToApplication")?no_esc)}</a></#if>
|
||||||
|
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Save">${msg("doSave")}</button>
|
||||||
|
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonDefaultClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="Cancel">${msg("doCancel")}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<h2>${msg("deleteAccountHtmlTitle")}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<#assign deleteUrl = url.accountUrl?replace("^(.*)(/account/?)(\\?(.*))?$", "$1/delete-account/delete?$4", 'r') />
|
||||||
|
<form action="${deleteUrl}" class="form-horizontal" method="post">
|
||||||
|
|
||||||
|
<img src="${url.resourcesPath}/img/delete-user.png" style="max-width:200px;" >
|
||||||
|
<input type="hidden" name="stateChecker" value="${stateChecker}">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||||
|
<div class="">
|
||||||
|
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="${msg("deleteAccountSubmitButton")}">${msg("deleteAccountSubmitButton")}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</@layout.mainLayout>
|
|
@ -0,0 +1,2 @@
|
||||||
|
deleteAccountHtmlTitle=Delete Personal Account
|
||||||
|
deleteAccountSubmitButton=Delete Account
|
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
|
@ -0,0 +1 @@
|
||||||
|
parent=keycloak
|
|
@ -42,6 +42,11 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.gcube</groupId>
|
||||||
|
<artifactId>delete-account</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.gcube</groupId>
|
<groupId>org.gcube</groupId>
|
||||||
<artifactId>event-listener-provider</artifactId>
|
<artifactId>event-listener-provider</artifactId>
|
||||||
|
@ -88,6 +93,13 @@
|
||||||
<bundleFileName>avatar-realm-resource.jar</bundleFileName>
|
<bundleFileName>avatar-realm-resource.jar</bundleFileName>
|
||||||
<bundleDir>/</bundleDir>
|
<bundleDir>/</bundleDir>
|
||||||
</jarModule>
|
</jarModule>
|
||||||
|
<jarModule>
|
||||||
|
<groupId>org.gcube</groupId>
|
||||||
|
<artifactId>delete-account</artifactId>
|
||||||
|
<includeInApplicationXml>true</includeInApplicationXml>
|
||||||
|
<bundleFileName>delete-account.jar</bundleFileName>
|
||||||
|
<bundleDir>/</bundleDir>
|
||||||
|
</jarModule>
|
||||||
<jarModule>
|
<jarModule>
|
||||||
<groupId>org.gcube</groupId>
|
<groupId>org.gcube</groupId>
|
||||||
<artifactId>event-listener-provider</artifactId>
|
<artifactId>event-listener-provider</artifactId>
|
||||||
|
|
|
@ -36,6 +36,19 @@
|
||||||
<module name="org.slf4j" />
|
<module name="org.slf4j" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</sub-deployment>
|
</sub-deployment>
|
||||||
|
<sub-deployment name="delete-account.jar">
|
||||||
|
<dependencies>
|
||||||
|
<module name="javax.servlet.api" />
|
||||||
|
<module name="javax.ws.rs.api" />
|
||||||
|
<module name="org.keycloak.keycloak-core" />
|
||||||
|
<module name="org.keycloak.keycloak-server-spi" />
|
||||||
|
<module name="org.keycloak.keycloak-server-spi-private" />
|
||||||
|
<module name="org.keycloak.keycloak-services" />
|
||||||
|
<module name="org.jboss.logging" />
|
||||||
|
<module name="org.jboss.resteasy.resteasy-jaxrs" />
|
||||||
|
<module name="org.slf4j" />
|
||||||
|
</dependencies>
|
||||||
|
</sub-deployment>
|
||||||
<sub-deployment name="event-listener-provider.jar">
|
<sub-deployment name="event-listener-provider.jar">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<module name="org.keycloak.keycloak-core" />
|
<module name="org.keycloak.keycloak-core" />
|
||||||
|
|
1
pom.xml
1
pom.xml
|
@ -19,6 +19,7 @@
|
||||||
<module>avatar-storage</module>
|
<module>avatar-storage</module>
|
||||||
<module>avatar-realm-resource</module>
|
<module>avatar-realm-resource</module>
|
||||||
<module>avatar-importer</module>
|
<module>avatar-importer</module>
|
||||||
|
<module>delete-account</module>
|
||||||
<module>event-listener-provider</module>
|
<module>event-listener-provider</module>
|
||||||
<module>identity-provider-mapper</module>
|
<module>identity-provider-mapper</module>
|
||||||
<module>ldap-storage-mapper</module>
|
<module>ldap-storage-mapper</module>
|
||||||
|
|
Loading…
Reference in New Issue