diff --git a/delete-account/pom.xml b/delete-account/pom.xml new file mode 100644 index 0000000..35a50d5 --- /dev/null +++ b/delete-account/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + org.gcube + keycloak-d4science-spi-parent + 0.2.0-SNAPSHOT + + + delete-account + jar + + + + org.gcube.common + event-publisher-library + [1.0.0-SNAPSHOT, 2.0.0-SNAPSHOT) + + + org.gcube + event-listener-provider + ${project.version} + provided + + + + \ No newline at end of file diff --git a/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountEvent.java b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountEvent.java new file mode 100644 index 0000000..7bdc294 --- /dev/null +++ b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountEvent.java @@ -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); + } + +} diff --git a/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountRealmResourceProvider.java b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountRealmResourceProvider.java new file mode 100644 index 0000000..ba4903c --- /dev/null +++ b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountRealmResourceProvider.java @@ -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() { + } + +} diff --git a/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountRealmResourceProviderFactory.java b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountRealmResourceProviderFactory.java new file mode 100644 index 0000000..fb9c858 --- /dev/null +++ b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountRealmResourceProviderFactory.java @@ -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() { + } + +} diff --git a/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java new file mode 100644 index 0000000..79c517b --- /dev/null +++ b/delete-account/src/main/java/org/gcube/keycloak/account/DeleteAccountResource.java @@ -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(); + } + +} diff --git a/delete-account/src/main/resources/META-INF/keycloak-themes.json b/delete-account/src/main/resources/META-INF/keycloak-themes.json new file mode 100644 index 0000000..9014155 --- /dev/null +++ b/delete-account/src/main/resources/META-INF/keycloak-themes.json @@ -0,0 +1,10 @@ +{ + "themes": [ + { + "name": "delete-account", + "types": [ + "account" + ] + } + ] +} \ No newline at end of file diff --git a/delete-account/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory b/delete-account/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory new file mode 100644 index 0000000..5709301 --- /dev/null +++ b/delete-account/src/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory @@ -0,0 +1 @@ +org.gcube.keycloak.account.DeleteAccountRealmResourceProviderFactory \ No newline at end of file diff --git a/delete-account/src/main/resources/theme/delete-account/account/account.ftl b/delete-account/src/main/resources/theme/delete-account/account/account.ftl new file mode 100644 index 0000000..cb30171 --- /dev/null +++ b/delete-account/src/main/resources/theme/delete-account/account/account.ftl @@ -0,0 +1,91 @@ +<#import "template.ftl" as layout> +<@layout.mainLayout active='account' bodyClass='user'; section> + +
+
+

${msg("editAccountHtmlTitle")}

+
+
+ * ${msg("requiredFields")} +
+
+ +
+ + + + <#if !realm.registrationEmailAsUsername> +
+
+ <#if realm.editUsernameAllowed>* +
+ +
+ disabled="disabled" value="${(account.username!'')}"/> +
+
+ + +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+ * +
+ +
+ +
+
+ +
+
+
+ <#if url.referrerURI??>${kcSanitize(msg("backToApplication")?no_esc)} + + +
+
+
+
+ +
+
+

${msg("deleteAccountHtmlTitle")}

+
+
+ + <#assign deleteUrl = url.accountUrl?replace("^(.*)(/account/?)(\\?(.*))?$", "$1/delete-account/delete?$4", 'r') /> +
+ + + + +
+
+
+ +
+
+
+
+ + \ No newline at end of file diff --git a/delete-account/src/main/resources/theme/delete-account/account/messages/messages_en.properties b/delete-account/src/main/resources/theme/delete-account/account/messages/messages_en.properties new file mode 100644 index 0000000..7579466 --- /dev/null +++ b/delete-account/src/main/resources/theme/delete-account/account/messages/messages_en.properties @@ -0,0 +1,2 @@ +deleteAccountHtmlTitle=Delete Personal Account +deleteAccountSubmitButton=Delete Account \ No newline at end of file diff --git a/delete-account/src/main/resources/theme/delete-account/account/resources/img/delete-user.png b/delete-account/src/main/resources/theme/delete-account/account/resources/img/delete-user.png new file mode 100644 index 0000000..d6e73ed Binary files /dev/null and b/delete-account/src/main/resources/theme/delete-account/account/resources/img/delete-user.png differ diff --git a/delete-account/src/main/resources/theme/delete-account/account/theme.properties b/delete-account/src/main/resources/theme/delete-account/account/theme.properties new file mode 100644 index 0000000..512d633 --- /dev/null +++ b/delete-account/src/main/resources/theme/delete-account/account/theme.properties @@ -0,0 +1 @@ +parent=keycloak \ No newline at end of file diff --git a/keycloak-d4science-bundle/pom.xml b/keycloak-d4science-bundle/pom.xml index ad0111c..199a24d 100644 --- a/keycloak-d4science-bundle/pom.xml +++ b/keycloak-d4science-bundle/pom.xml @@ -42,6 +42,11 @@ ${project.version} provided + + org.gcube + delete-account + ${project.version} + org.gcube event-listener-provider @@ -88,6 +93,13 @@ avatar-realm-resource.jar / + + org.gcube + delete-account + true + delete-account.jar + / + org.gcube event-listener-provider diff --git a/keycloak-d4science-bundle/src/main/application/META-INF/jboss-deployment-structure.xml b/keycloak-d4science-bundle/src/main/application/META-INF/jboss-deployment-structure.xml index 97d242d..2a34643 100644 --- a/keycloak-d4science-bundle/src/main/application/META-INF/jboss-deployment-structure.xml +++ b/keycloak-d4science-bundle/src/main/application/META-INF/jboss-deployment-structure.xml @@ -36,6 +36,19 @@ + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index e539645..03e20ce 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,7 @@ avatar-storage avatar-realm-resource avatar-importer + delete-account event-listener-provider identity-provider-mapper ldap-storage-mapper