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:
Mauro Mugnaini 2020-11-16 23:35:08 +01:00
parent a0bf4ee541
commit 3914ca5194
14 changed files with 333 additions and 0 deletions

30
delete-account/pom.xml Normal file
View File

@ -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>

View File

@ -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);
}
}

View File

@ -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() {
}
}

View File

@ -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() {
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,10 @@
{
"themes": [
{
"name": "delete-account",
"types": [
"account"
]
}
]
}

View File

@ -0,0 +1 @@
org.gcube.keycloak.account.DeleteAccountRealmResourceProviderFactory

View File

@ -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>

View File

@ -0,0 +1,2 @@
deleteAccountHtmlTitle=Delete Personal Account
deleteAccountSubmitButton=Delete Account

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1 @@
parent=keycloak

View File

@ -42,6 +42,11 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.gcube</groupId>
<artifactId>delete-account</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.gcube</groupId>
<artifactId>event-listener-provider</artifactId>
@ -88,6 +93,13 @@
<bundleFileName>avatar-realm-resource.jar</bundleFileName>
<bundleDir>/</bundleDir>
</jarModule>
<jarModule>
<groupId>org.gcube</groupId>
<artifactId>delete-account</artifactId>
<includeInApplicationXml>true</includeInApplicationXml>
<bundleFileName>delete-account.jar</bundleFileName>
<bundleDir>/</bundleDir>
</jarModule>
<jarModule>
<groupId>org.gcube</groupId>
<artifactId>event-listener-provider</artifactId>

View File

@ -36,6 +36,19 @@
<module name="org.slf4j" />
</dependencies>
</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">
<dependencies>
<module name="org.keycloak.keycloak-core" />

View File

@ -19,6 +19,7 @@
<module>avatar-storage</module>
<module>avatar-realm-resource</module>
<module>avatar-importer</module>
<module>delete-account</module>
<module>event-listener-provider</module>
<module>identity-provider-mapper</module>
<module>ldap-storage-mapper</module>