This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new ff440fa5f5 [SYNCOPE-1866] AuthProfile management in Enduser (#1083)
ff440fa5f5 is described below

commit ff440fa5f57949b7dff5effb06e1a6d64ffbd94f
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri May 16 19:10:51 2025 +0200

    [SYNCOPE-1866] AuthProfile management in Enduser (#1083)
---
 .../authprofiles/AuthProfileDirectoryPanel.java    |  43 ----
 .../AuthProfileItemDirectoryPanel.java             |  15 +-
 .../authprofiles/AuthProfileWizardBuilder.java     |   4 +-
 .../client/console/rest/AuthProfileRestClient.java |   4 -
 client/am/enduser/pom.xml                          |  84 +++++++
 .../syncope/client/enduser/AMEnduserContext.java   |  34 +++
 .../syncope/client/enduser/pages/AuthProfile.java  | 221 ++++++++++++++++++
 .../enduser}/rest/AuthProfileRestClient.java       |  35 ++-
 .../syncope/client/enduser/pages/AuthProfile.html  | 257 +++++++++++++++++++++
 .../client/enduser/pages/AuthProfile.properties}   |  31 ++-
 .../enduser/pages/AuthProfile_it.properties}       |  31 ++-
 .../enduser/pages/AuthProfile_ja.properties}       |  31 ++-
 .../enduser/pages/AuthProfile_pt_BR.properties}    |  31 ++-
 .../enduser/pages/AuthProfile_ru.properties}       |  31 ++-
 client/am/pom.xml                                  |   1 +
 .../console/topology/TopologyTogglePanel.java      |   2 +-
 .../commons}/markup/html/form/ConfirmBehavior.java |   6 +-
 .../html/form/IndicatingOnConfirmAjaxLink.java     |   2 +-
 .../syncope/client/console/pages/BasePage.java     |   2 +-
 .../console/panels/DelegationSelectionPanel.java   |   2 +-
 .../wicket/markup/html/form/ActionPanel.java       |   1 +
 client/idrepo/enduser/pom.xml                      |   6 +
 .../client/enduser/panels/ChangePasswordPanel.java |   3 +-
 .../syncope/client/enduser/panels/Sidebar.html     |   2 +-
 .../src/test/resources/enduser-debug.properties    |   4 +-
 .../common/lib/wa/ImpersonationAccount.java        |   8 +-
 .../syncope/common/lib/wa/MfaTrustedDevice.java    |  18 +-
 .../common/lib/wa/WebAuthnDeviceCredential.java    |  10 +-
 .../rest/api/service/AuthProfileSelfService.java   |  72 ++++++
 .../api/service/wa/GoogleMfaAuthTokenService.java  |  16 +-
 .../syncope/core/logic/AuthProfileLogic.java       |  51 +++-
 .../syncope/core/rest/cxf/AMRESTCXFContext.java    |   8 +
 .../cxf/service/AuthProfileSelfServiceImpl.java    |  47 ++++
 .../rest/cxf/service/AuthProfileServiceImpl.java   |  10 +-
 .../console/panels/UserRequestDirectoryPanel.java  |   2 +-
 .../syncope/client/enduser/pages/Flowable.java     |  37 ++-
 .../syncope/client/enduser/pages/Flowable.html     |  17 +-
 .../syncope/fit/console/AnyObjectsITCase.java      |   2 +-
 .../apache/syncope/fit/console/GroupsITCase.java   |   2 +-
 .../apache/syncope/fit/console/UsersITCase.java    |   2 +-
 40 files changed, 954 insertions(+), 231 deletions(-)

diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
index f6e02fe580..fa3dde612d 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileDirectoryPanel.java
@@ -19,7 +19,6 @@
 package org.apache.syncope.client.console.authprofiles;
 
 import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
-import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -39,7 +38,6 @@ import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.Bas
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.ui.commons.Constants;
-import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
 import org.apache.syncope.common.lib.types.AMEntitlement;
@@ -50,15 +48,12 @@ import org.apache.syncope.common.lib.wa.MfaTrustedDevice;
 import org.apache.syncope.common.lib.wa.WebAuthnDeviceCredential;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
-import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
-import org.apache.wicket.extensions.wizard.WizardModel;
 import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.StringResourceModel;
 
@@ -91,9 +86,6 @@ public class AuthProfileDirectoryPanel
         });
         addOuterObject(authProfileModal);
 
-        addNewItemPanelBuilder(new CreateAuthProfileWizardBuilder(pageRef), 
true);
-        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, 
AMEntitlement.AUTH_PROFILE_CREATE);
-
         disableCheckBoxes();
         initResultTable();
     }
@@ -467,39 +459,4 @@ public class AuthProfileDirectoryPanel
             return new CompoundPropertyModel<>(object);
         }
     }
-
-    private class CreateAuthProfileWizardBuilder extends 
AuthProfileWizardBuilder<AuthProfileTO> {
-
-        private static final long serialVersionUID = -2478221092672979490L;
-
-        private class NewAuthProfileStep extends 
AuthProfileWizardBuilder<AuthProfileTO>.Step {
-
-            private static final long serialVersionUID = 6290450377240300418L;
-
-            NewAuthProfileStep(final AuthProfileTO modelObject) {
-                super(modelObject);
-
-                AjaxTextFieldPanel owner = new AjaxTextFieldPanel(
-                        "bean", "owner", new PropertyModel<>(modelObject, 
"owner"));
-                owner.addRequiredLabel();
-                addOrReplace(owner);
-            }
-        }
-
-        CreateAuthProfileWizardBuilder(final PageReference pageRef) {
-            super(new AuthProfileTO(), new StepModel<>(), pageRef);
-        }
-
-        @Override
-        protected WizardModel buildModelSteps(final AuthProfileTO modelObject, 
final WizardModel wizardModel) {
-            wizardModel.add(new NewAuthProfileStep(modelObject));
-            return wizardModel;
-        }
-
-        @Override
-        protected Serializable onApplyInternal(final AuthProfileTO 
modelObject) {
-            restClient.create(modelObject);
-            return modelObject;
-        }
-    }
 }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileItemDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileItemDirectoryPanel.java
index 475d1ad60d..cfa57a04ea 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileItemDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileItemDirectoryPanel.java
@@ -75,8 +75,6 @@ public abstract class AuthProfileItemDirectoryPanel<I extends 
BaseBean>
         enableUtilityButton();
         setFooterVisibility(false);
 
-        addNewItemPanelBuilder(new AuthProfileItemWizardBuilder(pageRef), 
true);
-
         disableCheckBoxes();
         initResultTable();
     }
@@ -90,9 +88,8 @@ public abstract class AuthProfileItemDirectoryPanel<I extends 
BaseBean>
     @Override
     @SuppressWarnings("unchecked")
     public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof ExitEvent) {
-            AjaxRequestTarget target = 
ExitEvent.class.cast(event.getPayload()).getTarget();
-            authProfileModal.close(target);
+        if (event.getPayload() instanceof ExitEvent exitEvent) {
+            authProfileModal.close(exitEvent.getTarget());
         } else if (event.getPayload() instanceof final 
AjaxWizard.EditItemActionEvent<?> payload) {
             payload.getTarget().ifPresent(actionTogglePanel::close);
         }
@@ -194,12 +191,12 @@ public abstract class AuthProfileItemDirectoryPanel<I 
extends BaseBean>
                 List<Serializable> values = (List<Serializable>) 
wrapper.getPropertyValue("scratchCodes");
                 if (values != null) {
                     List<Integer> converted = values.stream().map(value -> {
-                        if (value instanceof Integer) {
-                            return Integer.class.cast(value);
+                        if (value instanceof Integer integer) {
+                            return integer;
                         }
-                        if (value instanceof String) {
+                        if (value instanceof String string) {
                             try {
-                                return Integer.valueOf((String) value);
+                                return Integer.valueOf(string);
                             } catch (NumberFormatException e) {
                                 LOG.error("Could not convert to Integer: {}", 
value, e);
                             }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java
index 08475c4110..41a5ff9958 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java
@@ -29,7 +29,7 @@ import org.apache.wicket.model.Model;
 
 public abstract class AuthProfileWizardBuilder<T extends BaseBean> extends 
BaseAjaxWizardBuilder<T> {
 
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = -723891643398238220L;
 
     protected final StepModel<T> model;
 
@@ -46,7 +46,7 @@ public abstract class AuthProfileWizardBuilder<T extends 
BaseBean> extends BaseA
 
     protected static class StepModel<T extends BaseBean> extends Model<T> {
 
-        private static final long serialVersionUID = 1L;
+        private static final long serialVersionUID = -3300650579312254364L;
 
         private T initialModelObject;
 
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
index d2b062bc11..d6801145ed 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
@@ -38,10 +38,6 @@ public class AuthProfileRestClient extends BaseRestClient {
         return getService(AuthProfileService.class).read(key);
     }
 
-    public void create(final AuthProfileTO authProfile) {
-        getService(AuthProfileService.class).create(authProfile);
-    }
-
     public void update(final AuthProfileTO authProfile) {
         getService(AuthProfileService.class).update(authProfile);
     }
diff --git a/client/am/enduser/pom.xml b/client/am/enduser/pom.xml
new file mode 100644
index 0000000000..2eb52d5089
--- /dev/null
+++ b/client/am/enduser/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<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 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.syncope.client</groupId>
+    <artifactId>syncope-client-am</artifactId>
+    <version>4.0.0-SNAPSHOT</version>
+  </parent>
+
+  <name>Apache Syncope Client AM Enduser</name>
+  <description>Apache Syncope Client AM Enduser</description>
+  <groupId>org.apache.syncope.client.am</groupId>
+  <artifactId>syncope-client-am-enduser</artifactId>
+  <packaging>jar</packaging>
+  
+  <properties>
+    <rootpom.basedir>${basedir}/../../..</rootpom.basedir>
+  </properties>
+  
+  <dependencies>
+    <dependency> 
+      <groupId>jakarta.servlet</groupId> 
+      <artifactId>jakarta.servlet-api</artifactId> 
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.syncope.client.idrepo</groupId>
+      <artifactId>syncope-client-idrepo-enduser</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.syncope.client.am</groupId>
+      <artifactId>syncope-client-am-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+    </plugins>
+
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>true</filtering>
+        <excludes>
+          <exclude>org/apache/syncope/**/*.properties</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <includes>
+          <include>org/apache/syncope/**/*.properties</include>
+        </includes>
+      </resource>
+    </resources>
+  </build>
+</project>
diff --git 
a/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/AMEnduserContext.java
 
b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/AMEnduserContext.java
new file mode 100644
index 0000000000..3b840e520f
--- /dev/null
+++ 
b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/AMEnduserContext.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.syncope.client.enduser;
+
+import org.apache.syncope.client.enduser.rest.AuthProfileRestClient;
+import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration(proxyBeanMethods = false)
+public class AMEnduserContext {
+
+    @ConditionalOnMissingBean
+    @Bean
+    public AuthProfileRestClient authProfileRestClient() {
+        return new AuthProfileRestClient();
+    }
+}
diff --git 
a/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java
 
b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java
new file mode 100644
index 0000000000..8a1a08a020
--- /dev/null
+++ 
b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.syncope.client.enduser.pages;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import org.apache.syncope.client.enduser.rest.AuthProfileRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
+import org.apache.syncope.common.lib.to.AuthProfileTO;
+import org.apache.syncope.common.lib.wa.GoogleMfaAuthAccount;
+import org.apache.syncope.common.lib.wa.GoogleMfaAuthToken;
+import org.apache.syncope.common.lib.wa.ImpersonationAccount;
+import org.apache.syncope.common.lib.wa.MfaTrustedDevice;
+import org.apache.syncope.common.lib.wa.WebAuthnDeviceCredential;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import 
org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigator;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+@ExtPage(label = "Auth Profile", icon = "fa fa-passport", listEntitlement = "")
+public class AuthProfile extends BaseExtPage {
+
+    private static final long serialVersionUID = -3147262161518280928L;
+
+    protected static final String AUTH_PROFILE = "page.authProfile";
+
+    protected static final int ROWS_PER_PAGE = 5;
+
+    @SpringBean
+    protected AuthProfileRestClient restClient;
+
+    public AuthProfile(final PageParameters parameters) {
+        super(parameters, AUTH_PROFILE);
+
+        Optional<AuthProfileTO> authProfile = restClient.read();
+
+        WebMarkupContainer container = new WebMarkupContainer("content");
+        contentWrapper.add(container.setOutputMarkupId(true));
+
+        DataView<ImpersonationAccount> impersonationAccounts = new DataView<>(
+                "impersonationAccounts", new ListDataProvider<>(
+                        
authProfile.map(AuthProfileTO::getImpersonationAccounts).orElseGet(() -> 
List.of()))) {
+
+            private static final long serialVersionUID = 6127875313385810666L;
+
+            @Override
+            public void populateItem(final Item<ImpersonationAccount> item) {
+                item.add(new Label("impersonated", 
item.getModelObject().getImpersonated()));
+                item.add(new IndicatingOnConfirmAjaxLink<>(
+                        "impersonationAccountDelete", 
Constants.CONFIRM_DELETE, true) {
+
+                    private static final long serialVersionUID = 
1632838687547839512L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        authProfile.ifPresent(p -> {
+                            
p.getImpersonationAccounts().remove(item.getModelObject());
+                            restClient.update(p);
+                            target.add(container);
+                        });
+                    }
+                });
+            }
+        };
+        impersonationAccounts.setItemsPerPage(ROWS_PER_PAGE);
+        
container.add(impersonationAccounts.setOutputMarkupPlaceholderTag(true));
+        container.add(new 
AjaxPagingNavigator("impersonationAccountsNavigator", impersonationAccounts));
+
+        DataView<GoogleMfaAuthToken> googleMfaAuthTokens = new DataView<>(
+                "googleMfaAuthTokens", new ListDataProvider<>(
+                        
authProfile.map(AuthProfileTO::getGoogleMfaAuthTokens).orElseGet(() -> 
List.of()))) {
+
+            private static final long serialVersionUID = 6127875313385810666L;
+
+            @Override
+            public void populateItem(final Item<GoogleMfaAuthToken> item) {
+                item.add(new Label("otp", item.getModelObject().getOtp()));
+                item.add(new Label("issueDate", 
item.getModelObject().getIssueDate()));
+                item.add(new IndicatingOnConfirmAjaxLink<>(
+                        "googleMfaAuthTokenDelete", Constants.CONFIRM_DELETE, 
true) {
+
+                    private static final long serialVersionUID = 
1632838687547839512L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        authProfile.ifPresent(p -> {
+                            
p.getGoogleMfaAuthTokens().remove(item.getModelObject());
+                            restClient.update(p);
+                            target.add(container);
+                        });
+                    }
+                });
+            }
+        };
+        googleMfaAuthTokens.setItemsPerPage(ROWS_PER_PAGE);
+        container.add(googleMfaAuthTokens.setOutputMarkupPlaceholderTag(true));
+        container.add(new AjaxPagingNavigator("googleMfaAuthTokensNavigator", 
googleMfaAuthTokens));
+
+        DataView<GoogleMfaAuthAccount> googleMfaAuthAccounts = new DataView<>(
+                "googleMfaAuthAccounts", new ListDataProvider<>(
+                        
authProfile.map(AuthProfileTO::getGoogleMfaAuthAccounts).orElseGet(() -> 
List.of()))) {
+
+            private static final long serialVersionUID = 6127875313385810666L;
+
+            @Override
+            public void populateItem(final Item<GoogleMfaAuthAccount> item) {
+                item.add(new Label("id", item.getModelObject().getId()));
+                item.add(new Label("name", item.getModelObject().getName()));
+                item.add(new Label("secretKey", 
item.getModelObject().getSecretKey()));
+                item.add(new Label("validationCode", 
item.getModelObject().getValidationCode()));
+                item.add(new Label("scratchCodes", 
item.getModelObject().getScratchCodes().stream().
+                        map(String::valueOf).collect(Collectors.joining(", 
"))));
+                item.add(new Label("registrationDate", 
item.getModelObject().getRegistrationDate()));
+                item.add(new Label("source", 
item.getModelObject().getSource()));
+                item.add(new IndicatingOnConfirmAjaxLink<>(
+                        "googleMfaAuthAccountDelete", 
Constants.CONFIRM_DELETE, true) {
+
+                    private static final long serialVersionUID = 
1632838687547839512L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        authProfile.ifPresent(p -> {
+                            
p.getGoogleMfaAuthAccounts().remove(item.getModelObject());
+                            restClient.update(p);
+                            target.add(container);
+                        });
+                    }
+                });
+            }
+        };
+        googleMfaAuthAccounts.setItemsPerPage(ROWS_PER_PAGE);
+        
container.add(googleMfaAuthAccounts.setOutputMarkupPlaceholderTag(true));
+        container.add(new 
AjaxPagingNavigator("googleMfaAuthAccountsNavigator", googleMfaAuthAccounts));
+
+        DataView<MfaTrustedDevice> mfaTrustedDevices = new DataView<>(
+                "mfaTrustedDevices", new ListDataProvider<>(
+                        
authProfile.map(AuthProfileTO::getMfaTrustedDevices).orElseGet(() -> 
List.of()))) {
+
+            private static final long serialVersionUID = 6127875313385810666L;
+
+            @Override
+            public void populateItem(final Item<MfaTrustedDevice> item) {
+                item.add(new Label("id", item.getModelObject().getId()));
+                item.add(new Label("name", item.getModelObject().getName()));
+                item.add(new Label("deviceFingerprint", 
item.getModelObject().getDeviceFingerprint()));
+                item.add(new Label("recordDate", 
item.getModelObject().getRecordDate()));
+                item.add(new Label("expirationDate", 
item.getModelObject().getExpirationDate()));
+                item.add(new IndicatingOnConfirmAjaxLink<>(
+                        "mfaTrustedDeviceDelete", Constants.CONFIRM_DELETE, 
true) {
+
+                    private static final long serialVersionUID = 
1632838687547839512L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        authProfile.ifPresent(p -> {
+                            
p.getMfaTrustedDevices().remove(item.getModelObject());
+                            restClient.update(p);
+                            target.add(container);
+                        });
+                    }
+                });
+            }
+        };
+        mfaTrustedDevices.setItemsPerPage(ROWS_PER_PAGE);
+        container.add(mfaTrustedDevices.setOutputMarkupPlaceholderTag(true));
+        container.add(new AjaxPagingNavigator("mfaTrustedDevicesNavigator", 
mfaTrustedDevices));
+
+        DataView<WebAuthnDeviceCredential> webAuthnDeviceCredentials = new 
DataView<>(
+                "webAuthnDeviceCredentials", new ListDataProvider<>(
+                        
authProfile.map(AuthProfileTO::getWebAuthnDeviceCredentials).orElseGet(() -> 
List.of()))) {
+
+            private static final long serialVersionUID = 6127875313385810666L;
+
+            @Override
+            public void populateItem(final Item<WebAuthnDeviceCredential> 
item) {
+                item.add(new Label("identifier", 
item.getModelObject().getIdentifier()));
+                item.add(new IndicatingOnConfirmAjaxLink<>(
+                        "webAuthnDeviceCredentialDelete", 
Constants.CONFIRM_DELETE, true) {
+
+                    private static final long serialVersionUID = 
1632838687547839512L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        authProfile.ifPresent(p -> {
+                            
p.getWebAuthnDeviceCredentials().remove(item.getModelObject());
+                            restClient.update(p);
+                            target.add(container);
+                        });
+                    }
+                });
+            }
+        };
+        webAuthnDeviceCredentials.setItemsPerPage(ROWS_PER_PAGE);
+        
container.add(webAuthnDeviceCredentials.setOutputMarkupPlaceholderTag(true));
+        container.add(new 
AjaxPagingNavigator("webAuthnDeviceCredentialsNavigator", 
webAuthnDeviceCredentials));
+    }
+}
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
 
b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AuthProfileRestClient.java
similarity index 52%
copy from 
client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
copy to 
client/am/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AuthProfileRestClient.java
index d2b062bc11..413247746f 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/AuthProfileRestClient.java
+++ 
b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AuthProfileRestClient.java
@@ -16,37 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.rest;
+package org.apache.syncope.client.enduser.rest;
 
-import java.util.List;
+import java.util.Optional;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
-import org.apache.syncope.common.rest.api.service.AuthProfileService;
+import org.apache.syncope.common.rest.api.service.AuthProfileSelfService;
 
 public class AuthProfileRestClient extends BaseRestClient {
 
-    private static final long serialVersionUID = -7379778542101161274L;
+    private static final long serialVersionUID = 4139153766778113329L;
 
-    public long count() {
-        return getService(AuthProfileService.class).list(1, 1).getTotalCount();
-    }
-
-    public List<AuthProfileTO> list(final int page, final int size) {
-        return getService(AuthProfileService.class).list(page, 
size).getResult();
-    }
-
-    public AuthProfileTO read(final String key) {
-        return getService(AuthProfileService.class).read(key);
-    }
-
-    public void create(final AuthProfileTO authProfile) {
-        getService(AuthProfileService.class).create(authProfile);
+    public Optional<AuthProfileTO> read() {
+        try {
+            return 
Optional.of(getService(AuthProfileSelfService.class).read());
+        } catch (Exception e) {
+            LOG.debug("While attempting to read the auth profile", e);
+            return Optional.empty();
+        }
     }
 
     public void update(final AuthProfileTO authProfile) {
-        getService(AuthProfileService.class).update(authProfile);
+        getService(AuthProfileSelfService.class).update(authProfile);
     }
 
-    public void delete(final String key) {
-        getService(AuthProfileService.class).delete(key);
+    public void delete() {
+        getService(AuthProfileSelfService.class).delete();
     }
 }
diff --git 
a/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile.html
 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile.html
new file mode 100644
index 0000000000..fdbe3b190a
--- /dev/null
+++ 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile.html
@@ -0,0 +1,257 @@
+<!DOCTYPE html>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"; 
xmlns:wicket="http://wicket.apache.org";>
+  <wicket:extend>
+    <section class="container-fluid" wicket:id="content">
+      <div class="card card-outline">
+        <div class="card-body">
+          <div class="col-md-12">
+            <div class="col-md-6 col-md-offset-3">
+              <div>
+                <h3 class="box-title"></h3>
+              </div>
+              <div class="box-body">
+                <div class="box-header formcard">
+                  <header class="card-container bg-danger">
+                    <label class="form-label card-header-style">
+                      <wicket:message key="impersonation.accounts.title"/>
+                    </label>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <table class="table table-striped table-bordered">
+                            <thead>
+                              <tr>
+                                <th><wicket:message key="impersonated"/></th>
+                                <th></th>
+                              </tr>                              
+                            </thead>
+                            <tbody>
+                              <tr wicket:id="impersonationAccounts">
+                                <td><span wicket:id="impersonated"/></td>
+                                <td style="width: 20px;">
+                                  <a href="#" 
wicket:id="impersonationAccountDelete"><i class="fas fa-trash"></i></a>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="paginator">
+                            <tfoot>
+                              <tr>
+                                <td 
wicket:id="impersonationAccountsNavigator"></td>
+                              </tr>
+                            </tfoot>
+                          </table>                          
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div class="box-body">
+                <div class="box-header formcard">
+                  <header class="card-container bg-danger">
+                    <label class="form-label card-header-style">
+                      <wicket:message key="google.mfa.authtokens.title"/>
+                    </label>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <table class="table table-striped table-bordered">
+                            <thead>
+                              <tr>
+                                <th><wicket:message key="otp"/></th>
+                                <th><wicket:message key="issueDate"/></th>
+                                <th></th>
+                              </tr>                              
+                            </thead>
+                            <tbody>
+                              <tr wicket:id="googleMfaAuthTokens">
+                                <td><span wicket:id="otp"/></td>
+                                <td><span wicket:id="issueDate"/></td>
+                                <td style="width: 20px;">
+                                  <a href="#" 
wicket:id="googleMfaAuthTokenDelete"><i class="fas fa-trash"></i></a>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="paginator">
+                            <tfoot>
+                              <tr>
+                                <td 
wicket:id="googleMfaAuthTokensNavigator"></td>
+                              </tr>
+                            </tfoot>
+                          </table>                          
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div class="box-body">
+                <div class="box-header formcard">
+                  <header class="card-container bg-danger">
+                    <label class="form-label card-header-style">
+                      <wicket:message key="google.mfa.authaccounts.title"/>
+                    </label>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <table class="table table-striped table-bordered">
+                            <thead>
+                              <tr>
+                                <th><wicket:message key="id"/></th>
+                                <th><wicket:message key="name"/></th>
+                                <th><wicket:message key="secretKey"/></th>
+                                <th><wicket:message key="validationCode"/></th>
+                                <th><wicket:message key="scratchCodes"/></th>
+                                <th><wicket:message 
key="registrationDate"/></th>
+                                <th><wicket:message key="source"/></th>
+                                <th></th>
+                              </tr>
+                            </thead>
+                            <tbody>
+                              <tr wicket:id="googleMfaAuthAccounts">
+                                <td><span wicket:id="id"/></td>
+                                <td><span wicket:id="name"/></td>
+                                <td style="word-wrap: anywhere;"><span 
wicket:id="secretKey"/></td>
+                                <td><span wicket:id="validationCode"/></td>
+                                <td><span wicket:id="scratchCodes"/></td>
+                                <td><span wicket:id="registrationDate"/></td>
+                                <td><span wicket:id="source"/></td>
+                                <td style="width: 20px;">
+                                  <a href="#" 
wicket:id="googleMfaAuthAccountDelete"><i class="fas fa-trash"></i></a>
+                                </td>                                
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="paginator">
+                            <tfoot>
+                              <tr>
+                                <td 
wicket:id="googleMfaAuthAccountsNavigator"></td>
+                              </tr>
+                            </tfoot>
+                          </table>                          
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div class="box-body">
+                <div class="box-header formcard">
+                  <header class="card-container bg-danger">
+                    <label class="form-label card-header-style">
+                      <wicket:message key="mfa.trusted.devices.title"/>
+                    </label>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <table class="table table-striped table-bordered">
+                            <thead>
+                              <tr>
+                                <th><wicket:message key="id"/></th>
+                                <th><wicket:message key="name"/></th>
+                                <th><wicket:message 
key="deviceFingerprint"/></th>
+                                <th><wicket:message key="recordDate"/></th>
+                                <th><wicket:message key="expirationDate"/></th>
+                                <th></th>
+                              </tr>
+                            </thead>
+                            <tbody>
+                              <tr wicket:id="mfaTrustedDevices">
+                                <td><span wicket:id="id"/></td>
+                                <td><span wicket:id="name"/></td>
+                                <td style="word-wrap: anywhere;"><span 
wicket:id="deviceFingerprint"/></td>
+                                <td><span wicket:id="recordDate"/></td>
+                                <td><span wicket:id="expirationDate"/></td>
+                                <td style="width: 20px;">
+                                  <a href="#" 
wicket:id="mfaTrustedDeviceDelete"><i class="fas fa-trash"></i></a>
+                                </td>                                
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="paginator">
+                            <tfoot>
+                              <tr>
+                                <td 
wicket:id="mfaTrustedDevicesNavigator"></td>
+                              </tr>
+                            </tfoot>
+                          </table>                          
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div> <div class="box-body">
+                <div class="box-header formcard">
+                  <header class="card-container bg-danger">
+                    <label class="form-label card-header-style">
+                      <wicket:message key="webauthn.device.credentials.title"/>
+                    </label>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <table class="table table-striped table-bordered">
+                            <thead>
+                              <tr>
+                                <th><wicket:message key="id"/></th>
+                                <th></th>
+                              </tr>
+                            </thead>
+                            <tbody>
+                              <tr wicket:id="webAuthnDeviceCredentials">
+                                <td><span wicket:id="identifier"/></td>
+                                <td style="width: 20px;">
+                                  <a href="#" 
wicket:id="webAuthnDeviceCredentialDelete"><i class="fas fa-trash"></i></a>
+                                </td>                                
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="paginator">
+                            <tfoot>
+                              <tr>
+                                <td 
wicket:id="webAuthnDeviceCredentialsNavigator"></td>
+                              </tr>
+                            </tfoot>
+                          </table>                          
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>   
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+  </wicket:extend>
+</html>
diff --git a/client/idrepo/enduser/src/test/resources/enduser-debug.properties 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile.properties
similarity index 57%
copy from client/idrepo/enduser/src/test/resources/enduser-debug.properties
copy to 
client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile.properties
index e34599c3d0..5bf4119b60 100644
--- a/client/idrepo/enduser/src/test/resources/enduser-debug.properties
+++ 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile.properties
@@ -14,15 +14,22 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-management.endpoints.web.exposure.include=health,info,beans,env,loggers
-
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
-keymaster.username=${anonymousUser}
-keymaster.password=${anonymousKey}
-
-server.port=9091
-service.discovery.address=http://localhost:9091/syncope-enduser/
-
-logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
+page.authProfile=Auth Profile
+impersonation.accounts.title=Impersonation Accounts
+google.mfa.authtokens.title=Google MFA Tokens
+google.mfa.authaccounts.title=Google MFA Accounts
+mfa.trusted.devices.title=MFA Trusted Devices
+webauthn.device.credentials.title=WebAuthn Device Credentials
+otp=OTP
+issueDate=Issue Date
+impersonated=Impersonated
+id=Id
+name=Name
+secretKey=Secret Key
+validationCode=Validation Code
+scratchCodes=Scratch Codes
+registrationDate=Registration Date
+source=Source
+deviceFingerprint=Fingerprint
+recordDate=Record Date
+expirationDate=Expiration Date
diff --git a/client/idrepo/enduser/src/test/resources/enduser-debug.properties 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_it.properties
similarity index 56%
copy from client/idrepo/enduser/src/test/resources/enduser-debug.properties
copy to 
client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_it.properties
index e34599c3d0..bb47fe6606 100644
--- a/client/idrepo/enduser/src/test/resources/enduser-debug.properties
+++ 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_it.properties
@@ -14,15 +14,22 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-management.endpoints.web.exposure.include=health,info,beans,env,loggers
-
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
-keymaster.username=${anonymousUser}
-keymaster.password=${anonymousKey}
-
-server.port=9091
-service.discovery.address=http://localhost:9091/syncope-enduser/
-
-logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
+page.authProfile=Auth Profile
+impersonation.accounts.title=Account di impersonificazione
+google.mfa.authtokens.title=Token MFA Google
+google.mfa.authaccounts.title=Account MFA Google
+mfa.trusted.devices.title=Dispositivi MFA
+webauthn.device.credentials.title=Credenziali dispositivi WebAuthn
+otp=OTP
+issueDate=Data di emissione
+impersonated=Impersonificato
+id=Id
+name=Nome
+secretKey=Chiave segreta
+validationCode=Codice di validazione
+scratchCodes=Codici Scratch
+registrationDate=Data di registrazione
+source=Origine
+deviceFingerprint=Fingerprint
+recordDate=Data di registrazione
+expirationDate=Data di scadenza
diff --git a/client/idrepo/enduser/src/test/resources/enduser-debug.properties 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_ja.properties
similarity index 57%
copy from client/idrepo/enduser/src/test/resources/enduser-debug.properties
copy to 
client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_ja.properties
index e34599c3d0..5bf4119b60 100644
--- a/client/idrepo/enduser/src/test/resources/enduser-debug.properties
+++ 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_ja.properties
@@ -14,15 +14,22 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-management.endpoints.web.exposure.include=health,info,beans,env,loggers
-
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
-keymaster.username=${anonymousUser}
-keymaster.password=${anonymousKey}
-
-server.port=9091
-service.discovery.address=http://localhost:9091/syncope-enduser/
-
-logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
+page.authProfile=Auth Profile
+impersonation.accounts.title=Impersonation Accounts
+google.mfa.authtokens.title=Google MFA Tokens
+google.mfa.authaccounts.title=Google MFA Accounts
+mfa.trusted.devices.title=MFA Trusted Devices
+webauthn.device.credentials.title=WebAuthn Device Credentials
+otp=OTP
+issueDate=Issue Date
+impersonated=Impersonated
+id=Id
+name=Name
+secretKey=Secret Key
+validationCode=Validation Code
+scratchCodes=Scratch Codes
+registrationDate=Registration Date
+source=Source
+deviceFingerprint=Fingerprint
+recordDate=Record Date
+expirationDate=Expiration Date
diff --git a/client/idrepo/enduser/src/test/resources/enduser-debug.properties 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_pt_BR.properties
similarity index 57%
copy from client/idrepo/enduser/src/test/resources/enduser-debug.properties
copy to 
client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_pt_BR.properties
index e34599c3d0..5bf4119b60 100644
--- a/client/idrepo/enduser/src/test/resources/enduser-debug.properties
+++ 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_pt_BR.properties
@@ -14,15 +14,22 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-management.endpoints.web.exposure.include=health,info,beans,env,loggers
-
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
-keymaster.username=${anonymousUser}
-keymaster.password=${anonymousKey}
-
-server.port=9091
-service.discovery.address=http://localhost:9091/syncope-enduser/
-
-logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
+page.authProfile=Auth Profile
+impersonation.accounts.title=Impersonation Accounts
+google.mfa.authtokens.title=Google MFA Tokens
+google.mfa.authaccounts.title=Google MFA Accounts
+mfa.trusted.devices.title=MFA Trusted Devices
+webauthn.device.credentials.title=WebAuthn Device Credentials
+otp=OTP
+issueDate=Issue Date
+impersonated=Impersonated
+id=Id
+name=Name
+secretKey=Secret Key
+validationCode=Validation Code
+scratchCodes=Scratch Codes
+registrationDate=Registration Date
+source=Source
+deviceFingerprint=Fingerprint
+recordDate=Record Date
+expirationDate=Expiration Date
diff --git a/client/idrepo/enduser/src/test/resources/enduser-debug.properties 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_ru.properties
similarity index 57%
copy from client/idrepo/enduser/src/test/resources/enduser-debug.properties
copy to 
client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_ru.properties
index e34599c3d0..5bf4119b60 100644
--- a/client/idrepo/enduser/src/test/resources/enduser-debug.properties
+++ 
b/client/am/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/AuthProfile_ru.properties
@@ -14,15 +14,22 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-management.endpoints.web.exposure.include=health,info,beans,env,loggers
-
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
-keymaster.username=${anonymousUser}
-keymaster.password=${anonymousKey}
-
-server.port=9091
-service.discovery.address=http://localhost:9091/syncope-enduser/
-
-logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
+page.authProfile=Auth Profile
+impersonation.accounts.title=Impersonation Accounts
+google.mfa.authtokens.title=Google MFA Tokens
+google.mfa.authaccounts.title=Google MFA Accounts
+mfa.trusted.devices.title=MFA Trusted Devices
+webauthn.device.credentials.title=WebAuthn Device Credentials
+otp=OTP
+issueDate=Issue Date
+impersonated=Impersonated
+id=Id
+name=Name
+secretKey=Secret Key
+validationCode=Validation Code
+scratchCodes=Scratch Codes
+registrationDate=Registration Date
+source=Source
+deviceFingerprint=Fingerprint
+recordDate=Record Date
+expirationDate=Expiration Date
diff --git a/client/am/pom.xml b/client/am/pom.xml
index abf0b323e7..b7386df99f 100644
--- a/client/am/pom.xml
+++ b/client/am/pom.xml
@@ -40,5 +40,6 @@ under the License.
   <modules>
     <module>lib</module>
     <module>console</module>
+    <module>enduser</module>
   </modules>
 </project>
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
index ab8db42b80..22d07abea0 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
@@ -35,12 +35,12 @@ import 
org.apache.syncope.client.console.tasks.PropagationTasks;
 import org.apache.syncope.client.console.tasks.PullTasks;
 import org.apache.syncope.client.console.tasks.PushTasks;
 import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import 
org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
 import 
org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder;
 import org.apache.syncope.client.console.wizards.resources.ResourceProvision;
 import 
org.apache.syncope.client.console.wizards.resources.ResourceProvisionPanel;
 import 
org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder;
 import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.SyncopeClientException;
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ConfirmBehavior.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/ConfirmBehavior.java
similarity index 93%
rename from 
client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ConfirmBehavior.java
rename to 
client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/ConfirmBehavior.java
index c4f0218a57..0a59c422af 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ConfirmBehavior.java
+++ 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/ConfirmBehavior.java
@@ -16,14 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.wicket.markup.html.form;
+package org.apache.syncope.client.ui.commons.markup.html.form;
 
 import static de.agilecoders.wicket.jquery.JQuery.$;
 
 import de.agilecoders.wicket.jquery.function.JavaScriptInlineFunction;
 import java.util.ArrayList;
-import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.wicket.Component;
+import org.apache.wicket.Session;
 import org.apache.wicket.behavior.Behavior;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.JavaScriptHeaderItem;
@@ -63,7 +63,7 @@ public class ConfirmBehavior extends Behavior {
                         + "        className: 'btn-danger'"
                         + "    }"
                         + "},"
-                        + "locale: '" + 
SyncopeConsoleSession.get().getLocale().getLanguage() + "',"
+                        + "locale: '" + 
Session.get().getLocale().getLanguage() + "',"
                         + "callback: function(result) {"
                         + "    if (result == true) {"
                         + "      proceed = true;"
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/IndicatingOnConfirmAjaxLink.java
similarity index 96%
rename from 
client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java
rename to 
client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/IndicatingOnConfirmAjaxLink.java
index d3ae2a383f..8df5ef4adf 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/IndicatingOnConfirmAjaxLink.java
+++ 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/IndicatingOnConfirmAjaxLink.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.wicket.markup.html.form;
+package org.apache.syncope.client.ui.commons.markup.html.form;
 
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index d8386ad693..8490ed6aea 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -31,10 +31,10 @@ import 
org.apache.syncope.client.console.annotations.IdMPage;
 import org.apache.syncope.client.console.panels.DelegationSelectionPanel;
 import org.apache.syncope.client.console.rest.SyncopeRestClient;
 import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem;
-import 
org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.console.widgets.ExtAlertWidget;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.annotations.ExtPage;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java
index 177430975a..6bc123d657 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DelegationSelectionPanel.java
@@ -20,7 +20,7 @@ package org.apache.syncope.client.console.panels;
 
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.pages.Dashboard;
-import 
org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.html.basic.Label;
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java
index 3a33c3bafa..20bd29068a 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionPanel.java
@@ -25,6 +25,7 @@ import 
org.apache.syncope.client.console.SyncopeConsoleSession;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import 
org.apache.syncope.client.console.wicket.markup.html.link.VeilPopupSettings;
 import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import 
org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
diff --git a/client/idrepo/enduser/pom.xml b/client/idrepo/enduser/pom.xml
index 49ca66bb1e..914c3f36ea 100644
--- a/client/idrepo/enduser/pom.xml
+++ b/client/idrepo/enduser/pom.xml
@@ -294,6 +294,12 @@ under the License.
           <version>${project.version}</version>
         </dependency>
 
+        <dependency>
+          <groupId>org.apache.syncope.client.am</groupId>
+          <artifactId>syncope-client-am-enduser</artifactId>
+          <version>${project.version}</version>
+        </dependency>
+
         <dependency>
           <groupId>org.apache.syncope.ext.flowable</groupId>
           <artifactId>syncope-ext-flowable-client-enduser</artifactId>
diff --git 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.java
 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.java
index b9f5111f1e..05482d42d0 100644
--- 
a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.java
+++ 
b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.java
@@ -33,6 +33,7 @@ import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.core.util.string.CssUtils;
+import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Button;
 import org.apache.wicket.markup.html.form.Form;
@@ -68,7 +69,7 @@ public abstract class ChangePasswordPanel extends Panel {
             private static final long serialVersionUID = 418292023846536149L;
 
             @Override
-            protected void appendDefaultButtonField() {
+            protected void addDefaultSubmitButtonHandler(final IHeaderResponse 
headerResponse) {
                 AppendingStringBuffer buffer = new AppendingStringBuffer();
 
                 String cssClass = getString(CssUtils.key(Form.class, 
"hidden-fields"));
diff --git 
a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/Sidebar.html
 
b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/Sidebar.html
index 6b62f0746e..5aaf7de965 100644
--- 
a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/Sidebar.html
+++ 
b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/Sidebar.html
@@ -19,7 +19,7 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml"; 
xmlns:wicket="http://wicket.apache.org";>
   <wicket:panel>
-    <div class="sidebar-brand bg-red d-flex">
+    <div class="sidebar-brand bg-red d-flex" style="height: 57px;">
       <span>
         <img src="ui-commons/img/logo-mini.png" alt="Apache Syncope Logo" 
class="brand-image" style="max-height: none; opacity: .8;"/>
         <span class="brand-text fw-light text-white">Apache Syncope</span>
diff --git a/client/idrepo/enduser/src/test/resources/enduser-debug.properties 
b/client/idrepo/enduser/src/test/resources/enduser-debug.properties
index e34599c3d0..a8905a8f27 100644
--- a/client/idrepo/enduser/src/test/resources/enduser-debug.properties
+++ b/client/idrepo/enduser/src/test/resources/enduser-debug.properties
@@ -17,8 +17,8 @@
 
 management.endpoints.web.exposure.include=health,info,beans,env,loggers
 
-keymaster.address=http://localhost:9080/syncope/rest/keymaster
-#keymaster.address=https://localhost:9443/syncope/rest/keymaster
+#keymaster.address=http://localhost:9080/syncope/rest/keymaster
+keymaster.address=https://localhost:9443/syncope/rest/keymaster
 keymaster.username=${anonymousUser}
 keymaster.password=${anonymousKey}
 
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/ImpersonationAccount.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/ImpersonationAccount.java
index 6936b6363a..a0f5458ae7 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/ImpersonationAccount.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/ImpersonationAccount.java
@@ -55,7 +55,7 @@ public class ImpersonationAccount implements BaseBean {
     public int hashCode() {
         return new HashCodeBuilder()
                 .append(impersonated)
-                .toHashCode();
+                .build();
     }
 
     @Override
@@ -71,14 +71,14 @@ public class ImpersonationAccount implements BaseBean {
         }
         ImpersonationAccount other = (ImpersonationAccount) obj;
         return new EqualsBuilder()
-                .append(this.impersonated, other.impersonated)
-                .isEquals();
+                .append(impersonated, other.impersonated)
+                .build();
     }
 
     @Override
     public String toString() {
         return new ToStringBuilder(this)
                 .append("impersonated", impersonated)
-                .toString();
+                .build();
     }
 }
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/MfaTrustedDevice.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/MfaTrustedDevice.java
index b53efdd2e0..fbc208afb5 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/MfaTrustedDevice.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/MfaTrustedDevice.java
@@ -97,7 +97,7 @@ public class MfaTrustedDevice implements BaseBean {
                 .append(recordDate)
                 .append(recordKey)
                 .append(expirationDate)
-                .toHashCode();
+                .build();
     }
 
     @Override
@@ -113,13 +113,13 @@ public class MfaTrustedDevice implements BaseBean {
         }
         MfaTrustedDevice other = (MfaTrustedDevice) obj;
         return new EqualsBuilder()
-                .append(this.id, other.id)
-                .append(this.name, other.name)
-                .append(this.deviceFingerprint, other.deviceFingerprint)
-                .append(this.recordDate, other.recordDate)
-                .append(this.recordKey, other.recordKey)
-                .append(this.expirationDate, other.expirationDate)
-                .isEquals();
+                .append(id, other.id)
+                .append(name, other.name)
+                .append(deviceFingerprint, other.deviceFingerprint)
+                .append(recordDate, other.recordDate)
+                .append(recordKey, other.recordKey)
+                .append(expirationDate, other.expirationDate)
+                .build();
     }
 
     @Override
@@ -131,6 +131,6 @@ public class MfaTrustedDevice implements BaseBean {
                 .append("recordDate", recordDate)
                 .append("recordKey", recordKey)
                 .append("expirationDate", expirationDate)
-                .toString();
+                .build();
     }
 }
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/WebAuthnDeviceCredential.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/WebAuthnDeviceCredential.java
index 40db6cfe54..88ca96b814 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/WebAuthnDeviceCredential.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/WebAuthnDeviceCredential.java
@@ -71,7 +71,7 @@ public class WebAuthnDeviceCredential implements BaseBean {
         return new HashCodeBuilder()
                 .append(json)
                 .append(identifier)
-                .toHashCode();
+                .build();
     }
 
     @Override
@@ -87,9 +87,9 @@ public class WebAuthnDeviceCredential implements BaseBean {
         }
         WebAuthnDeviceCredential other = (WebAuthnDeviceCredential) obj;
         return new EqualsBuilder()
-                .append(this.json, other.json)
-                .append(this.identifier, other.identifier)
-                .isEquals();
+                .append(json, other.json)
+                .append(identifier, other.identifier)
+                .build();
     }
 
     @Override
@@ -97,6 +97,6 @@ public class WebAuthnDeviceCredential implements BaseBean {
         return new ToStringBuilder(this)
                 .append("records", json)
                 .append("identifier", identifier)
-                .toString();
+                .build();
     }
 }
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileSelfService.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileSelfService.java
new file mode 100644
index 0000000000..12315eac3b
--- /dev/null
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthProfileSelfService.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.constraints.NotNull;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.to.AuthProfileTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for Auth profile self-management.
+ */
+@Tag(name = "AuthProfileSelf")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("authProfiles/self")
+public interface AuthProfileSelfService extends JAXRSService {
+
+    /**
+     * Returns the auth profile matching the user making the service call, if 
found.
+     *
+     * @return auth profile matching the user making the service call, if found
+     */
+    @GET
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    AuthProfileTO read();
+
+    /**
+     * Updates the auth profile matching the user making the service call, if 
found.
+     *
+     * @param authProfileTO auth profile
+     */
+    @PUT
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    void update(@NotNull AuthProfileTO authProfileTO);
+
+    /**
+     * Deletes the auth profile matching the user making the service call, if 
found.
+     */
+    @DELETE
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    void delete();
+}
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/GoogleMfaAuthTokenService.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/GoogleMfaAuthTokenService.java
index 5a591fa681..f7cc23f9de 100644
--- 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/GoogleMfaAuthTokenService.java
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/GoogleMfaAuthTokenService.java
@@ -41,35 +41,34 @@ import 
org.apache.syncope.common.rest.api.service.JAXRSService;
 @SecurityRequirements({
     @SecurityRequirement(name = "BasicAuthentication"),
     @SecurityRequirement(name = "Bearer") })
-@Path("wa/gauth")
+@Path("wa/gauth/tokens")
 public interface GoogleMfaAuthTokenService extends JAXRSService {
 
     @DELETE
-    @Path("tokens")
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(@QueryParam("expirationDate") LocalDateTime expirationDate);
 
     @DELETE
-    @Path("tokens/{owner}/{otp}")
+    @Path("{owner}/{otp}")
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("owner") String owner, @NotNull 
@PathParam("otp") int otp);
 
     @DELETE
-    @Path("tokens/{owner}")
+    @Path("{owner}")
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("owner") String owner);
 
     @DELETE
-    @Path("tokens/otp/{otp}")
+    @Path("otp/{otp}")
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void delete(@NotNull @PathParam("otp") int otp);
 
     @PUT
-    @Path("tokens/{owner}")
+    @Path("{owner}")
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void store(@NotNull @PathParam("owner") String owner, @NotNull 
GoogleMfaAuthToken token);
@@ -77,18 +76,17 @@ public interface GoogleMfaAuthTokenService extends 
JAXRSService {
     @GET
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Path("tokens/{owner}/{otp}")
+    @Path("{owner}/{otp}")
     GoogleMfaAuthToken read(@NotNull @PathParam("owner") String owner, 
@NotNull @PathParam("otp") int otp);
 
     @GET
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Path("tokens/{owner}")
+    @Path("{owner}")
     PagedResult<GoogleMfaAuthToken> read(@NotNull @PathParam("owner") String 
owner);
 
     @GET
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
-    @Path("tokens")
     PagedResult<GoogleMfaAuthToken> list();
 }
diff --git 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
index 59246a9056..e13ccc1caf 100644
--- 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
+++ 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthProfileLogic.java
@@ -21,12 +21,16 @@ package org.apache.syncope.core.logic;
 import java.util.List;
 import org.apache.syncope.common.lib.to.AuthProfileTO;
 import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.core.persistence.api.dao.AuthProfileDAO;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.am.AuthProfile;
 import org.apache.syncope.core.persistence.api.search.SyncopePage;
 import org.apache.syncope.core.provisioning.api.data.AuthProfileDataBinder;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
+import 
org.apache.syncope.core.spring.security.DelegatedAdministrationException;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.security.access.prepost.PreAuthorize;
@@ -42,9 +46,15 @@ public class AuthProfileLogic extends 
AbstractAuthProfileLogic {
         super(binder, authProfileDAO, entityFactory);
     }
 
-    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_DELETE + "') ")
-    public void delete(final String key) {
-        authProfileDAO.deleteById(key);
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_LIST + "')")
+    @Transactional(readOnly = true)
+    public Page<AuthProfileTO> list(final Pageable pageable) {
+        long count = authProfileDAO.count();
+
+        List<AuthProfileTO> result = authProfileDAO.findAll(pageable).
+                stream().map(binder::getAuthProfileTO).toList();
+
+        return new SyncopePage<>(result, pageable, count);
     }
 
     @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_READ + "') ")
@@ -55,6 +65,14 @@ public class AuthProfileLogic extends 
AbstractAuthProfileLogic {
                 orElseThrow(() -> new NotFoundException("AuthProfile " + key));
     }
 
+    @PreAuthorize("isAuthenticated() and not(hasRole('" + 
IdRepoEntitlement.ANONYMOUS + "'))")
+    @Transactional(readOnly = true)
+    public AuthProfileTO selfRead() {
+        return authProfileDAO.findByOwner(AuthContextUtils.getUsername()).
+                map(binder::getAuthProfileTO).
+                orElseThrow(() -> new NotFoundException("AuthProfile for " + 
AuthContextUtils.getUsername()));
+    }
+
     @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_CREATE + "') ")
     public AuthProfileTO create(final AuthProfileTO authProfileTO) {
         return 
binder.getAuthProfileTO(authProfileDAO.save(binder.create(authProfileTO)));
@@ -68,16 +86,25 @@ public class AuthProfileLogic extends 
AbstractAuthProfileLogic {
         authProfileDAO.save(authProfile);
     }
 
-    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_LIST + "')")
-    @Transactional(readOnly = true)
-    public Page<AuthProfileTO> list(final Pageable pageable) {
-        long count = authProfileDAO.count();
+    @PreAuthorize("isAuthenticated() and not(hasRole('" + 
IdRepoEntitlement.ANONYMOUS + "'))")
+    public void selfUpdate(final AuthProfileTO authProfileTO) {
+        authProfileDAO.findByOwner(AuthContextUtils.getUsername()).
+                filter(authProfile -> 
authProfile.getKey().equals(authProfileTO.getKey())
+                && authProfile.getOwner().equals(authProfileTO.getOwner())).
+                orElseThrow(() -> new 
DelegatedAdministrationException(AnyTypeKind.USER, authProfileTO.getOwner()));
 
-        List<AuthProfileTO> result = authProfileDAO.findAll(pageable).
-                stream().
-                map(binder::getAuthProfileTO).
-                toList();
+        update(authProfileTO);
+    }
 
-        return new SyncopePage<>(result, pageable, count);
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_PROFILE_DELETE + "') ")
+    public void delete(final String key) {
+        authProfileDAO.deleteById(key);
+    }
+
+    @PreAuthorize("isAuthenticated() and not(hasRole('" + 
IdRepoEntitlement.ANONYMOUS + "'))")
+    public void selfDelete() {
+        
authProfileDAO.deleteById(authProfileDAO.findByOwner(AuthContextUtils.getUsername()).
+                orElseThrow(() -> new DelegatedAdministrationException(
+                AnyTypeKind.USER, AuthContextUtils.getUsername())).getKey());
     }
 }
diff --git 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java
 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java
index 3ca003d1d7..e419126b1c 100644
--- 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java
+++ 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/AMRESTCXFContext.java
@@ -20,6 +20,7 @@ package org.apache.syncope.core.rest.cxf;
 
 import org.apache.syncope.common.rest.api.service.AttrRepoService;
 import org.apache.syncope.common.rest.api.service.AuthModuleService;
+import org.apache.syncope.common.rest.api.service.AuthProfileSelfService;
 import org.apache.syncope.common.rest.api.service.AuthProfileService;
 import org.apache.syncope.common.rest.api.service.ClientAppService;
 import org.apache.syncope.common.rest.api.service.OIDCJWKSService;
@@ -49,6 +50,7 @@ import org.apache.syncope.core.logic.wa.WAConfigLogic;
 import org.apache.syncope.core.logic.wa.WebAuthnRegistrationLogic;
 import org.apache.syncope.core.rest.cxf.service.AttrRepoServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.AuthModuleServiceImpl;
+import org.apache.syncope.core.rest.cxf.service.AuthProfileSelfServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.AuthProfileServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.ClientAppServiceImpl;
 import org.apache.syncope.core.rest.cxf.service.OIDCJWKSServiceImpl;
@@ -87,6 +89,12 @@ public class AMRESTCXFContext {
         return new AuthProfileServiceImpl(authProfileLogic);
     }
 
+    @ConditionalOnMissingBean
+    @Bean
+    public AuthProfileSelfService authProfileSelfService(final 
AuthProfileLogic authProfileLogic) {
+        return new AuthProfileSelfServiceImpl(authProfileLogic);
+    }
+
     @ConditionalOnMissingBean
     @Bean
     public ClientAppService clientAppService(final ClientAppLogic 
clientAppLogic) {
diff --git 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileSelfServiceImpl.java
 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileSelfServiceImpl.java
new file mode 100644
index 0000000000..9d65169c8e
--- /dev/null
+++ 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileSelfServiceImpl.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.apache.syncope.core.rest.cxf.service;
+
+import org.apache.syncope.common.lib.to.AuthProfileTO;
+import org.apache.syncope.common.rest.api.service.AuthProfileSelfService;
+import org.apache.syncope.core.logic.AuthProfileLogic;
+
+public class AuthProfileSelfServiceImpl extends AbstractService implements 
AuthProfileSelfService {
+
+    protected final AuthProfileLogic logic;
+
+    public AuthProfileSelfServiceImpl(final AuthProfileLogic logic) {
+        this.logic = logic;
+    }
+
+    @Override
+    public AuthProfileTO read() {
+        return logic.selfRead();
+    }
+
+    @Override
+    public void update(final AuthProfileTO authProfileTO) {
+        logic.selfUpdate(authProfileTO);
+    }
+
+    @Override
+    public void delete() {
+        logic.selfDelete();
+    }
+}
diff --git 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
index ec560c259d..c19e418143 100644
--- 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
+++ 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthProfileServiceImpl.java
@@ -37,8 +37,9 @@ public class AuthProfileServiceImpl extends AbstractService 
implements AuthProfi
     }
 
     @Override
-    public void delete(final String key) {
-        logic.delete(key);
+    public PagedResult<AuthProfileTO> list(final int page, final int size) {
+        Page<AuthProfileTO> result = logic.list(PageRequest.of(page < 1 ? 0 : 
page - 1, size < 1 ? 1 : size));
+        return buildPagedResult(result);
     }
 
     @Override
@@ -61,8 +62,7 @@ public class AuthProfileServiceImpl extends AbstractService 
implements AuthProfi
     }
 
     @Override
-    public PagedResult<AuthProfileTO> list(final int page, final int size) {
-        Page<AuthProfileTO> result = logic.list(PageRequest.of(page < 1 ? 0 : 
page - 1, size < 1 ? 1 : size));
-        return buildPagedResult(result);
+    public void delete(final String key) {
+        logic.delete(key);
     }
 }
diff --git 
a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
 
b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
index 3df79c8633..8e6bc4aee0 100644
--- 
a/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
+++ 
b/ext/flowable/client-console/src/main/java/org/apache/syncope/client/console/panels/UserRequestDirectoryPanel.java
@@ -33,9 +33,9 @@ import 
org.apache.syncope.client.console.rest.UserRequestRestClient;
 import 
org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import 
org.apache.syncope.client.console.wicket.markup.html.form.ConfirmBehavior;
 import org.apache.syncope.client.lib.batch.BatchRequest;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.ConfirmBehavior;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.types.FlowableEntitlement;
diff --git 
a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.java
 
b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.java
index afdb00afd1..5b99ee44ba 100644
--- 
a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.java
+++ 
b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/pages/Flowable.java
@@ -18,9 +18,9 @@
  */
 package org.apache.syncope.client.enduser.pages;
 
-import java.util.Collections;
 import java.util.Iterator;
-import java.util.stream.Collectors;
+import java.util.List;
+import java.util.function.Predicate;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import 
org.apache.syncope.client.enduser.markup.html.form.BpmnProcessesAjaxPanel;
@@ -63,27 +63,22 @@ public class Flowable extends BaseExtPage {
     @SpringBean
     protected BpmnProcessRestClient bpmnProcessRestClient;
 
-    protected final Model<String> bpmnProcessModel = new Model<>();
-
-    protected final WebMarkupContainer container;
-
-    protected final DataView<UserRequest> urDataView;
-
     public Flowable(final PageParameters parameters) {
         super(parameters, USER_REQUESTS);
 
-        container = new WebMarkupContainer("content");
-        container.setOutputMarkupId(true);
+        WebMarkupContainer container = new WebMarkupContainer("content");
+        contentWrapper.add(container.setOutputMarkupId(true));
 
         // list of accordions containing request form (if any) and delete 
button
-        urDataView = new DataView<>("userRequests", new 
URDataProvider(ROWS_PER_PAGE, "bpmnProcess")) {
+        DataView<UserRequest> urDataView = new DataView<>(
+                "userRequests", new URDataProvider(ROWS_PER_PAGE, 
"bpmnProcess")) {
 
             private static final long serialVersionUID = -5002600396458362774L;
 
             @Override
             protected void populateItem(final Item<UserRequest> item) {
-                final UserRequest userRequest = item.getModelObject();
-                item.add(new Accordion("userRequestDetails", 
Collections.singletonList(new AbstractTab(
+                UserRequest userRequest = item.getModelObject();
+                item.add(new Accordion("userRequestDetails", List.of(new 
AbstractTab(
                         new StringResourceModel("user.requests.accordion", 
container, Model.of(userRequest))) {
 
                     private static final long serialVersionUID = 
1037272333056449378L;
@@ -96,12 +91,12 @@ public class Flowable extends BaseExtPage {
                 }), Model.of(-1)).setOutputMarkupId(true));
             }
         };
-
         urDataView.setItemsPerPage(ROWS_PER_PAGE);
         urDataView.setOutputMarkupId(true);
         container.add(urDataView);
         container.add(new AjaxPagingNavigator("navigator", urDataView));
 
+        Model<String> bpmnProcessModel = new Model<>();
         AjaxLink<Void> startButton = new AjaxLink<>("start") {
 
             private static final long serialVersionUID = 3669569969172391336L;
@@ -133,20 +128,14 @@ public class Flowable extends BaseExtPage {
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                if (StringUtils.isNotBlank(bpmnProcessModel.getObject())) {
-                    startButton.setEnabled(true);
-                } else {
-                    startButton.setEnabled(false);
-                }
+                
startButton.setEnabled(StringUtils.isNotBlank(bpmnProcessModel.getObject()));
                 target.add(container);
             }
         });
-        
bpmnProcesses.setChoices(bpmnProcessRestClient.getDefinitions().stream()
-                .filter(definition -> !definition.isUserWorkflow())
-                .map(BpmnProcess::getKey).collect(Collectors.toList()));
+        
bpmnProcesses.setChoices(bpmnProcessRestClient.getDefinitions().stream().
+                filter(Predicate.not(BpmnProcess::isUserWorkflow)).
+                map(BpmnProcess::getKey).toList());
         container.add(bpmnProcesses);
-
-        contentWrapper.add(container);
     }
 
     protected class URDataProvider implements IDataProvider<UserRequest> {
diff --git 
a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html
 
b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html
index 5d747c3a76..60d7c77cbd 100644
--- 
a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html
+++ 
b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.html
@@ -60,21 +60,20 @@ under the License.
                           <div wicket:id="userRequests">
                             <div wicket:id="userRequestDetails"/>
                           </div>
+
+                          <table class="paginator">
+                            <tfoot>
+                              <tr>
+                                <td wicket:id="navigator"></td>
+                              </tr>
+                            </tfoot>
+                          </table>                            
                         </div>
                       </div>
                     </div>
                   </div>
                 </div>
               </div>
-              <div class="box-footer">
-                <table class="paginator">
-                  <tfoot>
-                    <tr>
-                      <td wicket:id="navigator"></td>
-                    </tr>
-                  </tfoot>
-                </table>
-              </div>
             </div>
           </div>
         </div>
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyObjectsITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyObjectsITCase.java
index a03979406d..b10afdd8ee 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyObjectsITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyObjectsITCase.java
@@ -20,8 +20,8 @@ package org.apache.syncope.fit.console;
 
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
-import 
org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.TextField;
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/GroupsITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/GroupsITCase.java
index b3e46d24a0..b6c10c3efb 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/GroupsITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/GroupsITCase.java
@@ -22,8 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.apache.commons.lang3.StringUtils;
-import 
org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.TextField;
diff --git 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
index d5824b1bd3..901ccbf10b 100644
--- 
a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
+++ 
b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/UsersITCase.java
@@ -23,8 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.util.Calendar;
-import 
org.apache.syncope.client.console.wicket.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.IndicatingOnConfirmAjaxLink;
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.TextField;


Reply via email to