[SYNCOPE-1435] New wicket enduser (#275)

diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
index f03f20a..44c4e23 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/BaseLogin.java
@@ -63,6 +63,10 @@
 
     protected static final Logger LOG = LoggerFactory.getLogger(BaseLogin.class);
 
+    public static final List<Locale> SUPPORTED_LOCALES = List.of(
+            Locale.ENGLISH, Locale.CANADA_FRENCH, Locale.ITALIAN, Locale.JAPANESE, new Locale("pt", "BR"),
+            new Locale("ru"));
+
     @SpringBean
     private DomainOps domainOps;
 
@@ -219,8 +223,6 @@
 
     protected abstract String getAnonymousUser();
 
-    protected abstract List<Locale> getSupportedLocales();
-
     protected abstract void authenticate(
             String username,
             String password,
@@ -245,7 +247,7 @@
         }
 
         LocaleDropDown(final String id) {
-            super(id, getSupportedLocales());
+            super(id, SUPPORTED_LOCALES);
 
             setChoiceRenderer(new LocaleRenderer());
             setModel(new IModel<Locale>() {
@@ -275,7 +277,7 @@
                     getHeader(HttpHeaders.ACCEPT_LANGUAGE);
             if (StringUtils.isNotBlank(acceptLanguage)) {
                 try {
-                    filtered = Locale.filter(Locale.LanguageRange.parse(acceptLanguage), getSupportedLocales());
+                    filtered = Locale.filter(Locale.LanguageRange.parse(acceptLanguage), SUPPORTED_LOCALES);
                 } catch (Exception e) {
                     LOG.debug("Could not parse {} HTTP header value '{}'",
                             HttpHeaders.ACCEPT_LANGUAGE, acceptLanguage, e);
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java
index ee8976a..2efdb0b 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/Constants.java
@@ -113,6 +113,8 @@
 
     public static final int MAX_ROLE_LIST_SIZE = 30;
 
+    public static final String NOTIFICATION_TITLE_PARAM = "notificationTitle";
+
     public static final String NOTIFICATION_MSG_PARAM = "notificationMessage";
 
     public static final String NOTIFICATION_LEVEL_PARAM = "notificationLevel";
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxPalettePanel.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxPalettePanel.java
index 339bea7..c6c2250 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxPalettePanel.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxPalettePanel.java
@@ -24,6 +24,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.function.Function;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -33,6 +34,7 @@
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.wicket.Component;
 import org.apache.wicket.Session;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -193,6 +195,7 @@
                     @Override
                     protected void onUpdate(final AjaxRequestTarget target) {
                         processInput();
+                        Optional.ofNullable(builder.event).ifPresent(e -> e.apply(target));
                     }
                 });
 
@@ -267,6 +270,8 @@
         protected Function<String, Stream<String>> idExtractor =
                 (Function<String, Stream<String>> & Serializable) input -> Stream.of(Strings.split(input, ','));
 
+        protected Function<AjaxRequestTarget, Boolean> event;
+
         protected Function<Object, Map<String, String>> additionalAttributes;
 
         public Builder<T> setName(final String name) {
@@ -320,6 +325,11 @@
             return this;
         }
 
+        public Builder<T> event(final Function<AjaxRequestTarget, Boolean> event) {
+            this.event = event;
+            return this;
+        }
+
         public Builder<T> additionalAttributes(final Function<Object, Map<String, String>> additionalAttributes) {
             this.additionalAttributes = additionalAttributes;
             return this;
@@ -404,4 +414,24 @@
             return filtered;
         }
     }
+
+    public static class UpdateActionEvent {
+
+        private final UserTO item;
+
+        private final AjaxRequestTarget target;
+
+        public UpdateActionEvent(final UserTO item, final AjaxRequestTarget target) {
+            this.item = item;
+            this.target = target;
+        }
+
+        public UserTO getItem() {
+            return item;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+    }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldDownload.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/BinaryFieldDownload.java
similarity index 97%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldDownload.java
rename to client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/BinaryFieldDownload.java
index 65b0c03..d64f997 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldDownload.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/BinaryFieldDownload.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 java.time.Duration;
 import org.apache.commons.lang3.StringUtils;
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/panels/CardPanel.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/panels/CardPanel.java
new file mode 100644
index 0000000..face722
--- /dev/null
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/panels/CardPanel.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ui.commons.panels;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.ResourceModel;
+
+public final class CardPanel<T extends Component> extends Panel {
+
+    private static final long serialVersionUID = -7906010415162945394L;
+
+    private CardPanel(final String id, final Builder<T> builder) {
+        super(id);
+        this.setOutputMarkupId(true);
+        this.setVisible(builder.visible);
+
+        this.add(new Label("cardLabel", new ResourceModel(builder.name, builder.name)).setOutputMarkupId(true));
+        this.add(builder.component);
+    }
+
+    public static class Builder<T extends Component> {
+
+        protected String name;
+
+        protected T component;
+
+        protected boolean visible;
+
+        public Builder<T> setName(final String name) {
+            this.name = name;
+            return this;
+        }
+
+        public Builder<T> setComponent(final T component) {
+            this.component = component;
+            return this;
+        }
+
+        public Builder<T> isVisible(final boolean visible) {
+            this.visible = visible;
+            return this;
+        }
+
+        public CardPanel<T> build(final String id) {
+            return new CardPanel<>(id, this);
+        }
+    }
+}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/themes/AdminLTE.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/themes/AdminLTE.java
similarity index 96%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/themes/AdminLTE.java
rename to client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/themes/AdminLTE.java
index d270149..1dccc49 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/themes/AdminLTE.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/themes/AdminLTE.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.themes;
+package org.apache.syncope.client.ui.commons.themes;
 
 import de.agilecoders.wicket.core.settings.Theme;
 import java.util.ArrayList;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/themes/AdminLTECssResourceReference.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/themes/AdminLTECssResourceReference.java
similarity index 96%
rename from client/idrepo/console/src/main/java/org/apache/syncope/client/console/themes/AdminLTECssResourceReference.java
rename to client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/themes/AdminLTECssResourceReference.java
index 397ef5e..f9e3617 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/themes/AdminLTECssResourceReference.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/themes/AdminLTECssResourceReference.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.themes;
+package org.apache.syncope.client.ui.commons.themes;
 
 import de.agilecoders.wicket.core.Bootstrap;
 import java.util.ArrayList;
diff --git a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/PasswordPanel.java b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/PasswordPanel.java
index 6cf3087..d34ef77 100644
--- a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/PasswordPanel.java
+++ b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/any/PasswordPanel.java
@@ -38,14 +38,16 @@
     public PasswordPanel(
             final String id,
             final UserWrapper wrapper,
+            final Boolean storePasswordInSyncope,
             final boolean templateMode) {
-        this(id, wrapper, templateMode, null);
+        this(id, wrapper, templateMode, storePasswordInSyncope, null);
     }
 
     public PasswordPanel(
             final String id,
             final UserWrapper wrapper,
             final boolean templateMode,
+            final Boolean storePasswordInSyncope,
             final PasswordStrengthBehavior passwordStrengthBehavior) {
 
         super(id);
@@ -88,16 +90,16 @@
             form.add(new EqualPasswordInputValidator(passwordField.getField(), confirmPasswordField.getField()));
         }
 
-        AjaxCheckBoxPanel storePasswordInSyncope = new AjaxCheckBoxPanel("storePasswordInSyncope",
+        AjaxCheckBoxPanel storePasswordInSyncopePanel = new AjaxCheckBoxPanel("storePasswordInSyncope",
                 "storePasswordInSyncope", new PropertyModel<>(wrapper, "storePasswordInSyncope"));
-        storePasswordInSyncope.getField().setLabel(new ResourceModel("storePasswordInSyncope"));
-        storePasswordInSyncope.setOutputMarkupId(true);
-        storePasswordInSyncope.setOutputMarkupPlaceholderTag(true);
-        if (wrapper.getInnerObject().getKey() == null) {
-            storePasswordInSyncope.getField().setDefaultModelObject(Boolean.TRUE);
+        storePasswordInSyncopePanel.getField().setLabel(new ResourceModel("storePasswordInSyncope"));
+        storePasswordInSyncopePanel.setOutputMarkupId(true);
+        storePasswordInSyncopePanel.setOutputMarkupPlaceholderTag(true);
+        if (storePasswordInSyncope) {
+            storePasswordInSyncopePanel.getField().setDefaultModelObject(Boolean.TRUE);
         } else {
-            storePasswordInSyncope.setVisible(false);
+            storePasswordInSyncopePanel.setVisible(false);
         }
-        form.add(storePasswordInSyncope);
+        form.add(storePasswordInSyncopePanel);
     }
 }
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html b/client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/panels/CardPanel.html
similarity index 73%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html
copy to client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/panels/CardPanel.html
index e77ed5d..1ef8d64 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html
+++ b/client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/panels/CardPanel.html
@@ -1,3 +1,4 @@
+<?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
@@ -18,12 +19,13 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
-    <div id="editUserChangePassword">
-      <div class="alert alert-warning">
-        <i class="fas fa-exclamation-triangle"></i> <label wicket:id="warning">[warning]</label>
+    <div class="box-header formcard">
+      <header class="card-container card-red">
+        <label class="card-header-style" wicket:id="cardLabel">[CARD LABEL]</label>
+      </header>
+      <div class="card-container-body">
+        <span wicket:id="contentPanel">[CONTENT PANEL]</span>
       </div>
-
-      <div wicket:id="passwordPanel">[change password panel]</div>
     </div>
   </wicket:panel>
 </html>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/themes/css/AdminLTE.css b/client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/themes/css/AdminLTE.css
similarity index 100%
rename from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/themes/css/AdminLTE.css
rename to client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/themes/css/AdminLTE.css
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/themes/js/AdminLTE-app.min.js b/client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/themes/js/AdminLTE-app.min.js
similarity index 100%
rename from client/idrepo/console/src/main/resources/org/apache/syncope/client/console/themes/js/AdminLTE-app.min.js
rename to client/idrepo/common-ui/src/main/resources/org/apache/syncope/client/ui/commons/themes/js/AdminLTE-app.min.js
diff --git a/client/idrepo/console/pom.xml b/client/idrepo/console/pom.xml
index db78158..dc4ca1a 100644
--- a/client/idrepo/console/pom.xml
+++ b/client/idrepo/console/pom.xml
@@ -123,7 +123,6 @@
       <artifactId>disruptor</artifactId>
     </dependency>
 
-    <!-- required by wicket tester -->
     <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
index 76ff69c..a9e48da 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeWebApplication.java
@@ -28,8 +28,6 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 import org.apache.commons.lang3.BooleanUtils;
@@ -43,7 +41,6 @@
 import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.pages.Dashboard;
 import org.apache.syncope.client.console.pages.Login;
-import org.apache.syncope.client.console.themes.AdminLTE;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.common.lib.PropertyUtils;
 import org.apache.wicket.Page;
@@ -69,6 +66,7 @@
 import org.apache.syncope.client.console.commons.VirSchemaDetailsPanelProvider;
 import org.apache.syncope.client.console.pages.MustChangePassword;
 import org.apache.syncope.client.console.panels.AnyPanel;
+import org.apache.syncope.client.ui.commons.themes.AdminLTE;
 import org.apache.syncope.client.ui.commons.SyncopeUIRequestCycleListener;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
@@ -88,10 +86,6 @@
 
     private static final String CONSOLE_PROPERTIES = "console.properties";
 
-    public static final List<Locale> SUPPORTED_LOCALES = List.of(
-            Locale.ENGLISH, Locale.CANADA_FRENCH, Locale.ITALIAN, Locale.JAPANESE, new Locale("pt", "BR"),
-            new Locale("ru"));
-
     public static SyncopeWebApplication get() {
         return (SyncopeWebApplication) WebApplication.get();
     }
@@ -169,7 +163,7 @@
         }
     }
 
-    protected void setSecurityHeaders(final Properties props, final WebResponse response) {
+    protected static void setSecurityHeaders(final Properties props, final WebResponse response) {
         @SuppressWarnings("unchecked")
         Enumeration<String> propNames = (Enumeration<String>) props.propertyNames();
         while (propNames.hasMoreElements()) {
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 11a1cad..a6ff75f 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
@@ -22,6 +22,7 @@
 import java.lang.reflect.Constructor;
 import java.time.Duration;
 import java.util.List;
+import java.util.stream.StreamSupport;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
@@ -394,19 +395,16 @@
         }
         // 4. when found, set CSS coordinates for menu
         if (containingLI != null) {
-            for (Component child : containingLI) {
-                if (child instanceof Link) {
-                    child.add(new Behavior() {
+            StreamSupport.stream(containingLI.spliterator(), false).filter(Link.class::isInstance).
+                    forEach(child -> child.add(new Behavior() {
 
-                        private static final long serialVersionUID = -5775607340182293596L;
+                private static final long serialVersionUID = -5775607340182293596L;
 
-                        @Override
-                        public void onComponentTag(final Component component, final ComponentTag tag) {
-                            tag.append("class", "active", " ");
-                        }
-                    });
+                @Override
+                public void onComponentTag(final Component component, final ComponentTag tag) {
+                    tag.append("class", "active", " ");
                 }
-            }
+            }));
 
             if (keymasterULContainer.getId().equals(containingLI.getParent().getId())) {
                 keymasterULContainer.add(new Behavior() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Login.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Login.java
index 37944e8..a1244fe 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Login.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Login.java
@@ -21,7 +21,6 @@
 import java.security.AccessControlException;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Locale;
 import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.ui.commons.BaseLogin;
@@ -71,11 +70,6 @@
     }
 
     @Override
-    protected List<Locale> getSupportedLocales() {
-        return SyncopeWebApplication.SUPPORTED_LOCALES;
-    }
-
-    @Override
     protected void authenticate(final String username, final String password, final AjaxRequestTarget target)
             throws AccessControlException {
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
index b646d0a..77366e6 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/SchemaTypeWizardBuilder.java
@@ -32,6 +32,7 @@
 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.wizards.BaseAjaxWizardBuilder;
+import org.apache.syncope.client.ui.commons.BaseLogin;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
 import org.apache.syncope.common.lib.to.DerSchemaTO;
@@ -163,7 +164,7 @@
                         }
                     });
                     locale.setRequired(true).hideLabel();
-                    locale.setChoices(SyncopeWebApplication.SUPPORTED_LOCALES.stream().
+                    locale.setChoices(BaseLogin.SUPPORTED_LOCALES.stream().
                             map(Objects::toString).collect(Collectors.toList()));
                     locale.addValidator(validatable -> {
                         try {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java
index 5d56dba..0e865ec 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/status/ChangePasswordModal.java
@@ -58,7 +58,7 @@
 
         this.wrapper = wrapper;
 
-        final PasswordPanel passwordPanel = new PasswordPanel("passwordPanel", wrapper, false);
+        final PasswordPanel passwordPanel = new PasswordPanel("passwordPanel", wrapper, false, false);
         passwordPanel.setOutputMarkupId(true);
         add(passwordPanel);
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
index 861394e..2bdd5b4 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/bootstrap/dialog/BaseModal.java
@@ -102,8 +102,6 @@
         this.windowClosedCallback = null;
         components = new ArrayList<>();
 
-        // Note: not adding this would imply adding WebjarsJavaScriptResourceReference about JQuery resizable and mouse
-        // add(new Resizable().withChildSelector(".modal-content"));
         // Note: not adding this would imply adding of WebjarsJavaScriptResourceReference about JQuery draggable
         add(new Draggable(new DraggableConfig().withHandle(".modal-header").withCursor("move")));
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
index bc48ae0..bc46754 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/BinaryFieldPanel.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.client.console.wicket.markup.html.form;
 
+import org.apache.syncope.client.ui.commons.markup.html.form.BinaryFieldDownload;
+
 import static de.agilecoders.wicket.jquery.JQuery.$;
 
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.fileinput.BootstrapFileInputField;
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
index f4ae2c3..600fc1d 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/AbstractAttrsWizardStep.java
@@ -285,7 +285,7 @@
                 break;
 
             case Binary:
-                final PageReference pageRef = getPageReference();
+                PageReference pageRef = getPageReference();
                 panel = new BinaryFieldPanel(
                         "panel",
                         plainSchema.getLabel(SyncopeConsoleSession.get().getLocale()),
@@ -299,7 +299,6 @@
                     protected PageReference getPageReference() {
                         return pageRef;
                     }
-
                 };
                 if (required) {
                     panel.addRequiredLabel();
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java
index d8c49e3..e067ef1 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserDetails.java
@@ -137,7 +137,7 @@
             super(id);
             setOutputMarkupId(true);
             add(new Label("warning", new ResourceModel("password.change.warning")));
-            add(new PasswordPanel("passwordPanel", wrapper, templateMode));
+            add(new PasswordPanel("passwordPanel", wrapper, false, templateMode));
         }
     }
 }
diff --git a/client/idrepo/enduser/pom.xml b/client/idrepo/enduser/pom.xml
index f7ed9fa..9bf1786 100644
--- a/client/idrepo/enduser/pom.xml
+++ b/client/idrepo/enduser/pom.xml
@@ -110,14 +110,16 @@
       <groupId>com.lmax</groupId>
       <artifactId>disruptor</artifactId>
     </dependency>
+
     <dependency>
-      <groupId>commons-logging</groupId>
-      <artifactId>commons-logging</artifactId>
-      <scope>provided</scope>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>jcl-over-slf4j</artifactId>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>test</scope>
     </dependency>
   </dependencies>
   
@@ -240,6 +242,7 @@
             <configuration>
               <jvmArguments>
                 -Dwicket.core.settings.general.configuration-type=development
+                -XX:HotswapAgent=fatjar
                 -Xdebug -Xrunjdwp:transport=dt_socket,address=8004,server=y,suspend=n
               </jvmArguments>
               <profiles>
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/BookmarkablePageLinkBuilder.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/BookmarkablePageLinkBuilder.java
new file mode 100644
index 0000000..7f5c806
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/BookmarkablePageLinkBuilder.java
@@ -0,0 +1,45 @@
+/*
+ * 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.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+
+public final class BookmarkablePageLinkBuilder {
+
+    public static <T extends WebPage> BookmarkablePageLink<T> build(
+            final String key, final Class<T> defaultPageClass) {
+
+        return build(key, key, defaultPageClass);
+    }
+
+    public static <T extends WebPage> BookmarkablePageLink<T> build(
+            final String key, final String id, final Class<T> defaultPageClass) {
+
+        @SuppressWarnings("unchecked")
+        Class<T> pageClass = (Class<T>) SyncopeWebApplication.get().getPageClass(key);
+        return new BookmarkablePageLink<>(
+                id,
+                pageClass == null ? defaultPageClass : pageClass);
+    }
+
+    private BookmarkablePageLinkBuilder() {
+        // private constructor for static utility class
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/PreferenceManager.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/PreferenceManager.java
new file mode 100644
index 0000000..845e7a6
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/PreferenceManager.java
@@ -0,0 +1,172 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.wicket.util.cookies.CookieDefaults;
+import org.apache.wicket.util.cookies.CookieUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PreferenceManager implements Serializable {
+
+    private static final long serialVersionUID = 3581434664555284193L;
+
+    private static final Logger LOG = LoggerFactory.getLogger(PreferenceManager.class);
+
+    private static final String COOKIE_NAME = "syncope2EnduserPrefs";
+
+    private static final int ONE_YEAR_TIME = 60 * 60 * 24 * 365;
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private static final TypeReference<Map<String, String>> MAP_TYPE_REF = new TypeReference<Map<String, String>>() {
+    };
+
+    private static final List<Integer> PAGINATOR_CHOICES = Arrays.asList(new Integer[] { 10, 25, 50 });
+
+    private static final CookieUtils COOKIE_UTILS;
+
+    static {
+        CookieDefaults cookieDefaults = new CookieDefaults();
+        cookieDefaults.setMaxAge(ONE_YEAR_TIME);
+        COOKIE_UTILS = new CookieUtils(cookieDefaults);
+    }
+
+    public List<Integer> getPaginatorChoices() {
+        return PAGINATOR_CHOICES;
+    }
+
+    private Map<String, String> getPrefs(final String value) {
+        Map<String, String> prefs;
+        try {
+            if (StringUtils.isNotBlank(value)) {
+                prefs = MAPPER.readValue(value, MAP_TYPE_REF);
+            } else {
+                throw new Exception("Invalid cookie value '" + value + "'");
+            }
+        } catch (Exception e) {
+            LOG.debug("No preferences found", e);
+            prefs = new HashMap<>();
+        }
+
+        return prefs;
+    }
+
+    private String setPrefs(final Map<String, String> prefs) throws IOException {
+        StringWriter writer = new StringWriter();
+        MAPPER.writeValue(writer, prefs);
+
+        return writer.toString();
+    }
+
+    public String get(final String key) {
+        String result = null;
+
+        String prefString = COOKIE_UTILS.load(COOKIE_NAME);
+        if (prefString != null) {
+            Map<String, String> prefs = getPrefs(new String(Base64.getDecoder().decode(prefString.getBytes())));
+            result = prefs.get(key);
+        }
+
+        return result;
+    }
+
+    public Integer getPaginatorRows(final String key) {
+        Integer result = getPaginatorChoices().get(0);
+
+        String value = get(key);
+        if (value != null) {
+            result = NumberUtils.toInt(value, 10);
+        }
+
+        return result;
+    }
+
+    public List<String> getList(final String key) {
+        List<String> result = new ArrayList<>();
+
+        String compound = get(key);
+
+        if (StringUtils.isNotBlank(compound)) {
+            String[] items = compound.split(";");
+            result.addAll(Arrays.asList(items));
+        }
+
+        return result;
+    }
+
+    public void set(final Map<String, List<String>> prefs) {
+        Map<String, String> current = new HashMap<>();
+
+        String prefString = COOKIE_UTILS.load(COOKIE_NAME);
+        if (prefString != null) {
+            current.putAll(getPrefs(new String(Base64.getDecoder().decode(prefString.getBytes()))));
+        }
+
+        // after retrieved previous setting in order to overwrite the key ...
+        prefs.forEach((key, values) -> {
+            current.put(key, StringUtils.join(values, ";"));
+        });
+
+        try {
+            COOKIE_UTILS.save(COOKIE_NAME, Base64.getEncoder().encodeToString(setPrefs(current).getBytes()));
+        } catch (IOException e) {
+            LOG.error("Could not save {} info: {}", getClass().getSimpleName(), current, e);
+        }
+    }
+
+    public void set(final String key, final String value) {
+        String prefString = COOKIE_UTILS.load(COOKIE_NAME);
+
+        final Map<String, String> current = new HashMap<>();
+        if (prefString != null) {
+            current.putAll(getPrefs(new String(Base64.getDecoder().decode(prefString.getBytes()))));
+        }
+
+        // after retrieved previous setting in order to overwrite the key ...
+        current.put(key, value);
+
+        try {
+            COOKIE_UTILS.save(COOKIE_NAME, Base64.getEncoder().encodeToString(setPrefs(current).getBytes()));
+        } catch (IOException e) {
+            LOG.error("Could not save {} info: {}", getClass().getSimpleName(), current, e);
+        }
+    }
+
+    public void setList(final String key, final List<String> values) {
+        set(key, StringUtils.join(values, ";"));
+    }
+
+    public void setList(final Map<String, List<String>> prefs) {
+        set(prefs);
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java
deleted file mode 100644
index 0a683a9..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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;
-
-public final class SyncopeEnduserConstants {
-
-    public static final String CAPTCHA_SESSION_KEY = "captcha";
-
-    public static final String XSRF_COOKIE = "XSRF-TOKEN";
-
-    public static final String XSRF_HEADER_NAME = "X-XSRF-TOKEN";
-
-    public static final String MEMBERSHIP_ATTR_SEPARATOR = "#";
-
-    private SyncopeEnduserConstants() {
-        // private constructor for utility class
-    }
-
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserRequestCycleListener.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserRequestCycleListener.java
new file mode 100644
index 0000000..a0e2192
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserRequestCycleListener.java
@@ -0,0 +1,104 @@
+/*
+ * 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 java.security.AccessControlException;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.ForbiddenException;
+import javax.xml.ws.WebServiceException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.pages.Login;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.wicket.authorization.UnauthorizedInstantiationException;
+import org.apache.wicket.core.request.handler.PageProvider;
+import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
+import org.apache.wicket.markup.html.pages.ExceptionErrorPage;
+import org.apache.wicket.protocol.http.PageExpiredException;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.component.IRequestablePage;
+import org.apache.wicket.request.cycle.IRequestCycleListener;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SyncopeEnduserRequestCycleListener implements IRequestCycleListener {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserRequestCycleListener.class);
+
+    private Throwable instanceOf(final Exception e, final Class<? extends Exception> clazz) {
+        return clazz.isAssignableFrom(e.getClass())
+                ? e
+                : e.getCause() != null && clazz.isAssignableFrom(e.getCause().getClass())
+                ? e.getCause()
+                : e.getCause() != null && e.getCause().getCause() != null
+                && clazz.isAssignableFrom(e.getCause().getCause().getClass())
+                ? e.getCause().getCause()
+                : null;
+    }
+
+    @Override
+    public IRequestHandler onException(final RequestCycle cycle, final Exception e) {
+        LOG.error("Exception found", e);
+
+        PageParameters errorParameters = new PageParameters();
+
+        IRequestablePage errorPage;
+        if (instanceOf(e, UnauthorizedInstantiationException.class) != null) {
+            errorParameters.add("errorMessage", SyncopeEnduserSession.Error.AUTHORIZATION.fallback());
+            errorPage = new Login(errorParameters);
+        } else if (instanceOf(e, AccessControlException.class) != null) {
+            if (StringUtils.containsIgnoreCase(instanceOf(e, AccessControlException.class).getMessage(), "expired")) {
+                errorParameters.add("errorMessage", SyncopeEnduserSession.Error.SESSION_EXPIRED.fallback());
+            } else {
+                errorParameters.add("errorMessage", SyncopeEnduserSession.Error.AUTHORIZATION.fallback());
+            }
+            errorPage = new Login(errorParameters);
+        } else if (instanceOf(e, PageExpiredException.class) != null || !SyncopeEnduserSession.get().isSignedIn()) {
+            errorParameters.add("errorMessage", SyncopeEnduserSession.Error.SESSION_EXPIRED.fallback());
+            errorPage = new Login(errorParameters);
+        } else if (instanceOf(e, BadRequestException.class) != null
+                || instanceOf(e, WebServiceException.class) != null
+                || instanceOf(e, SyncopeClientException.class) != null) {
+
+            errorParameters.add("errorMessage", SyncopeEnduserSession.Error.REST.fallback());
+            errorPage = new Login(errorParameters);
+        } else {
+            Throwable cause = instanceOf(e, ForbiddenException.class);
+            if (cause == null) {
+                // redirect to default Wicket error page
+                errorPage = new ExceptionErrorPage(e, null);
+            } else {
+                errorParameters.add("errorMessage", cause.getMessage());
+                errorPage = new Login(errorParameters);
+            }
+        }
+
+        if (errorPage instanceof Login) {
+            try {
+                SyncopeEnduserSession.get().invalidate();
+            } catch (Throwable t) {
+                // ignore
+                LOG.debug("Unexpected error while forcing logout after error", t);
+            }
+        }
+
+        return new RenderPageRequestHandler(new PageProvider(errorPage));
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
index 7ea4470..81f8a25 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java
@@ -18,21 +18,6 @@
  */
 package org.apache.syncope.client.enduser;
 
-import java.security.AccessControlException;
-import java.text.DateFormat;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
-import java.util.stream.Collectors;
-import javax.ws.rs.BadRequestException;
-import javax.ws.rs.ForbiddenException;
-import javax.ws.rs.core.EntityTag;
-import javax.ws.rs.core.MediaType;
-import javax.xml.ws.WebServiceException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
@@ -43,24 +28,63 @@
 import org.apache.syncope.client.ui.commons.BaseSession;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.info.PlatformInfo;
+import org.apache.syncope.common.lib.info.SystemInfo;
 import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.Session;
-import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authroles.authorization.strategies.role.Roles;
 import org.apache.wicket.request.Request;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.task.TaskRejectedException;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import javax.ws.rs.BadRequestException;
+import javax.ws.rs.ForbiddenException;
+import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.MediaType;
+import javax.xml.ws.WebServiceException;
+import java.security.AccessControlException;
+import java.text.DateFormat;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
 
-/**
- * Custom Syncope Enduser Session class.
- */
-public class SyncopeEnduserSession extends WebSession implements BaseSession {
+public class SyncopeEnduserSession extends AuthenticatedWebSession implements BaseSession {
 
-    private static final long serialVersionUID = 1284946129513378647L;
+    private static final long serialVersionUID = 747562246415852166L;
+
+    public enum Error {
+        INVALID_SECURITY_ANSWER("invalid.security.answer", "Invalid Security Answer"),
+        SESSION_EXPIRED("error.session.expired", "Session expired: please login again"),
+        AUTHORIZATION("error.authorization", "Insufficient access rights when performing the requested operation"),
+        REST("error.rest", "There was an error while contacting the Core server");
+
+        private final String key;
+
+        private final String fallback;
+
+        Error(final String key, final String fallback) {
+            this.key = key;
+            this.fallback = fallback;
+        }
+
+        public String key() {
+            return key;
+        }
+
+        public String fallback() {
+            return fallback;
+        }
+    }
 
     private static final Logger LOG = LoggerFactory.getLogger(SyncopeEnduserSession.class);
 
@@ -68,9 +92,9 @@
 
     private final SyncopeClient anonymousClient;
 
-    private SyncopeClient client;
+    private final PlatformInfo platformInfo;
 
-    private UserTO selfTO;
+    private final SystemInfo systemInfo;
 
     private final Map<Class<?>, Object> services = Collections.synchronizedMap(new HashMap<>());
 
@@ -78,6 +102,10 @@
 
     private String domain;
 
+    private SyncopeClient client;
+
+    private UserTO selfTO;
+
     public static SyncopeEnduserSession get() {
         return (SyncopeEnduserSession) Session.get();
     }
@@ -86,22 +114,39 @@
         super(request);
 
         clientFactory = SyncopeWebApplication.get().newClientFactory();
-        anonymousClient = clientFactory.create(new AnonymousAuthenticationHandler(
-                SyncopeWebApplication.get().getAnonymousUser(),
-                SyncopeWebApplication.get().getAnonymousKey()));
+        anonymousClient = clientFactory.
+                create(new AnonymousAuthenticationHandler(
+                        SyncopeWebApplication.get().getAnonymousUser(),
+                        SyncopeWebApplication.get().getAnonymousKey()));
+
+        platformInfo = getAnonymousService(SyncopeService.class).platform();
+        systemInfo = getAnonymousService(SyncopeService.class).system();
 
         executor = new ThreadPoolTaskExecutor();
         executor.setWaitForTasksToCompleteOnShutdown(false);
-        executor.setCorePoolSize(SyncopeWebApplication.get().getCorePoolSize());
-        executor.setMaxPoolSize(SyncopeWebApplication.get().getMaxPoolSize());
-        executor.setQueueCapacity(SyncopeWebApplication.get().getQueueCapacity());
         executor.initialize();
     }
 
     protected String message(final SyncopeClientException sce) {
-        return sce.getType().name() + ": " + sce.getElements().stream().collect(Collectors.joining(", "));
+        Error error = null;
+        if (sce.getType() == ClientExceptionType.InvalidSecurityAnswer) {
+            error = Error.INVALID_SECURITY_ANSWER;
+        }
+        if (error == null) {
+            return sce.getType().name() + ": " + sce.getElements().stream().collect(Collectors.joining(", "));
+        }
+        return getApplication().getResourceSettings().getLocalizer().
+                getString(error.key(), null, null, null, null, error.fallback());
     }
 
+    /**
+     * Extract and localize (if translation available) the actual message from the given exception; then, report it
+     * via {@link Session#error(java.io.Serializable)}.
+     *
+     * @see org.apache.syncope.client.lib.RestClientExceptionMapper
+     *
+     * @param e raised exception
+     */
     @Override
     public void onException(final Exception e) {
         Throwable root = ExceptionUtils.getRootCause(e);
@@ -111,8 +156,6 @@
             SyncopeClientException sce = (SyncopeClientException) root;
             message = sce.isComposite()
                     ? sce.asComposite().getExceptions().stream().map(this::message).collect(Collectors.joining("; "))
-                    : sce.getType() == ClientExceptionType.InvalidSecurityAnswer
-                    ? getApplication().getResourceSettings().getLocalizer().getString("invalid.security.answer", null)
                     : message(sce);
         } else if (root instanceof AccessControlException || root instanceof ForbiddenException) {
             Error error = StringUtils.containsIgnoreCase(message, "expired")
@@ -130,15 +173,31 @@
         error(message);
     }
 
-    public void cleanup() {
-        client = null;
-        selfTO = null;
-        services.clear();
+    public MediaType getMediaType() {
+        return clientFactory.getContentType().getMediaType();
+    }
+
+    public SyncopeClient getAnonymousClient() {
+        return anonymousClient;
+    }
+
+    public void execute(final Runnable command) {
+        try {
+            executor.execute(command);
+        } catch (TaskRejectedException e) {
+            LOG.error("Could not execute {}", command, e);
+        }
     }
 
     @Override
-    public String getJWT() {
-        return Optional.ofNullable(client).map(SyncopeClient::getJWT).orElse(null);
+    public <T> Future<T> execute(final Callable<T> command) {
+        try {
+            return executor.submit(command);
+        } catch (TaskRejectedException e) {
+            LOG.error("Could not execute {}", command, e);
+
+            return new CompletableFuture<>();
+        }
     }
 
     @Override
@@ -151,24 +210,30 @@
         return StringUtils.isBlank(domain) ? SyncopeConstants.MASTER_DOMAIN : domain;
     }
 
-    private void afterAuthentication(final String username) {
-        try {
-            selfTO = client.self().getRight();
-        } catch (ForbiddenException e) {
-            LOG.warn("Could not read self(), probably in a {} scenario", IdRepoEntitlement.MUST_CHANGE_PASSWORD, e);
-
-            selfTO = new UserTO();
-            selfTO.setUsername(username);
-            selfTO.setMustChangePassword(true);
-        }
-
-        // bind explicitly this session to have a stateful behavior during http requests, unless session will
-        // expire at each request
-        this.bind();
+    @Override
+    public String getJWT() {
+        return client == null ? null : client.getJWT();
     }
 
+    @Override
+    public Roles getRoles() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    public PlatformInfo getPlatformInfo() {
+        return platformInfo;
+    }
+
+    public SystemInfo getSystemInfo() {
+        return systemInfo;
+    }
+
+    @Override
     public boolean authenticate(final String username, final String password) {
         boolean authenticated = false;
+        if (SyncopeWebApplication.get().getAdminUser().equalsIgnoreCase(username)) {
+            return authenticated;
+        }
 
         try {
             client = clientFactory.setDomain(getDomain()).create(username, password);
@@ -199,6 +264,36 @@
         return authenticated;
     }
 
+    private void afterAuthentication(final String username) {
+        try {
+            selfTO = client.self().getRight();
+        } catch (ForbiddenException e) {
+            LOG.warn("Could not read self(), probably in a {} scenario", IdRepoEntitlement.MUST_CHANGE_PASSWORD, e);
+
+            selfTO = new UserTO();
+            selfTO.setUsername(username);
+            selfTO.setMustChangePassword(true);
+        }
+
+        // bind explicitly this session to have a stateful behavior during http requests, unless session will
+        // expire at each request
+        this.bind();
+    }
+
+    protected boolean isAuthenticated() {
+        return client != null && client.getJWT() != null;
+    }
+
+    protected boolean isMustChangePassword() {
+        return selfTO != null && selfTO.isMustChangePassword();
+    }
+
+    public void cleanup() {
+        client = null;
+        selfTO = null;
+        services.clear();
+    }
+
     @Override
     public void invalidate() {
         if (isAuthenticated()) {
@@ -214,9 +309,30 @@
         super.invalidate();
     }
 
-    @Override
-    public <T> T getAnonymousService(final Class<T> serviceClass) {
-        return getService(serviceClass);
+    public UserTO getSelfTO() {
+        return getSelfTO(false);
+    }
+
+    public UserTO getSelfTO(final boolean reload) {
+        if (reload) {
+            afterAuthentication(selfTO.getUsername());
+        }
+        return selfTO;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> T getCachedService(final Class<T> serviceClass) {
+        T service;
+        if (services.containsKey(serviceClass)) {
+            service = (T) services.get(serviceClass);
+        } else {
+            service = client.getService(serviceClass);
+            services.put(serviceClass, service);
+        }
+
+        WebClient.client(service).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
+
+        return service;
     }
 
     @Override
@@ -237,43 +353,6 @@
         return serviceInstance;
     }
 
-    public UserTO getSelfTO() {
-        if (selfTO == null) {
-            throw new IllegalArgumentException("User not authenticated");
-        }
-        return selfTO;
-    }
-
-    @Override
-    public <T> Future<T> execute(final Callable<T> command) {
-        try {
-            return executor.submit(command);
-        } catch (TaskRejectedException e) {
-            LOG.error("Could not execute {}", command, e);
-
-            return new CompletableFuture<>();
-        }
-    }
-
-    public boolean isAuthenticated() {
-        return client != null && client.getJWT() != null;
-    }
-
-    @SuppressWarnings("unchecked")
-    private <T> T getCachedService(final Class<T> serviceClass) {
-        T service;
-        if (services.containsKey(serviceClass)) {
-            service = (T) services.get(serviceClass);
-        } else {
-            service = client.getService(serviceClass);
-            services.put(serviceClass, service);
-        }
-
-        WebClient.client(service).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
-
-        return service;
-    }
-
     @Override
     public <T> void resetClient(final Class<T> service) {
         T serviceInstance = getCachedService(service);
@@ -284,4 +363,9 @@
     public FastDateFormat getDateFormat() {
         return FastDateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, getLocale());
     }
+
+    @Override
+    public <T> T getAnonymousService(final Class<T> serviceClass) {
+        return getAnonymousClient().getService(serviceClass);
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
index af4fa3a..37ca700 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeWebApplication.java
@@ -25,9 +25,11 @@
 import de.agilecoders.wicket.core.Bootstrap;
 import de.agilecoders.wicket.core.settings.BootstrapSettings;
 import de.agilecoders.wicket.core.settings.IBootstrapSettings;
+import de.agilecoders.wicket.core.settings.SingleThemeProvider;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
@@ -40,26 +42,26 @@
 import org.apache.commons.io.monitor.FileAlterationMonitor;
 import org.apache.commons.io.monitor.FileAlterationObserver;
 import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.ClassUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
-import org.apache.syncope.client.enduser.assets.SyncopeEnduserCss;
-import org.apache.syncope.client.enduser.model.CustomAttributesInfo;
+import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.enduser.pages.BasePage;
+import org.apache.syncope.client.enduser.pages.Dashboard;
 import org.apache.syncope.client.enduser.pages.Login;
 import org.apache.syncope.client.enduser.pages.MustChangePassword;
-import org.apache.syncope.client.enduser.pages.Self;
 import org.apache.syncope.client.enduser.pages.SelfConfirmPasswordReset;
+import org.apache.syncope.client.enduser.panels.Sidebar;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.client.ui.commons.SyncopeUIRequestCycleListener;
 import org.apache.syncope.client.ui.commons.annotations.Resource;
+import org.apache.syncope.client.ui.commons.themes.AdminLTE;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.lib.PropertyUtils;
 import org.apache.wicket.Page;
 import org.apache.wicket.Session;
 import org.apache.wicket.WicketRuntimeException;
-import org.apache.wicket.markup.head.CssHeaderItem;
-import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.html.IHeaderContributor;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.protocol.http.ResourceIsolationRequestCycleListener;
 import org.apache.wicket.protocol.http.WebApplication;
@@ -74,7 +76,6 @@
 import org.apache.wicket.request.resource.AbstractResource;
 import org.apache.wicket.request.resource.IResource;
 import org.apache.wicket.request.resource.ResourceReference;
-import org.apache.wicket.resource.JQueryResourceReference;
 import org.apache.wicket.util.lang.Args;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -88,7 +89,7 @@
 
     private static final String ENDUSER_PROPERTIES = "enduser.properties";
 
-    private static final String CUSTOM_FORM_ATTRIBUTES_FILE = "customFormAttributes.json";
+    private static final String CUSTOM_FORM_LAYOUT_FILE = "customFormLayout.json";
 
     public static final List<Locale> SUPPORTED_LOCALES = List.of(
             Locale.ENGLISH, Locale.ITALIAN, new Locale("pt", "BR"), new Locale("ru"), Locale.JAPANESE);
@@ -125,11 +126,36 @@
 
     private Integer maxUploadFileSizeMB;
 
-    private FileAlterationMonitor customFormAttributesMonitor;
+    private FileAlterationMonitor customFormLayoutMonitor;
 
-    private Map<String, CustomAttributesInfo> customFormAttributes;
+    private Map<String, Class<? extends BasePage>> pageClasses;
 
-    protected void setSecurityHeaders(final Properties props, final WebResponse response) {
+    private Class<? extends Sidebar> sidebar;
+
+    private UserFormLayoutInfo customFormLayout;
+
+    @SuppressWarnings("unchecked")
+    protected void populatePageClasses(final Properties props) {
+        Enumeration<String> propNames = (Enumeration<String>) props.propertyNames();
+        while (propNames.hasMoreElements()) {
+            String className = propNames.nextElement();
+            if (className.startsWith("page.")) {
+                try {
+                    Class<?> clazz = ClassUtils.getClass(props.getProperty(className));
+                    if (BasePage.class.isAssignableFrom(clazz)) {
+                        pageClasses.put(
+                                StringUtils.substringAfter(className, "page."), (Class<? extends BasePage>) clazz);
+                    } else {
+                        LOG.warn("{} does not extend {}, ignoring...", clazz.getName(), BasePage.class.getName());
+                    }
+                } catch (ClassNotFoundException e) {
+                    LOG.error("While looking for class identified by property '{}'", className, e);
+                }
+            }
+        }
+    }
+
+    protected static void setSecurityHeaders(final Properties props, final WebResponse response) {
         @SuppressWarnings("unchecked")
         Enumeration<String> propNames = (Enumeration<String>) props.propertyNames();
         while (propNames.hasMoreElements()) {
@@ -199,33 +225,31 @@
             maxPoolSize = 50;
         }
 
-        // read customFormAttributes.json
-        File enduserDir;
-        try (InputStream is = SyncopeWebApplication.class.getResourceAsStream('/' + CUSTOM_FORM_ATTRIBUTES_FILE)) {
-            customFormAttributes = MAPPER.readValue(is,
-                    new TypeReference<HashMap<String, CustomAttributesInfo>>() {
+        // read customFormLayout.json
+        try (InputStream is = SyncopeWebApplication.class.getResourceAsStream('/' + CUSTOM_FORM_LAYOUT_FILE)) {
+            customFormLayout = MAPPER.readValue(is, new TypeReference<UserFormLayoutInfo>() {
             });
-            enduserDir = new File(props.getProperty("enduser.directory"));
+            File enduserDir = new File(props.getProperty("enduser.directory"));
             boolean existsEnduserDir = enduserDir.exists() && enduserDir.canRead() && enduserDir.isDirectory();
             if (existsEnduserDir) {
-                File customFormAttributesFile = FileUtils.getFile(enduserDir, CUSTOM_FORM_ATTRIBUTES_FILE);
-                if (customFormAttributesFile.exists()
-                        && customFormAttributesFile.canRead()
-                        && customFormAttributesFile.isFile()) {
-                    customFormAttributes = MAPPER.readValue(FileUtils.openInputStream(customFormAttributesFile),
-                            new TypeReference<HashMap<String, CustomAttributesInfo>>() {
+                File customFormLayoutFile = FileUtils.getFile(enduserDir, CUSTOM_FORM_LAYOUT_FILE);
+                if (customFormLayoutFile.exists()
+                        && customFormLayoutFile.canRead()
+                        && customFormLayoutFile.isFile()) {
+                    customFormLayout = MAPPER.readValue(FileUtils.openInputStream(customFormLayoutFile),
+                            new TypeReference<UserFormLayoutInfo>() {
                     });
                 }
             }
             FileAlterationObserver observer = existsEnduserDir
                     ? new FileAlterationObserver(
                             enduserDir,
-                            pathname -> StringUtils.contains(pathname.getPath(), CUSTOM_FORM_ATTRIBUTES_FILE))
+                            pathname -> StringUtils.contains(pathname.getPath(), CUSTOM_FORM_LAYOUT_FILE))
                     : new FileAlterationObserver(
-                            SyncopeWebApplication.class.getResource('/' + CUSTOM_FORM_ATTRIBUTES_FILE).getFile(),
-                            pathname -> StringUtils.contains(pathname.getPath(), CUSTOM_FORM_ATTRIBUTES_FILE));
+                            SyncopeWebApplication.class.getResource('/' + CUSTOM_FORM_LAYOUT_FILE).getFile(),
+                            pathname -> StringUtils.contains(pathname.getPath(), CUSTOM_FORM_LAYOUT_FILE));
 
-            customFormAttributesMonitor = new FileAlterationMonitor(5000);
+            customFormLayoutMonitor = new FileAlterationMonitor(5000);
 
             FileAlterationListener listener = new FileAlterationListenerAdaptor() {
 
@@ -233,13 +257,13 @@
                 public void onFileChange(final File file) {
                     try {
                         LOG.trace("{} has changed. Reloading form attributes customization configuration.",
-                                CUSTOM_FORM_ATTRIBUTES_FILE);
-                        customFormAttributes = MAPPER.readValue(FileUtils.openInputStream(file),
-                                new TypeReference<HashMap<String, CustomAttributesInfo>>() {
+                                CUSTOM_FORM_LAYOUT_FILE);
+                        customFormLayout = MAPPER.readValue(FileUtils.openInputStream(file),
+                                new TypeReference<UserFormLayoutInfo>() {
                         });
                     } catch (IOException e) {
                         LOG.error("{} While reading app customization configuration.",
-                                CUSTOM_FORM_ATTRIBUTES_FILE, e);
+                                CUSTOM_FORM_LAYOUT_FILE, e);
                     }
                 }
 
@@ -247,34 +271,44 @@
                 public void onFileCreate(final File file) {
                     try {
                         LOG.trace("{} has been created. Loading form attributes customization configuration.",
-                                CUSTOM_FORM_ATTRIBUTES_FILE);
-                        customFormAttributes = MAPPER.readValue(FileUtils.openInputStream(file),
-                                new TypeReference<HashMap<String, CustomAttributesInfo>>() {
+                                CUSTOM_FORM_LAYOUT_FILE);
+                        customFormLayout = MAPPER.readValue(FileUtils.openInputStream(file),
+                                new TypeReference<UserFormLayoutInfo>() {
                         });
                     } catch (IOException e) {
                         LOG.error("{} While reading app customization configuration.",
-                                CUSTOM_FORM_ATTRIBUTES_FILE, e);
+                                CUSTOM_FORM_LAYOUT_FILE, e);
                     }
                 }
 
                 @Override
                 public void onFileDelete(final File file) {
                     LOG.trace("{} has been deleted. Resetting form attributes customization configuration.",
-                            CUSTOM_FORM_ATTRIBUTES_FILE);
-                    customFormAttributes = null;
+                            CUSTOM_FORM_LAYOUT_FILE);
+                    customFormLayout = null;
                 }
             };
 
             observer.addListener(listener);
-            customFormAttributesMonitor.addObserver(observer);
-            customFormAttributesMonitor.start();
+            customFormLayoutMonitor.addObserver(observer);
+            customFormLayoutMonitor.start();
         } catch (Exception e) {
-            throw new WicketRuntimeException("Could not read " + CUSTOM_FORM_ATTRIBUTES_FILE, e);
+            throw new WicketRuntimeException("Could not read " + CUSTOM_FORM_LAYOUT_FILE, e);
         }
 
+        // process page properties
+        pageClasses = new HashMap<>();
+        populatePageClasses(props);
+        pageClasses = Collections.unmodifiableMap(pageClasses);
+
+        buildSidebarClass(props);
+
         // Application settings
         IBootstrapSettings settings = new BootstrapSettings();
 
+        // set theme provider
+        settings.setThemeProvider(new SingleThemeProvider(new AdminLTE()));
+
         // install application settings
         Bootstrap.install(this, settings);
 
@@ -282,25 +316,8 @@
         getResourceSettings().setUseDefaultOnMissingResource(true);
         getResourceSettings().setThrowExceptionOnMissingResource(false);
 
-        getJavaScriptLibrarySettings().setJQueryReference(JQueryResourceReference.getV2());
-
-        getResourceSettings().setThrowExceptionOnMissingResource(true);
-
         getMarkupSettings().setStripWicketTags(true);
         getMarkupSettings().setCompressWhitespace(true);
-        getMarkupSettings().setStripComments(true);
-
-        // add some css assets as Java Wicket resource in order to set Bootstrap css as a dependency of those
-        // and stop it to override the custom css rules
-        getHeaderContributorListeners().add(new IHeaderContributor() {
-
-            private static final long serialVersionUID = -8955205747168484695L;
-
-            @Override
-            public void renderHead(final IHeaderResponse response) {
-                response.render(CssHeaderItem.forReference(SyncopeEnduserCss.INSTANCE));
-            }
-        });
 
         getRequestCycleListeners().add(new SyncopeUIRequestCycleListener() {
 
@@ -318,7 +335,6 @@
             protected IRequestablePage getErrorPage(final PageParameters errorParameters) {
                 return new Login(errorParameters);
             }
-
         });
 
         if (BooleanUtils.toBoolean(props.getProperty("x-forward"))) {
@@ -388,11 +404,11 @@
 
     @Override
     protected void onDestroy() {
-        if (customFormAttributesMonitor != null) {
+        if (customFormLayoutMonitor != null) {
             try {
-                customFormAttributesMonitor.stop(0);
+                customFormLayoutMonitor.stop(0);
             } catch (Exception e) {
-                LOG.error("{} While stopping file monitor", CUSTOM_FORM_ATTRIBUTES_FILE, e);
+                LOG.error("{} While stopping file monitor", CUSTOM_FORM_LAYOUT_FILE, e);
             }
         }
     }
@@ -400,11 +416,37 @@
     @Override
     public Class<? extends Page> getHomePage() {
         return SyncopeEnduserSession.get().isAuthenticated()
-                && SyncopeEnduserSession.get().getSelfTO().isMustChangePassword()
+                && SyncopeEnduserSession.get().isMustChangePassword()
                 ? MustChangePassword.class
                 : SyncopeEnduserSession.get().isAuthenticated()
-                ? Self.class
-                : Login.class;
+                ? getPageClass("profile", Dashboard.class)
+                : getSignInPageClass();
+    }
+
+    public ClassPathScanImplementationLookup getLookup() {
+        return lookup;
+    }
+
+    @SuppressWarnings("unchecked")
+    private void buildSidebarClass(final Properties props) {
+        try {
+            Class<?> clazz = ClassUtils.getClass(props.getProperty("sidebar", Sidebar.class.getCanonicalName()));
+            if (Sidebar.class.isAssignableFrom(clazz)) {
+                sidebar = (Class<? extends Sidebar>) clazz;
+            } else {
+                LOG.warn("{} does not extend {}, ignoring...", clazz.getName(), Sidebar.class.getName());
+            }
+        } catch (ClassNotFoundException e) {
+            LOG.error("While looking for class identified by property 'sidebar'", e);
+        }
+    }
+
+    public UserFormLayoutInfo getCustomFormLayout() {
+        return customFormLayout;
+    }
+
+    public Class<? extends Sidebar> getSidebar() {
+        return sidebar;
     }
 
     @Override
@@ -418,7 +460,15 @@
                 setUseCompression(useGZIPCompression);
     }
 
-    protected static Class<? extends WebPage> getSignInPageClass() {
+    public Class<? extends BasePage> getPageClass(final String key) {
+        return pageClasses.get(key);
+    }
+
+    public Class<? extends BasePage> getPageClass(final String key, final Class<? extends BasePage> defaultValue) {
+        return pageClasses.getOrDefault(key, defaultValue);
+    }
+
+    protected Class<? extends WebPage> getSignInPageClass() {
         return Login.class;
     }
 
@@ -458,12 +508,4 @@
         return maxWaitTime;
     }
 
-    public Map<String, CustomAttributesInfo> getCustomFormAttributes() {
-        return customFormAttributes;
-    }
-
-    public void setCustomFormAttributes(final Map<String, CustomAttributesInfo> customFormAttributes) {
-        this.customFormAttributes.clear();
-        this.customFormAttributes.putAll(customFormAttributes);
-    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/assets/SyncopeEnduserCss.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/assets/SyncopeEnduserCss.java
deleted file mode 100644
index bc25789..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/assets/SyncopeEnduserCss.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.assets;
-
-import de.agilecoders.wicket.core.Bootstrap;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.wicket.markup.head.CssHeaderItem;
-import org.apache.wicket.markup.head.HeaderItem;
-import org.apache.wicket.request.resource.CssResourceReference;
-
-public class SyncopeEnduserCss extends CssResourceReference {
-
-    private static final long serialVersionUID = 7244898174745686253L;
-
-    /**
-     * Singleton instance of this reference.
-     */
-    public static final SyncopeEnduserCss INSTANCE = new SyncopeEnduserCss();
-
-    public SyncopeEnduserCss() {
-        super(SyncopeEnduserCss.class, "css/syncopeEnduser.css");
-    }
-
-    @Override
-    public List<HeaderItem> getDependencies() {
-        final List<HeaderItem> dependencies = new ArrayList<>();
-        dependencies.add(CssHeaderItem.forReference(Bootstrap.getSettings().getCssResourceReference()));
-        dependencies.addAll(super.getDependencies());
-        return dependencies;
-    }
-
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseExtPage.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/commons/EnduserConstants.java
similarity index 60%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseExtPage.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/commons/EnduserConstants.java
index ace1d2f..abdf652 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseExtPage.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/commons/EnduserConstants.java
@@ -16,19 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.pages;
+package org.apache.syncope.client.enduser.commons;
 
-import org.apache.wicket.request.mapper.parameter.PageParameters;
+public final class EnduserConstants {
 
-public abstract class BaseExtPage extends BaseEnduserWebPage {
+    public static final String STATUS = "status";
 
-    private static final long serialVersionUID = 4627828052717627159L;
+    public static final String SUCCESS = "success";
 
-    public BaseExtPage() {
-        super();
-    }
+    public static final String LANDING_PAGE = "landingPage";
 
-    public BaseExtPage(final PageParameters parameters) {
-        super(parameters);
+    public static final String CONTENT_PANEL = "contentPanel";
+
+    public static final String SELF_ALLOWED = "selfRegistration.allowed";
+
+    public static final String PAGE_TITLE = "pageTitle";
+
+    private EnduserConstants() {
+        // private constructor for static utility class
     }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java
index c89afff..6f1e334 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/init/ClassPathScanImplementationLookup.java
@@ -24,8 +24,7 @@
 import java.util.List;
 import java.util.Objects;
 import org.apache.commons.lang3.ArrayUtils;
-import org.apache.syncope.client.enduser.pages.BaseEnduserWebPage;
-import org.apache.syncope.client.enduser.pages.BaseExtPage;
+import org.apache.syncope.client.enduser.pages.BasePage;
 import org.apache.syncope.client.ui.commons.annotations.BinaryPreview;
 import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.ui.commons.annotations.Resource;
@@ -50,9 +49,7 @@
 
     private List<Class<? extends BinaryPreviewer>> previewers;
 
-    private List<Class<? extends BaseExtPage>> extPages;
-
-    private List<Class<? extends BaseEnduserWebPage>> pages;
+    private List<Class<? extends BasePage>> extPages;
 
     /**
      * This method can be overridden by subclasses to customize classpath scan.
@@ -63,9 +60,8 @@
         return DEFAULT_BASE_PACKAGE;
     }
 
-    @SuppressWarnings("unchecked")
+    @SuppressWarnings({ "unchecked", "unchecked" })
     public void load() {
-        pages = new ArrayList<>();
         previewers = new ArrayList<>();
         extPages = new ArrayList<>();
         ssoLoginFormPanels = new ArrayList<>();
@@ -73,10 +69,9 @@
 
         ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
         scanner.addIncludeFilter(new AssignableTypeFilter(AbstractResource.class));
-        scanner.addIncludeFilter(new AssignableTypeFilter(BaseExtPage.class));
+        scanner.addIncludeFilter(new AssignableTypeFilter(BasePage.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(BaseSSOLoginFormPanel.class));
         scanner.addIncludeFilter(new AssignableTypeFilter(BinaryPreviewer.class));
-        scanner.addIncludeFilter(new AssignableTypeFilter(BaseEnduserWebPage.class));
 
         scanner.findCandidateComponents(getBasePackage()).forEach(bd -> {
             try {
@@ -84,9 +79,9 @@
                         ClassUtils.getDefaultClassLoader());
                 boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
                 if (!isAbstractClazz) {
-                    if (BaseExtPage.class.isAssignableFrom(clazz)) {
+                    if (BasePage.class.isAssignableFrom(clazz)) {
                         if (clazz.isAnnotationPresent(ExtPage.class)) {
-                            extPages.add((Class<? extends BaseExtPage>) clazz);
+                            extPages.add((Class<? extends BasePage>) clazz);
                         } else {
                             LOG.error("Could not find annotation {} in {}, ignoring",
                                     ExtPage.class.getName(), clazz.getName());
@@ -102,8 +97,6 @@
                         previewers.add((Class<? extends BinaryPreviewer>) clazz);
                     } else if (BaseSSOLoginFormPanel.class.isAssignableFrom(clazz)) {
                         ssoLoginFormPanels.add((Class<? extends BaseSSOLoginFormPanel>) clazz);
-                    } else if (BaseEnduserWebPage.class.isAssignableFrom(clazz)) {
-                        pages.add((Class<? extends BaseEnduserWebPage>) clazz);
                     }
                 }
             } catch (Throwable t) {
@@ -114,8 +107,6 @@
 
         ssoLoginFormPanels = Collections.unmodifiableList(ssoLoginFormPanels);
 
-        pages = Collections.unmodifiableList(pages);
-
         LOG.debug("Binary previewers found: {}", previewers);
         LOG.debug("Extension pages found: {}", extPages);
         LOG.debug("SSO Login pages found: {}", ssoLoginFormPanels);
@@ -144,11 +135,7 @@
         return this.ssoLoginFormPanels;
     }
 
-    public List<Class<? extends BaseExtPage>> getExtPageClasses() {
+    public List<Class<? extends BasePage>> getExtPageClasses() {
         return extPages;
     }
-
-    public List<Class<? extends BaseEnduserWebPage>> getPageClasses() {
-        return pages;
-    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/AnyLayoutUtils.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/AnyLayoutUtils.java
deleted file mode 100644
index 5739f02..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/AnyLayoutUtils.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.layout;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.util.List;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.ui.commons.wizards.ModalPanelBuilder;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.wicket.PageReference;
-
-/**
- * Utility methods for dealing with form layout information.
- */
-public final class AnyLayoutUtils {
-
-    private static final ObjectMapper MAPPER;
-
-    private static final String DEFAULT_USER_FORM_LAYOUT_INFO;
-
-    static {
-        MAPPER = new ObjectMapper();
-        try {
-            DEFAULT_USER_FORM_LAYOUT_INFO = MAPPER.writeValueAsString(new UserFormLayoutInfo());
-        } catch (IOException e) {
-            throw new IllegalArgumentException("While generating default enduser layout info for "
-                    + SyncopeEnduserSession.get().getSelfTO().getUsername(), e);
-        }
-    }
-
-    public static String getDefaultValue() {
-        return DEFAULT_USER_FORM_LAYOUT_INFO;
-    }
-
-    public static UserFormLayoutInfo fromJsonString(final String content) {
-        try {
-            return MAPPER.readValue(content, UserFormLayoutInfo.class);
-        } catch (IOException e) {
-            throw new IllegalArgumentException("While parsing console layout info for "
-                    + SyncopeEnduserSession.get().getSelfTO().getUsername(), e);
-        }
-    }
-
-    public static String defaultConsoleLayoutInfoIfEmpty(final String content) {
-        String result;
-
-        if (StringUtils.isBlank(content)) {
-            try {
-                ObjectNode tree = MAPPER.createObjectNode();
-
-                tree.set(AnyTypeKind.USER.name(), MAPPER.valueToTree(new UserFormLayoutInfo()));
-
-                result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(tree);
-            } catch (IOException e) {
-                throw new IllegalArgumentException("While generating default console layout info for "
-                        + SyncopeEnduserSession.get().getSelfTO().getUsername(), e);
-            }
-        } else {
-            try {
-                result = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(MAPPER.readTree(content));
-            } catch (IOException e) {
-                result = content;
-            }
-        }
-
-        return result;
-    }
-
-    public static ModalPanelBuilder<AnyWrapper<UserTO>> newUserWizardBuilder(
-            final UserTO userTO,
-            final List<String> anyTypeClasses,
-            final UserFormLayoutInfo userFormLayoutInfo,
-            final PageReference pageRef) {
-
-        try {
-            return userFormLayoutInfo.getFormClass().getConstructor(
-                    userTO.getClass(), // previous
-                    userTO.getClass(), // actual
-                    List.class,
-                    userFormLayoutInfo.getClass(),
-                    pageRef.getClass()).
-                    newInstance(null, userTO, anyTypeClasses, userFormLayoutInfo, pageRef);
-
-        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
-                | IllegalArgumentException | InvocationTargetException e) {
-            throw new IllegalArgumentException(
-                    "Could not instantiate " + userFormLayoutInfo.getFormClass().getName(), e);
-        }
-    }
-
-    private AnyLayoutUtils() {
-        // private constructor for static utility class
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/CustomizationOption.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/CustomizationOption.java
index c716f07..0fe471f 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/CustomizationOption.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/CustomizationOption.java
@@ -30,9 +30,6 @@
 
     private List<String> defaultValues = new ArrayList<>();
 
-    public CustomizationOption() {
-    }
-
     public boolean isReadonly() {
         return readonly;
     }
@@ -48,15 +45,4 @@
     public void setDefaultValues(final List<String> defaultValues) {
         this.defaultValues = defaultValues;
     }
-
-    public CustomizationOption readonly(final Boolean value) {
-        this.readonly = value;
-        return this;
-    }
-
-    public CustomizationOption defaultValues(final List<String> value) {
-        this.defaultValues = value;
-        return this;
-    }
-
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayoutInfo.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayoutInfo.java
index 477c1d1..0c81adc 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayoutInfo.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayoutInfo.java
@@ -20,10 +20,10 @@
 
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.syncope.client.enduser.wizards.any.UserWizardBuilder;
+import org.apache.syncope.client.enduser.panels.UserFormPanel;
 import org.apache.syncope.client.ui.commons.layout.AbstractAnyFormBaseLayout;
-import org.apache.syncope.client.ui.commons.layout.UserForm;
 import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.client.ui.commons.layout.UserForm;
 
 public class UserFormLayoutInfo extends AbstractAnyFormBaseLayout<UserTO, UserForm> {
 
@@ -37,6 +37,8 @@
 
     private boolean passwordManagement = true;
 
+    private boolean detailsManagement = true;
+
     public Map<String, CustomizationOption> getWhichPlainAttrs() {
         return whichPlainAttrs;
     }
@@ -51,7 +53,7 @@
 
     @Override
     protected Class<? extends UserForm> getDefaultFormClass() {
-        return UserWizardBuilder.class;
+        return UserFormPanel.class;
     }
 
     public boolean isPasswordManagement() {
@@ -61,4 +63,12 @@
     public void setPasswordManagement(final boolean passwordManagement) {
         this.passwordManagement = passwordManagement;
     }
+
+    public boolean isDetailsManagement() {
+        return detailsManagement;
+    }
+
+    public void setDetailsManagement(final boolean detailsManagement) {
+        this.detailsManagement = detailsManagement;
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/AjaxDownload.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/AjaxDownload.java
deleted file mode 100644
index 8e1367a..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/AjaxDownload.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.markup.html.form;
-
-import java.time.Duration;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.ui.commons.HttpResourceStream;
-import org.apache.syncope.client.ui.commons.MIMETypesLoader;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.behavior.AbstractAjaxBehavior;
-import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
-import org.apache.wicket.request.resource.ContentDisposition;
-import org.apache.wicket.spring.injection.annot.SpringBean;
-
-public abstract class AjaxDownload extends AbstractAjaxBehavior {
-
-    private static final long serialVersionUID = 7203445884857810583L;
-
-    @SpringBean
-    private MIMETypesLoader mimeTypesLoader;
-
-    private final String name;
-
-    private String fileKey;
-
-    private String mimeType;
-
-    private final boolean addAntiCache;
-
-    public AjaxDownload(final String name, final boolean addAntiCache) {
-        super();
-        this.name = name;
-        this.addAntiCache = addAntiCache;
-    }
-
-    public AjaxDownload(final String name, final String fileKey, final String mimeType, final boolean addAntiCache) {
-        this(name, addAntiCache);
-        this.fileKey = fileKey;
-        this.mimeType = mimeType;
-    }
-
-    public void initiate(final AjaxRequestTarget target) {
-
-        String url = getCallbackUrl().toString();
-        if (addAntiCache) {
-            url = url + (url.contains("?") ? "&" : "?");
-            url = url + "antiCache=" + System.currentTimeMillis();
-        }
-        target.appendJavaScript("setTimeout(\"window.location.href='" + url + "'\", 100);");
-    }
-
-    @Override
-    public void onRequest() {
-        HttpResourceStream stream = getResourceStream();
-        ResourceStreamRequestHandler handler = new ResourceStreamRequestHandler(stream);
-        String key = StringUtils.isNotBlank(fileKey) ? fileKey + '_' : "";
-        String ext = "";
-        if (StringUtils.isNotBlank(mimeType)) {
-            String extByMimeType = mimeTypesLoader.getFileExt(mimeType);
-            ext = StringUtils.isBlank(extByMimeType) ? ".bin" : ('.' + extByMimeType);
-        }
-        String fileName = key + (stream.getFilename() == null ? name : stream.getFilename()) + ext;
-
-        handler.setFileName(fileName);
-        handler.setContentDisposition(ContentDisposition.ATTACHMENT);
-        handler.setCacheDuration(Duration.ZERO);
-        getComponent().getRequestCycle().scheduleRequestHandlerAfterCurrent(handler);
-    }
-
-    protected abstract HttpResourceStream getResourceStream();
-
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java
index 512283d..f8d88a2 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.java
@@ -30,17 +30,19 @@
 import java.util.Base64;
 import java.util.Locale;
 import java.util.Optional;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.ui.commons.HttpResourceStream;
 import org.apache.syncope.client.enduser.SyncopeWebApplication;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.commons.PreviewUtils;
 import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.markup.html.form.BaseBinaryFieldPanel;
+import org.apache.syncope.client.ui.commons.HttpResourceStream;
 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.preview.BinaryPreviewer;
+import org.apache.syncope.client.ui.commons.markup.html.form.BaseBinaryFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.BinaryFieldDownload;
 import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.rest.ResponseHolder;
 import org.apache.wicket.Component;
@@ -82,7 +84,7 @@
 
     private final BootstrapFileInputField fileUpload;
 
-    private final AjaxDownload fileDownload;
+    private final BinaryFieldDownload fileDownload;
 
     private final BinaryPreviewer previewer;
 
@@ -100,6 +102,7 @@
             final IModel<String> model,
             final String mimeType,
             final String fileKey) {
+
         super(id, name, model);
         this.model = model;
         this.fileKey = fileKey;
@@ -122,7 +125,7 @@
             public void renderHead(final IHeaderResponse response) {
                 if (previewer == null) {
                     FileinputJsReference.INSTANCE.renderHead(response);
-                    final JQuery fileinputJS = $(fileUpload).chain(new IFunction() {
+                    JQuery fileinputJS = $(fileUpload).chain(new IFunction() {
 
                         private static final long serialVersionUID = -2285418135375523652L;
 
@@ -150,7 +153,7 @@
 
         uploadForm.add(new Label("preview", StringUtils.isBlank(mimeType) ? StringUtils.EMPTY : '(' + mimeType + ')'));
 
-        fileDownload = new AjaxDownload(name, fileKey, mimeType, true) {
+        fileDownload = new BinaryFieldDownload(name, fileKey, mimeType, true) {
 
             private static final long serialVersionUID = 7203445884857810583L;
 
@@ -185,10 +188,7 @@
         if (!Locale.ENGLISH.getLanguage().equals(language)) {
             config.withLocale(language);
         }
-
         fileUpload = new BootstrapFileInputField("fileUpload", new ListModel<>(new ArrayList<>()), config);
-        fileUpload.setOutputMarkupId(true);
-
         fileUpload.add(new AjaxFormSubmitBehavior(Constants.ON_CHANGE) {
 
             private static final long serialVersionUID = -1107858522700306810L;
@@ -253,11 +253,13 @@
 
     private Response buildResponse() {
         return Response.ok(new ByteArrayInputStream(Base64.getMimeDecoder().decode(getModelObject()))).
-                type(StringUtils.isBlank(mimeType) ? MediaType.APPLICATION_OCTET_STREAM : mimeType).build();
+                type(StringUtils.isBlank(mimeType) ? MediaType.APPLICATION_OCTET_STREAM : mimeType).
+                header(HttpHeaders.LOCATION, StringUtils.EMPTY).
+                build();
     }
 
     private void changePreviewer(final Component panelPreview) {
-        final Fragment fragment = new Fragment("panelPreview", "previewFragment", container);
+        Fragment fragment = new Fragment("panelPreview", "previewFragment", container);
         fragment.add(panelPreview);
         container.addOrReplace(fragment);
         uploadForm.addOrReplace(container);
@@ -297,4 +299,11 @@
     protected Integer getMaxUploadFileSizeMB() {
         return SyncopeWebApplication.get().getMaxUploadFileSizeMB();
     }
+
+    @Override
+    public FieldPanel<String> setReadOnly(final boolean readOnly) {
+        super.setReadOnly(readOnly);
+        fileUpload.setEnabled(!readOnly);
+        return this;
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
deleted file mode 100644
index fa87444..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/navigation/Navbar.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.navigation;
-
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.pages.BaseExtPage;
-import org.apache.syncope.client.enduser.pages.Logout;
-import org.apache.syncope.client.enduser.pages.Self;
-import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.annotations.ExtPage;
-import org.apache.wicket.Component;
-import org.apache.wicket.Page;
-import org.apache.wicket.ajax.AjaxEventBehavior;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.attributes.AjaxCallListener;
-import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
-import org.apache.wicket.ajax.markup.html.AjaxLink;
-import org.apache.wicket.behavior.Behavior;
-import org.apache.wicket.markup.ComponentTag;
-import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.list.ListItem;
-import org.apache.wicket.markup.html.list.ListView;
-import org.apache.wicket.markup.html.panel.Panel;
-
-public class Navbar extends Panel {
-
-    private static final long serialVersionUID = 1323251762654401168L;
-
-    private final ListView<Class<? extends BaseExtPage>> extPages;
-
-    private final List<WebMarkupContainer> navbarItems = new ArrayList<>();
-
-    public Navbar(final String id, final List<Class<? extends BaseExtPage>> extPageClasses) {
-        super(id);
-        setOutputMarkupId(true);
-
-        WebMarkupContainer detailsLI = new WebMarkupContainer("detailsLI");
-        detailsLI.setMarkupId("self");
-        navbarItems.add(detailsLI);
-        add(detailsLI);
-
-        BookmarkablePageLink<Page> detailsLink = new BookmarkablePageLink<>("detailsLILink", Self.class);
-        detailsLink.setOutputMarkupId(true);
-        detailsLink.add(new Label("detailsLILabel", getString("details")));
-        detailsLI.add(detailsLink);
-
-        WebMarkupContainer extLI = new WebMarkupContainer("extensionsLI");
-        extLI.setOutputMarkupPlaceholderTag(true);
-        extLI.setVisible(!extPageClasses.isEmpty());
-        add(extLI);
-
-        extPages = new ListView<Class<? extends BaseExtPage>>("extPages", extPageClasses) {
-
-            private static final long serialVersionUID = 4949588177564901031L;
-
-            @Override
-            protected void populateItem(final ListItem<Class<? extends BaseExtPage>> item) {
-                WebMarkupContainer extPageLI = new WebMarkupContainer("extPageLI");
-                item.add(extPageLI);
-                extPageLI.setMarkupId(item.getModelObject().getSimpleName().toLowerCase());
-                navbarItems.add(extPageLI);
-
-                ExtPage ann = item.getModelObject().getAnnotation(ExtPage.class);
-
-                BookmarkablePageLink<Page> extLIPageLink =
-                        new BookmarkablePageLink<>("extPageLILink", item.getModelObject());
-                extLIPageLink.setOutputMarkupId(true);
-                extLIPageLink.add(new Label("extPageLabel", ann.label()));
-                extPageLI.add(extLIPageLink);
-            }
-        };
-        extPages.setOutputMarkupId(true);
-        extPages.setVisible(true);
-        extLI.add(extPages);
-
-        WebMarkupContainer logoLinkWmc = new WebMarkupContainer("logoIcon");
-        logoLinkWmc.add(new AjaxEventBehavior("click") {
-
-            private static final long serialVersionUID = -4255753643957306394L;
-
-            @Override
-            protected void onEvent(final AjaxRequestTarget target) {
-                setResponsePage(getApplication().getHomePage());
-            }
-        });
-        add(logoLinkWmc);
-
-        @SuppressWarnings("unchecked")
-        final Class<? extends WebPage> beforeLogout = (Class<? extends WebPage>) SyncopeEnduserSession.get().
-                getAttribute(Constants.BEFORE_LOGOUT_PAGE);
-        if (beforeLogout == null) {
-            add(new BookmarkablePageLink<>("logout", Logout.class));
-        } else {
-            add(new AjaxLink<Page>("logout") {
-
-                private static final long serialVersionUID = -4889563567201424183L;
-
-                @Override
-                protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
-                    super.updateAjaxAttributes(attributes);
-
-                    AjaxCallListener ajaxCallListener = new AjaxCallListener();
-                    ajaxCallListener.onPrecondition("return confirm('" + getString("confirmGlobalLogout") + "');");
-                    attributes.getAjaxCallListeners().add(ajaxCallListener);
-                }
-
-                @Override
-                public void onClick(final AjaxRequestTarget target) {
-                    setResponsePage(beforeLogout);
-                }
-            });
-        }
-    }
-
-    public ListView<Class<? extends BaseExtPage>> getExtPages() {
-        return extPages;
-    }
-
-    public void setActiveNavItem(final String id) {
-        navbarItems.stream().
-                filter(containingLI -> containingLI.getMarkupId().equals(id)).findFirst().
-                ifPresent(found -> found.add(new Behavior() {
-
-            private static final long serialVersionUID = -5775607340182293596L;
-
-            @Override
-            public void onComponentTag(final Component component, final ComponentTag tag) {
-                tag.put("class", "active");
-            }
-        }));
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AbstractChangePassword.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AbstractChangePassword.java
new file mode 100644
index 0000000..5944754
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AbstractChangePassword.java
@@ -0,0 +1,89 @@
+/*
+ * 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 org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
+import org.apache.syncope.client.enduser.panels.ChangePasswordPanel;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public abstract class AbstractChangePassword extends BasePage {
+
+    private static final long serialVersionUID = 5889157642852559004L;
+
+    private static final String CHANGE_PASSWORD = "page.changePassword";
+
+    public AbstractChangePassword(final PageParameters parameters) {
+        super(parameters, CHANGE_PASSWORD);
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        contentWrapper.add(content);
+
+        ChangePasswordPanel changePasswordPanel = getPasswordPanel();
+        content.add(changePasswordPanel);
+        content.add(new AttributeModifier("style", "height: \"100%\""));
+    }
+
+    protected ChangePasswordPanel getPasswordPanel() {
+        ChangePasswordPanel changePasswordPanel = new ChangePasswordPanel("changePasswordPanel", notificationPanel) {
+
+            private static final long serialVersionUID = 5195544218030499386L;
+
+            @Override
+            protected void doSubmit(final AjaxRequestTarget target, final AjaxPasswordFieldPanel passwordField) {
+                boolean checked = true;
+                if (SyncopeWebApplication.get().isCaptchaEnabled()) {
+                    checked = captcha.check();
+                }
+                if (!checked) {
+                    SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
+                    getNotificationPanel().refresh(target);
+                } else {
+                    doPwdSubmit(target, passwordField);
+                }
+            }
+
+            @Override
+            protected void doCancel() {
+                doPwdCancel();
+            }
+
+            @Override
+            protected UserTO getLoggedUser() {
+                return getPwdLoggedUser();
+            }
+        };
+
+        changePasswordPanel.setOutputMarkupId(true);
+        return changePasswordPanel;
+    }
+
+    protected abstract void doPwdSubmit(AjaxRequestTarget target, AjaxPasswordFieldPanel passwordField);
+
+    protected abstract void doPwdCancel();
+
+    protected abstract UserTO getPwdLoggedUser();
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
deleted file mode 100644
index e7503bf..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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 org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
-import org.apache.syncope.client.enduser.navigation.Navbar;
-import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.spring.injection.annot.SpringBean;
-
-public class BaseEnduserWebPage extends BaseWebPage {
-
-    private static final long serialVersionUID = 5760583420031293480L;
-
-    protected final Navbar navbar;
-
-    @SpringBean
-    protected ClassPathScanImplementationLookup lookup;
-
-    public BaseEnduserWebPage() {
-        this(null);
-
-        body.add(navbar);
-    }
-
-    public BaseEnduserWebPage(final PageParameters parameters) {
-        super(parameters);
-
-        navbar = new Navbar("navbar", lookup.getExtPageClasses());
-        body.add(navbar);
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BasePage.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BasePage.java
new file mode 100644
index 0000000..eb238da
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/BasePage.java
@@ -0,0 +1,161 @@
+/*
+ * 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.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
+import org.apache.syncope.client.enduser.wicket.markup.head.MetaHeaderItem;
+import org.apache.syncope.client.ui.commons.BaseSession;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.enduser.panels.Sidebar;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Page;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxCallListener;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public class BasePage extends BaseWebPage {
+
+    private static final long serialVersionUID = 1571997737305598502L;
+
+    @SpringBean
+    private ClassPathScanImplementationLookup lookup;
+
+    protected static final HeaderItem META_IE_EDGE = new MetaHeaderItem("X-UA-Compatible", "IE=edge");
+
+    protected final Sidebar sidebar;
+
+    protected final WebMarkupContainer contentWrapper;
+
+    protected final AjaxLink<Void> collapse;
+
+    public BasePage() {
+        this(null, null);
+    }
+
+    public BasePage(final PageParameters parameters, final String name) {
+        super(parameters);
+
+        Serializable leftMenuCollapse = SyncopeEnduserSession.get().getAttribute(Constants.MENU_COLLAPSE);
+        if ((leftMenuCollapse instanceof Boolean) && ((Boolean) leftMenuCollapse)) {
+            body.add(new AttributeAppender("class", " sidebar-collapse"));
+        }
+
+        // sidebar
+        Class<? extends Sidebar> clazz = SyncopeWebApplication.get().getSidebar();
+
+        try {
+            sidebar = clazz.getConstructor(
+                    String.class,
+                    PageReference.class,
+                    List.class).
+                    newInstance("sidebar", getPageReference(), lookup.getExtPageClasses());
+        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
+                | IllegalArgumentException | InvocationTargetException e) {
+            throw new IllegalArgumentException("Could not instantiate " + clazz.getName(), e);
+        }
+
+        sidebar.setOutputMarkupPlaceholderTag(true);
+        body.add(sidebar);
+
+        // contentWrapper
+        contentWrapper = new WebMarkupContainer("contentWrapper");
+        contentWrapper.setOutputMarkupPlaceholderTag(true);
+        body.add(contentWrapper);
+
+        //pageTitle
+        addPageTitle(name);
+
+        // collapse
+        collapse = new AjaxLink<Void>("collapse") {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                Session.get().setAttribute(Constants.MENU_COLLAPSE,
+                        Session.get().getAttribute(Constants.MENU_COLLAPSE) == null
+                        ? true
+                        : !(Boolean) Session.get().getAttribute(Constants.MENU_COLLAPSE));
+            }
+        };
+        collapse.setOutputMarkupPlaceholderTag(true);
+        body.add(collapse);
+
+        @SuppressWarnings("unchecked")
+        Class<? extends WebPage> beforeLogout = (Class<? extends WebPage>) Session.get().
+                getAttribute(Constants.BEFORE_LOGOUT_PAGE);
+        if (beforeLogout == null) {
+            body.add(new BookmarkablePageLink<>("logout", Logout.class));
+        } else {
+            body.add(new AjaxLink<Page>("logout") {
+
+                private static final long serialVersionUID = -7978723352517770644L;
+
+                @Override
+                protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
+                    super.updateAjaxAttributes(attributes);
+
+                    AjaxCallListener ajaxCallListener = new AjaxCallListener();
+                    ajaxCallListener.onPrecondition("return confirm('" + getString("confirmGlobalLogout") + "');");
+                    attributes.getAjaxCallListeners().add(ajaxCallListener);
+                }
+
+                @Override
+                public void onClick(final AjaxRequestTarget target) {
+                    setResponsePage(beforeLogout);
+                }
+            });
+        }
+    }
+
+    protected void addPageTitle(final String title) {
+        contentWrapper.addOrReplace(new Label(EnduserConstants.PAGE_TITLE, new ResourceModel(title, title)));
+    }
+
+    protected void disableSidebar() {
+        sidebar.setVisible(false);
+        collapse.setVisible(false);
+        contentWrapper.add(new AttributeModifier("style", "margin-left: 0px"));
+    }
+
+    protected void setDomain(final PageParameters parameters) {
+        if (parameters != null && !parameters.get("domain").isEmpty()) {
+            BaseSession.class.cast(Session.get()).setDomain(parameters.get("domain").toString());
+        }
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Dashboard.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Dashboard.java
new file mode 100644
index 0000000..c99c789
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Dashboard.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.apache.syncope.client.enduser.widgets.UserProfileWidget;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class Dashboard extends BasePage {
+
+    private static final long serialVersionUID = -1100228004207271270L;
+
+    protected static final String HOME = "home";
+
+    protected final WebMarkupContainer content;
+
+    public Dashboard(final PageParameters parameters) {
+        super(parameters, HOME);
+
+        content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+
+        UserProfileWidget userProfileWidget = new UserProfileWidget("userProfileInfo");
+        userProfileWidget.setOutputMarkupId(true);
+        content.add(userProfileWidget);
+
+        contentWrapper.add(content);
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditChangePassword.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditChangePassword.java
new file mode 100644
index 0000000..fb54b7a
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditChangePassword.java
@@ -0,0 +1,80 @@
+/*
+ * 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 org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
+import org.apache.syncope.common.lib.request.PasswordPatch;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class EditChangePassword extends AbstractChangePassword {
+
+    private static final long serialVersionUID = -537205681762708502L;
+
+    private final UserSelfRestClient userSelfRestClient = new UserSelfRestClient();
+
+    public EditChangePassword(final PageParameters parameters) {
+        super(parameters);
+    }
+
+    @Override
+    protected void doPwdSubmit(final AjaxRequestTarget target, final AjaxPasswordFieldPanel passwordField) {
+        PageParameters parameters = new PageParameters();
+        try {
+            UserTO userTO = getPwdLoggedUser();
+
+            UserUR req = new UserUR();
+            req.setKey(userTO.getKey());
+            req.setPassword(new PasswordPatch.Builder().
+                    value(passwordField.getModelObject()).onSyncope(true).resources(userTO.getResources()).build());
+            userSelfRestClient.update(userTO.getETagValue(), req);
+
+            parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+            parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.pwd.change.success.msg"));
+            parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.pwd.change.success"));
+            SyncopeEnduserSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+            parameters.add(
+                    EnduserConstants.LANDING_PAGE,
+                    SyncopeWebApplication.get().getPageClass("profile", Dashboard.class).getName());
+            setResponsePage(SelfResult.class, parameters);
+        } catch (Exception e) {
+            LOG.error("While changing password for {}",
+                    SyncopeEnduserSession.get().getSelfTO().getUsername(), e);
+            SyncopeEnduserSession.get().onException(e);
+            notificationPanel.refresh(target);
+        }
+    }
+
+    @Override
+    protected UserTO getPwdLoggedUser() {
+        return SyncopeEnduserSession.get().getSelfTO(true);
+    }
+
+    @Override
+    protected void doPwdCancel() {
+        setResponsePage(getApplication().getHomePage());
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.java
new file mode 100644
index 0000000..3a63cad
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.java
@@ -0,0 +1,210 @@
+/*
+ * 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.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.panels.captcha.CaptchaPanel;
+import org.apache.syncope.client.enduser.rest.SecurityQuestionRestClient;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
+import org.apache.syncope.client.ui.commons.panels.CardPanel;
+import org.apache.syncope.common.lib.request.StringReplacePatchItem;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.ajax.AjaxEventBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.markup.html.form.StatelessForm;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class EditSecurityQuestion extends BasePage {
+
+    private static final long serialVersionUID = -537205681762708502L;
+
+    private static final String EDIT_SECURITY_QUESTION = "page.editSecurityQuestion";
+
+    private final UserSelfRestClient userSelfRestClient = new UserSelfRestClient();
+
+    private final AjaxDropDownChoicePanel<String> securityQuestion;
+
+    private final FieldPanel<String> securityAnswer;
+
+    private final UserTO userTO;
+
+    public EditSecurityQuestion(final PageParameters parameters) {
+        super(parameters, EDIT_SECURITY_QUESTION);
+
+        userTO = SyncopeEnduserSession.get().getSelfTO(true);
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        contentWrapper.add(content);
+
+        StatelessForm<Void> form = new StatelessForm<>("securityQuestionForm");
+        form.setOutputMarkupId(true);
+        content.add(form);
+
+        securityQuestion = new AjaxDropDownChoicePanel<>("securityQuestion",
+                "securityQuestion", new PropertyModel<>(userTO, "securityQuestion"));
+        securityQuestion.setNullValid(true);
+
+        List<SecurityQuestionTO> securityQuestions = SecurityQuestionRestClient.list();
+        securityQuestion.setChoices(securityQuestions.stream().
+                map(SecurityQuestionTO::getKey).collect(Collectors.toList()));
+        securityQuestion.setChoiceRenderer(new IChoiceRenderer<String>() {
+
+            private static final long serialVersionUID = -4421146737845000747L;
+
+            @Override
+            public Object getDisplayValue(final String value) {
+                return securityQuestions.stream().filter(sq -> value.equals(sq.getKey())).
+                        map(SecurityQuestionTO::getContent).findFirst().orElse(null);
+            }
+
+            @Override
+            public String getIdValue(final String value, final int index) {
+                return value;
+            }
+
+            @Override
+            public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
+                return id;
+            }
+        });
+
+        securityQuestion.add(new AjaxEventBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = 192359260308762078L;
+
+            @Override
+            protected void onEvent(final AjaxRequestTarget target) {
+                securityAnswer.setEnabled(StringUtils.isNotBlank(securityQuestion.getModelObject()));
+                target.add(securityAnswer);
+            }
+        });
+
+        form.add(securityQuestion);
+
+        securityAnswer = new AjaxTextFieldPanel("securityAnswer", "securityAnswer",
+                new PropertyModel<>(userTO, "securityAnswer"), false);
+        form.add(securityAnswer.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true).
+                setEnabled(StringUtils.isNotBlank(securityQuestion.getModelObject())));
+
+        CaptchaPanel<Void> captcha = new CaptchaPanel<>(EnduserConstants.CONTENT_PANEL);
+        captcha.setOutputMarkupPlaceholderTag(true);
+
+        form.add(new CardPanel.Builder<CaptchaPanel<Void>>()
+                .setName("captcha")
+                .setComponent(captcha)
+                .isVisible(SyncopeWebApplication.get().isCaptchaEnabled()).build("captchaPanelCard"));
+
+        AjaxButton submitButton = new AjaxButton("submit", new Model<>(getString("submit"))) {
+
+            private static final long serialVersionUID = 429178684321093953L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                if (StringUtils.isBlank(securityQuestion.getModelObject())
+                        || StringUtils.isBlank(securityAnswer.getModelObject())) {
+
+                    SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
+                    ((BasePage) getPageReference().getPage()).getNotificationPanel().refresh(target);
+                } else {
+                    boolean checked = true;
+                    if (SyncopeWebApplication.get().isCaptchaEnabled()) {
+                        checked = captcha.check();
+                    }
+                    if (!checked) {
+                        SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
+                        ((BasePage) getPageReference().getPage()).getNotificationPanel().refresh(target);
+                    } else {
+                        PageParameters parameters = new PageParameters();
+                        try {
+                            UserUR req = new UserUR();
+                            req.setKey(userTO.getKey());
+                            req.setSecurityQuestion(new StringReplacePatchItem.Builder().
+                                    value(securityQuestion.getModelObject()).build());
+                            req.setSecurityAnswer(new StringReplacePatchItem.Builder().
+                                    value(securityAnswer.getModelObject()).build());
+                            userSelfRestClient.update(userTO.getETagValue(), req);
+
+                            parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+                            parameters.add(Constants.NOTIFICATION_TITLE_PARAM,
+                                    getString("self.securityquestion.change.success"));
+                            parameters.add(Constants.NOTIFICATION_MSG_PARAM,
+                                    getString("self.securityquestion.change.success.msg"));
+                            SyncopeEnduserSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (Exception e) {
+                            LOG.error("While changing password for {}",
+                                    SyncopeEnduserSession.get().getSelfTO().getUsername(), e);
+                            parameters.add(EnduserConstants.STATUS, Constants.OPERATION_ERROR);
+                            parameters.add(Constants.NOTIFICATION_TITLE_PARAM,
+                                    getString("self.securityquestion.change.error"));
+                            parameters.add(Constants.NOTIFICATION_MSG_PARAM,
+                                    getString("self.securityquestion.change.error.msg"));
+                            SyncopeEnduserSession.get().onException(e);
+                            notificationPanel.refresh(target);
+                        }
+                        parameters.add(
+                                EnduserConstants.LANDING_PAGE,
+                                SyncopeWebApplication.get().getPageClass("profile", Dashboard.class).getName());
+                        setResponsePage(SelfResult.class, parameters);
+                    }
+                }
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target) {
+                notificationPanel.refresh(target);
+            }
+        };
+        form.add(submitButton);
+
+        form.setDefaultButton(submitButton);
+
+        Button cancel = new Button("cancel") {
+
+            private static final long serialVersionUID = 3669569969172391336L;
+
+            @Override
+            public void onSubmit() {
+                setResponsePage(getApplication().getHomePage());
+            }
+        };
+
+        cancel.setOutputMarkupId(true);
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java
new file mode 100644
index 0000000..341f185
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/EditUser.java
@@ -0,0 +1,62 @@
+/*
+ * 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 org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.enduser.panels.UserFormPanel;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class EditUser extends BasePage {
+
+    private static final long serialVersionUID = -1100228004207271270L;
+
+    private static final String EDIT_USER = "page.edituser";
+
+    protected WebMarkupContainer content;
+
+    public EditUser(final PageParameters parameters) {
+        super(parameters, EDIT_USER);
+
+        content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        contentWrapper.add(content);
+
+        UserTO userTO = SyncopeEnduserSession.get().getSelfTO(true);
+
+        UserFormPanel editUserPanel = new UserFormPanel(
+                "editUserPanel",
+                userTO,
+                userTO,
+                SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getUserClasses(),
+                buildFormLayout(),
+                getPageReference());
+        editUserPanel.setOutputMarkupId(true);
+        content.add(editUserPanel);
+    }
+
+    protected UserFormLayoutInfo buildFormLayout() {
+        UserFormLayoutInfo customlayoutInfo = SyncopeWebApplication.get().getCustomFormLayout();
+        return customlayoutInfo != null ? customlayoutInfo : new UserFormLayoutInfo();
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Login.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Login.java
index c9f9a91..bcc26ad 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Login.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Login.java
@@ -22,12 +22,10 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
-import java.util.Locale;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import org.apache.syncope.client.enduser.SyncopeWebApplication;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
 import org.apache.syncope.client.ui.commons.BaseLogin;
 import org.apache.syncope.client.ui.commons.BaseSession;
 import org.apache.wicket.Component;
@@ -35,27 +33,27 @@
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.spring.injection.annot.SpringBean;
 
 public class Login extends BaseLogin {
 
-    private static final long serialVersionUID = -3422492668689122688L;
-
-    @SpringBean
-    private ClassPathScanImplementationLookup lookup;
-
-    private final BookmarkablePageLink<Void> selfRegistration;
+    private static final long serialVersionUID = 5889157642852559004L;
 
     private final BookmarkablePageLink<Void> selfPwdReset;
 
+    private final BookmarkablePageLink<Void> selfRegistration;
+
     public Login(final PageParameters parameters) {
         super(parameters);
 
-        selfRegistration = new BookmarkablePageLink<>("self-registration", Self.class);
-        add(selfRegistration.setOutputMarkupId(true));
-
         selfPwdReset = new BookmarkablePageLink<>("self-pwd-reset", SelfPasswordReset.class);
-        add(selfPwdReset.setOutputMarkupId(true));
+        selfPwdReset.getPageParameters().add("domain", SyncopeEnduserSession.get().getDomain());
+        selfPwdReset.setVisible(SyncopeEnduserSession.get().getPlatformInfo().isPwdResetAllowed());
+        add(selfPwdReset.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
+
+        selfRegistration = new BookmarkablePageLink<>("self-registration", SelfRegistration.class);
+        selfRegistration.getPageParameters().add("domain", SyncopeEnduserSession.get().getDomain());
+        selfRegistration.setVisible(SyncopeEnduserSession.get().getPlatformInfo().isSelfRegAllowed());
+        add(selfRegistration.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true));
     }
 
     @Override
@@ -74,10 +72,10 @@
     @Override
     protected List<Panel> getSSOLoginFormPanels() {
         List<Panel> ssoLoginFormPanels = new ArrayList<>();
-        lookup.getSSOLoginFormPanels().forEach(ssoLoginFormPanel -> {
+        SyncopeWebApplication.get().getLookup().getSSOLoginFormPanels().forEach(ssoLoginFormPanel -> {
             try {
-                ssoLoginFormPanels.add(ssoLoginFormPanel.getConstructor(String.class, BaseSession.class).
-                        newInstance("ssoLogin", SyncopeEnduserSession.get()));
+                ssoLoginFormPanels.add(ssoLoginFormPanel.getConstructor(String.class, BaseSession.class).newInstance(
+                        "ssoLogin", SyncopeEnduserSession.get()));
             } catch (Exception e) {
                 LOG.error("Could not initialize the provided SSO login form panel", e);
             }
@@ -96,26 +94,22 @@
     }
 
     @Override
-    protected List<Locale> getSupportedLocales() {
-        return SyncopeWebApplication.SUPPORTED_LOCALES;
-    }
+    protected void authenticate(final String username, final String password, final AjaxRequestTarget target)
+            throws AccessControlException {
 
-    @Override
-    protected void authenticate(
-            final String username,
-            final String password,
-            final AjaxRequestTarget target) throws AccessControlException {
+        if (SyncopeWebApplication.get().getAnonymousUser().equals(username)
+                || SyncopeWebApplication.get().getAdminUser().equals(username)) {
 
-        if (!SyncopeWebApplication.get().getAdminUser().equalsIgnoreCase(username)
-                && !SyncopeWebApplication.get().getAnonymousUser().equalsIgnoreCase(username)
-                && SyncopeEnduserSession.get().authenticate(username, password)) {
+            throw new AccessControlException("Illegal username");
+        }
 
-            // user has been authenticated successfully
+        if (SyncopeEnduserSession.get().authenticate(username, password)) {
+            // If login has been called because the user was not yet logged in, than continue to the
+            // original destination, otherwise to the Home page
             continueToOriginalDestination();
             setResponsePage(getApplication().getHomePage());
         } else {
-            // not authenticated
-            sendError(getString("login-error"));
+            SyncopeEnduserSession.get().error(getString("login-error"));
             notificationPanel.refresh(target);
         }
     }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/MustChangePassword.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/MustChangePassword.java
index 0480618..4649c41 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/MustChangePassword.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/MustChangePassword.java
@@ -19,48 +19,57 @@
 package org.apache.syncope.client.enduser.pages;
 
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
 import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.pages.AbstractMustChangePassword;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
-public class MustChangePassword extends AbstractMustChangePassword {
+public class MustChangePassword extends AbstractChangePassword {
 
     private static final long serialVersionUID = 8581970794722709800L;
 
     public MustChangePassword(final PageParameters parameters) {
         super(parameters);
+
+        setDomain(parameters);
+        disableSidebar();
     }
 
     @Override
-    protected void doSubmit(final AjaxRequestTarget target) {
+    protected void doPwdSubmit(final AjaxRequestTarget target, final AjaxPasswordFieldPanel passwordField) {
+        PageParameters parameters = new PageParameters();
         try {
             UserSelfRestClient.changePassword(passwordField.getModelObject());
 
             SyncopeEnduserSession.get().invalidate();
-
-            final PageParameters parameters = new PageParameters();
+            parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+            parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.pwd.change.success"));
             parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.pwd.change.success"));
-            setResponsePage(getApplication().getHomePage(), parameters);
-
-            setResponsePage(getApplication().getHomePage(), parameters);
+            SyncopeEnduserSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
         } catch (Exception e) {
             LOG.error("While changing password for {}",
                     SyncopeEnduserSession.get().getSelfTO().getUsername(), e);
+            parameters.add(EnduserConstants.STATUS, Constants.OPERATION_ERROR);
+            parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.pwd.change.error"));
+            parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.pwd.change.error.msg"));
             SyncopeEnduserSession.get().onException(e);
-            notificationPanel.refresh(target);
         }
+        notificationPanel.refresh(target);
+        setResponsePage(SelfResult.class, parameters);
     }
 
     @Override
-    protected UserTO getLoggedUser() {
+    protected UserTO getPwdLoggedUser() {
         return SyncopeEnduserSession.get().getSelfTO();
     }
 
     @Override
-    protected void doCancel() {
-        setResponsePage(getApplication().getHomePage());
+    protected void doPwdCancel() {
+        SyncopeEnduserSession.get().invalidate();
+        final PageParameters parameters = new PageParameters();
+        setResponsePage(getApplication().getHomePage(), parameters);
     }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java
deleted file mode 100644
index aab4197..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/Self.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * 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 com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.layout.AnyLayoutUtils;
-import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
-import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizardBuilder;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
-import org.apache.wicket.event.IEvent;
-import org.apache.wicket.event.IEventSource;
-import org.apache.wicket.markup.html.WebPage;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.spring.injection.annot.SpringBean;
-
-public class Self extends BaseEnduserWebPage implements IEventSource {
-
-    private static final long serialVersionUID = 164651008547631054L;
-
-    public static final String NEW_USER_PARAM = "newUser";
-
-    private static final ObjectMapper MAPPER = new ObjectMapper();
-
-    @SpringBean
-    private ConfParamOps confParamOps;
-
-    private AjaxWizardBuilder<AnyWrapper<UserTO>> wizardBuilder;
-
-    protected static final String WIZARD_ID = "wizard";
-
-    public Self(final PageParameters parameters) {
-        super(parameters);
-
-        body.add(buildWizard(SyncopeEnduserSession.get().isAuthenticated()
-                ? SyncopeEnduserSession.get().getSelfTO()
-                : buildNewUserTO(parameters),
-                SyncopeEnduserSession.get().isAuthenticated()
-                ? AjaxWizard.Mode.EDIT
-                : AjaxWizard.Mode.CREATE));
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void onEvent(final IEvent<?> event) {
-        if (event.getPayload() instanceof AjaxWizard.NewItemEvent) {
-            if (event.getPayload() instanceof AjaxWizard.NewItemCancelEvent) {
-                @SuppressWarnings("unchecked")
-                final Class<? extends WebPage> beforeLogout = (Class<? extends WebPage>) SyncopeEnduserSession.get().
-                        getAttribute(Constants.BEFORE_LOGOUT_PAGE);
-                if (beforeLogout == null) {
-                    SyncopeEnduserSession.get().invalidate();
-                    setResponsePage(getApplication().getHomePage());
-                } else {
-                    setResponsePage(beforeLogout);
-                }
-            } else if (event.getPayload() instanceof AjaxWizard.NewItemFinishEvent) {
-                SyncopeEnduserSession.get().invalidate();
-
-                final PageParameters parameters = new PageParameters();
-                parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.wizard.success"));
-                setResponsePage(getApplication().getHomePage(), parameters);
-            }
-        }
-        super.onEvent(event);
-    }
-
-    @Override
-    protected void onBeforeRender() {
-        super.onBeforeRender();
-        navbar.setActiveNavItem(getClass().getSimpleName().toLowerCase());
-    }
-
-    protected final AjaxWizard<AnyWrapper<UserTO>> buildWizard(final UserTO userTO, final AjaxWizard.Mode mode) {
-        String formLayoutConfParam = confParamOps.get(
-                SyncopeEnduserSession.get().getDomain(),
-                Constants.ENDUSER_ANYLAYOUT,
-                AnyLayoutUtils.getDefaultValue(),
-                String.class);
-
-        UserFormLayoutInfo formLayoutInfo =
-                StringUtils.isBlank(formLayoutConfParam)
-                ? new UserFormLayoutInfo()
-                : AnyLayoutUtils.fromJsonString(formLayoutConfParam);
-
-        wizardBuilder = (AjaxWizardBuilder<AnyWrapper<UserTO>>) AnyLayoutUtils.newUserWizardBuilder(
-                userTO,
-                SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getUserClasses(),
-                formLayoutInfo,
-                this.getPageReference());
-        wizardBuilder.setItem(new UserWrapper(userTO));
-        return wizardBuilder.build(WIZARD_ID, mode);
-    }
-
-    private static UserTO buildNewUserTO(final PageParameters parameters) {
-        UserTO userTO = null;
-        if (parameters != null) {
-            if (!parameters.get(NEW_USER_PARAM).isNull()) {
-                try {
-                    userTO = MAPPER.readValue(parameters.get(NEW_USER_PARAM).toString(), UserTO.class);
-                } catch (JsonProcessingException e) {
-                    LOG.error("While reading user data from social registration", e);
-                }
-            }
-        }
-        if (userTO == null) {
-            userTO = new UserTO();
-        }
-        userTO.setRealm(SyncopeConstants.ROOT_REALM);
-        return userTO;
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java
index f937507..6038d73 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.java
@@ -18,127 +18,95 @@
  */
 package org.apache.syncope.client.enduser.pages;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthConfig;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
 import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.DomainDropDown;
-import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
-import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
-import org.apache.syncope.common.keymaster.client.api.DomainOps;
-import org.apache.syncope.common.keymaster.client.api.model.Domain;
+import org.apache.syncope.client.ui.commons.panels.CardPanel;
+import org.apache.syncope.client.ui.commons.wizards.any.PasswordPanel;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.form.Button;
 import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.PasswordTextField;
 import org.apache.wicket.markup.html.form.StatelessForm;
-import org.apache.wicket.markup.html.form.validation.EqualPasswordInputValidator;
-import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.spring.injection.annot.SpringBean;
 
-public class SelfConfirmPasswordReset extends BaseEnduserWebPage {
+public class SelfConfirmPasswordReset extends BasePage {
 
     private static final long serialVersionUID = -2166782304542750726L;
 
-    @SpringBean
-    private DomainOps domainOps;
-
-    private final LoadableDetachableModel<List<String>> domains = new LoadableDetachableModel<List<String>>() {
-
-        private static final long serialVersionUID = 4659376149825914247L;
-
-        @Override
-        protected List<String> load() {
-            List<String> current = new ArrayList<>();
-            current.addAll(domainOps.list().stream().map(Domain::getKey).sorted().collect(Collectors.toList()));
-            current.add(0, SyncopeConstants.MASTER_DOMAIN);
-            return current;
-        }
-    };
+    private static final String CONFIRM_PASSWORD_RESET = "confirmPasswordReset";
 
     public SelfConfirmPasswordReset(final PageParameters parameters) {
-        super(parameters);
+        super(parameters, CONFIRM_PASSWORD_RESET);
 
-        if (parameters.get("token").isEmpty()) {
-            LOG.debug("No token parameter found in the request url");
-            parameters.add("errorMessage", getString("self.confirm.pwd.reset.error.empty"));
-            setResponsePage(getApplication().getHomePage(), parameters);
+        setDomain(parameters);
+        disableSidebar();
+
+        if (parameters == null || parameters.get("token").isEmpty()) {
+            LOG.error("No token parameter found in the request url");
+
+            PageParameters homeParameters = new PageParameters();
+            homeParameters.add("errorMessage", getString("self.confirm.pwd.reset.error.empty"));
+            setResponsePage(getApplication().getHomePage(), homeParameters);
         }
 
-        navbar.setEnabled(false);
-        navbar.setVisible(false);
-
         WebMarkupContainer content = new WebMarkupContainer("content");
         content.setOutputMarkupId(true);
-        body.add(content);
+        contentWrapper.add(content);
 
         Form<?> form = new StatelessForm<>("selfConfirmPwdResetForm");
         form.setOutputMarkupId(true);
         content.add(form);
 
-        DomainDropDown domainSelect = new DomainDropDown("domain", domains);
-        domainSelect.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) {
+        UserTO fakeUserTO = new UserTO();
+        PasswordPanel passwordPanel = new PasswordPanel(
+                EnduserConstants.CONTENT_PANEL,
+                new UserWrapper(fakeUserTO),
+                false,
+                false,
+                new PasswordStrengthBehavior(new PasswordStrengthConfig().
+                        withDebug(false).
+                        withShowVerdictsInsideProgressBar(true).
+                        withShowProgressBar(true)));
+        passwordPanel.setOutputMarkupId(true);
 
-            private static final long serialVersionUID = -1107858522700306810L;
+        form.add(new CardPanel.Builder<PasswordPanel>()
+                .setName("selfConfirmPasswordResetPanel")
+                .setComponent(passwordPanel)
+                .isVisible(true)
+                .build("selfConfirmPasswordResetPanelCard"));
 
-            @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
-                // nothing to do
-            }
-        }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
-
-            private static final long serialVersionUID = -1107858522700306810L;
-
-            @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
-                // nothing to do
-            }
-        });
-        form.add(domainSelect);
-
-        AjaxPasswordFieldPanel passwordField = new AjaxPasswordFieldPanel(
-                "password", getString("password"), new Model<>());
-        passwordField.setRequired(true);
-        passwordField.setMarkupId("password");
-        passwordField.setPlaceholder(getString("password"));
-        ((PasswordTextField) passwordField.getField()).setResetPassword(false);
-        form.add(passwordField);
-
-        FieldPanel<String> confirmPasswordField = new AjaxPasswordFieldPanel(
-                "confirmPassword", getString("confirm-password"), new Model<>());
-        confirmPasswordField.setRequired(true);
-        confirmPasswordField.setMarkupId("confirmPassword");
-        confirmPasswordField.setPlaceholder(getString("confirm-password"));
-        ((PasswordTextField) confirmPasswordField.getField()).setResetPassword(false);
-        form.add(confirmPasswordField);
-
-        form.add(new EqualPasswordInputValidator(passwordField.getField(), confirmPasswordField.getField()));
-
-        AjaxButton submitButton = new AjaxButton("submit", new Model<>(getString("submit"))) {
+        AjaxButton submit = new AjaxButton("submit", new Model<>(getString("submit"))) {
 
             private static final long serialVersionUID = 509325877101838812L;
 
             @Override
             protected void onSubmit(final AjaxRequestTarget target) {
+                PageParameters params = new PageParameters();
                 try {
                     SyncopeEnduserSession.get().getService(UserSelfService.class).confirmPasswordReset(
-                            parameters.get("token").toString(), passwordField.getDefaultModelObjectAsString());
-                    PageParameters parameters = new PageParameters();
-                    parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.confirm.pwd.reset.success"));
-                    setResponsePage(getApplication().getHomePage(), parameters);
+                            parameters.get("token").toString(), fakeUserTO.getPassword());
+                    params.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+                    params.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.confirm.pwd.reset.success"));
+                    params.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.confirm.pwd.reset.success.msg"));
+                    SyncopeEnduserSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                    parameters.add(EnduserConstants.LANDING_PAGE, Login.class.getName());
+                    setResponsePage(SelfResult.class, params);
                 } catch (SyncopeClientException sce) {
                     LOG.error("Unable to complete the 'Password Reset Confirmation' process", sce);
+                    params.add(EnduserConstants.STATUS, Constants.OPERATION_ERROR);
+                    params.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.confirm.pwd.reset.error"));
+                    params.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.confirm.pwd.reset.error.msg"));
                     SyncopeEnduserSession.get().onException(sce);
-                    ((BaseEnduserWebPage) getPageReference().getPage()).getNotificationPanel().refresh(target);
+                    ((BasePage) getPageReference().getPage()).getNotificationPanel().refresh(target);
                 }
             }
 
@@ -147,8 +115,8 @@
                 notificationPanel.refresh(target);
             }
         };
-        form.setDefaultButton(submitButton);
-        form.add(submitButton);
+        form.setDefaultButton(submit);
+        form.add(submit);
 
         Button cancel = new Button("cancel") {
 
@@ -158,7 +126,6 @@
             public void onSubmit() {
                 setResponsePage(getApplication().getHomePage());
             }
-
         };
         cancel.setOutputMarkupId(true);
         cancel.setDefaultFormProcessing(false);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfPasswordReset.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfPasswordReset.java
index 0d53e99..3d34ed0 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfPasswordReset.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfPasswordReset.java
@@ -18,33 +18,219 @@
  */
 package org.apache.syncope.client.enduser.pages;
 
-import org.apache.syncope.client.enduser.panels.SelfPwdResetPanel;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.panels.captcha.CaptchaPanel;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.panels.CardPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.common.rest.api.service.SecurityQuestionService;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
 import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
-public class SelfPasswordReset extends BaseEnduserWebPage {
+public class SelfPasswordReset extends BasePage {
 
     private static final long serialVersionUID = 164651008547631054L;
 
+    private static final String SELF_PWD_RESET = "page.selfPwdReset";
+
+    private String usernameValue;
+
+    private String securityAnswerValue;
+
+    private final CaptchaPanel<Void> captcha;
+
     private final SelfPwdResetPanel pwdResetPanel;
 
     public SelfPasswordReset(final PageParameters parameters) {
-        super(parameters);
+        super(parameters, SELF_PWD_RESET);
 
-        navbar.setEnabled(false);
-        navbar.setVisible(false);
+        setDomain(parameters);
+        disableSidebar();
+
+        captcha = new CaptchaPanel<>("captchaPanel");
+        captcha.setOutputMarkupPlaceholderTag(true);
+        captcha.setVisible(SyncopeWebApplication.get().isCaptchaEnabled());
 
         WebMarkupContainer content = new WebMarkupContainer("content");
         content.setOutputMarkupId(true);
-        body.add(content);
+        contentWrapper.add(content);
 
         Form<?> form = new Form<>("selfPwdResetForm");
         content.add(form);
 
-        pwdResetPanel = new SelfPwdResetPanel("selfPwdResetPanel", getPageReference());
+        pwdResetPanel = new SelfPwdResetPanel(EnduserConstants.CONTENT_PANEL, captcha, getPageReference());
         pwdResetPanel.setOutputMarkupId(true);
 
-        form.add(pwdResetPanel);
+        form.add(new CardPanel.Builder<SelfPwdResetPanel>()
+                .setName("selfPasswordResetPanel")
+                .setComponent(pwdResetPanel)
+                .isVisible(true)
+                .build("selfPasswordResetPanelCard"));
+
+        AjaxButton submitButton = new AjaxButton("submit") {
+
+            private static final long serialVersionUID = 4284361595033427185L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                boolean checked = true;
+                if (SyncopeWebApplication.get().isCaptchaEnabled()) {
+                    checked = captcha.check();
+                }
+                if (!checked) {
+                    SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
+                    SelfPasswordReset.this.getNotificationPanel().refresh(target);
+                } else {
+                    PageParameters parameters = new PageParameters();
+                    try {
+                        UserSelfRestClient.requestPasswordReset(usernameValue, securityAnswerValue);
+                        parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+                        parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.pwd.reset.success"));
+                        parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.pwd.reset.success.msg"));
+                        parameters.add(EnduserConstants.LANDING_PAGE, Login.class.getName());
+                        setResponsePage(SelfResult.class, parameters);
+                    } catch (SyncopeClientException sce) {
+                        LOG.error("Unable to reset password of [{}]", usernameValue, sce);
+                        SyncopeEnduserSession.get().onException(sce);
+                        SelfPasswordReset.this.getNotificationPanel().refresh(target);
+                    }
+                }
+            }
+
+        };
+        submitButton.setOutputMarkupId(true);
+        submitButton.setDefaultFormProcessing(false);
+        form.add(submitButton);
+
+        Button cancel = new Button("cancel") {
+
+            private static final long serialVersionUID = 3669569969172391336L;
+
+            @Override
+            public void onSubmit() {
+                setResponsePage(getApplication().getHomePage());
+            }
+
+        };
+        cancel.setOutputMarkupId(true);
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+    }
+
+    public class SelfPwdResetPanel extends Panel {
+
+        private static final long serialVersionUID = -2841210052053545578L;
+
+        private final TextField<String> securityQuestion;
+
+        SelfPwdResetPanel(final String id, final CaptchaPanel<Void> captcha, final PageReference pageRef) {
+            super(id);
+
+            boolean isSecurityQuestionEnabled =
+                    SyncopeEnduserSession.get().getPlatformInfo().isPwdResetRequiringSecurityQuestions();
+
+            TextField<String> username = new TextField<>("username",
+                    new PropertyModel<>(SelfPasswordReset.this, "usernameValue"), String.class);
+            username.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) {
+
+                private static final long serialVersionUID = -1107858522700306810L;
+
+                @Override
+                protected void onUpdate(final AjaxRequestTarget target) {
+                    if (isSecurityQuestionEnabled) {
+                        loadSecurityQuestion(pageRef, target);
+                    }
+                }
+            });
+            username.setRequired(true);
+            add(username);
+
+            Label sqLabel =
+                    new Label("securityQuestionLabel", new ResourceModel("securityQuestion", "securityQuestion"));
+            sqLabel.setOutputMarkupPlaceholderTag(true);
+            sqLabel.setVisible(isSecurityQuestionEnabled);
+            add(sqLabel);
+
+            securityQuestion =
+                    new TextField<>("securityQuestion", new PropertyModel<>(Model.of(), "content"), String.class);
+            securityQuestion.setOutputMarkupId(true);
+            securityQuestion.setEnabled(false);
+            securityQuestion.setOutputMarkupPlaceholderTag(true);
+            securityQuestion.setVisible(isSecurityQuestionEnabled);
+            add(securityQuestion);
+
+            Label notLoading = new Label("not.loading", new ResourceModel("not.loading", "not.loading"));
+            notLoading.setOutputMarkupPlaceholderTag(true);
+            notLoading.setVisible(isSecurityQuestionEnabled);
+            add(notLoading);
+
+            AjaxLink<Void> reloadLink = new AjaxLink<Void>("reloadLink") {
+
+                private static final long serialVersionUID = -817438685948164787L;
+
+                @Override
+                public void onClick(final AjaxRequestTarget target) {
+                    loadSecurityQuestion(pageRef, target);
+                }
+            };
+            reloadLink.setOutputMarkupPlaceholderTag(true);
+            reloadLink.setVisible(isSecurityQuestionEnabled);
+            add(reloadLink);
+
+            Label saLabel = new Label("securityAnswerLabel", new ResourceModel("securityAnswer", "securityAnswer"));
+            saLabel.setOutputMarkupPlaceholderTag(true);
+            saLabel.setVisible(isSecurityQuestionEnabled);
+            add(saLabel);
+
+            TextField<String> securityAnswer =
+                    new TextField<>("securityAnswer", new PropertyModel<>(SelfPasswordReset.this,
+                            "securityAnswerValue"), String.class);
+            securityAnswer.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                private static final long serialVersionUID = -1107858522700306810L;
+
+                @Override
+                protected void onUpdate(final AjaxRequestTarget target) {
+                    // do nothing
+                }
+            });
+            securityAnswer.setRequired(isSecurityQuestionEnabled);
+            securityAnswer.setOutputMarkupPlaceholderTag(true);
+            securityAnswer.setVisible(isSecurityQuestionEnabled);
+            add(securityAnswer);
+
+            add(captcha);
+        }
+
+        protected void loadSecurityQuestion(final PageReference pageRef, final AjaxRequestTarget target) {
+            try {
+                SecurityQuestionTO securityQuestionTO = SyncopeEnduserSession.get().getService(
+                        SecurityQuestionService.class).readByUser(usernameValue);
+                // set security question field model
+                securityQuestion.setModel(Model.of(securityQuestionTO.getContent()));
+                target.add(securityQuestion);
+            } catch (Exception e) {
+                LOG.error("Unable to get security question for [{}]", usernameValue, e);
+                SyncopeEnduserSession.get().onException(e);
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }
     }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java
new file mode 100644
index 0000000..ec85d59
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfRegistration.java
@@ -0,0 +1,86 @@
+/*
+ * 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 com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.enduser.panels.UserSelfFormPanel;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class SelfRegistration extends BasePage {
+
+    private static final long serialVersionUID = -1100228004207271270L;
+
+    private static final String SELF_REGISTRATION = "page.selfRegistration";
+
+    public static final String NEW_USER_PARAM = "newUser";
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    public SelfRegistration(final PageParameters parameters) {
+        super(parameters, SELF_REGISTRATION);
+
+        setDomain(parameters);
+        disableSidebar();
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        contentWrapper.add(content);
+
+        UserSelfFormPanel selfRegistrationPanel = new UserSelfFormPanel(
+                "selfRegistrationPanel",
+                buildNewUserTO(parameters),
+                buildNewUserTO(parameters),
+                SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getUserClasses(),
+                buildFormLayout(),
+                getPageReference());
+        selfRegistrationPanel.setOutputMarkupId(true);
+        content.add(selfRegistrationPanel);
+    }
+
+    private UserFormLayoutInfo buildFormLayout() {
+        UserFormLayoutInfo customlayoutInfo = SyncopeWebApplication.get().getCustomFormLayout();
+        return customlayoutInfo != null ? customlayoutInfo : new UserFormLayoutInfo();
+    }
+
+    private static UserTO buildNewUserTO(final PageParameters parameters) {
+        UserTO userTO = null;
+        if (parameters != null) {
+            if (!parameters.get(NEW_USER_PARAM).isNull()) {
+                try {
+                    userTO = MAPPER.readValue(parameters.get(NEW_USER_PARAM).toString(), UserTO.class);
+                } catch (JsonProcessingException e) {
+                    LOG.error("While reading user data from social registration", e);
+                }
+            }
+        }
+        if (userTO == null) {
+            userTO = new UserTO();
+        }
+        userTO.setRealm(SyncopeConstants.ROOT_REALM);
+        return userTO;
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfResult.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfResult.java
new file mode 100644
index 0000000..4d415c4
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfResult.java
@@ -0,0 +1,67 @@
+/*
+ * 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 org.apache.syncope.client.enduser.BookmarkablePageLinkBuilder;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class SelfResult extends BasePage {
+
+    private static final long serialVersionUID = 3804053409052140145L;
+
+    private static final String RESULT_PAGE = "page.resultPage";
+
+    @SuppressWarnings("unchecked")
+    public SelfResult(final PageParameters parameters) {
+        super(parameters, RESULT_PAGE);
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        contentWrapper.add(content);
+        Class<? extends WebPage> page;
+        try {
+            page = (Class<? extends WebPage>) Class.forName(parameters.get(EnduserConstants.LANDING_PAGE).
+                    toString("org.apache.syncope.client.enduser.pages.Login"));
+        } catch (ClassNotFoundException e) {
+            LOG.debug("Login page not found", e);
+            page = Login.class;
+        }
+        if (page.equals(Login.class)) {
+            BookmarkablePageLink<WebPage> login =
+                    new BookmarkablePageLink<>("login", Login.class);
+            content.add(login.setOutputMarkupId(true));
+            disableSidebar();
+        } else {
+            content.add(BookmarkablePageLinkBuilder.build("login", page));
+        }
+
+        content.add(new Label("resultTitle", parameters.get(Constants.NOTIFICATION_TITLE_PARAM).toString()));
+        content.add(new Label("resultMessage", parameters.get(Constants.NOTIFICATION_MSG_PARAM).toString()));
+        content.add(new Fragment("statusIcon",
+                Constants.OPERATION_SUCCEEDED.equals(parameters.get(EnduserConstants.STATUS).toString())
+                ? "successIcon" : "errorIcon", content));
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AbstractAnyFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AbstractAnyFormPanel.java
new file mode 100644
index 0000000..18c461a
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AbstractAnyFormPanel.java
@@ -0,0 +1,95 @@
+/*
+ * 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.panels;
+
+import java.io.Serializable;
+import org.apache.syncope.client.enduser.pages.BasePage;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.CompoundPropertyModel;
+
+public abstract class AbstractAnyFormPanel<T extends Serializable> extends AbstractFormPanel<T> {
+
+    private static final long serialVersionUID = -5976166731584959275L;
+
+    protected final Form<T> form;
+
+    public AbstractAnyFormPanel(final String id, final T defaultItem, final PageReference pageReference) {
+        super(id, defaultItem, pageReference);
+
+        form = new Form<>("form");
+        form.setOutputMarkupId(true);
+        add(form);
+        AjaxButton submitButton = new AjaxButton("submit") {
+
+            private static final long serialVersionUID = 4284361595033427185L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                onFormSubmit(target);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target) {
+                ((BasePage) getPage()).getNotificationPanel().refresh(target);
+            }
+        };
+
+        submitButton.setOutputMarkupId(true);
+        submitButton.setDefaultFormProcessing(true);
+        form.add(submitButton);
+
+        Button cancel = new Button("cancel") {
+
+            private static final long serialVersionUID = 3669569969172391336L;
+
+            @Override
+            public void onSubmit() {
+                setResponsePage(getApplication().getHomePage());
+            }
+
+        };
+        cancel.setOutputMarkupId(true);
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+    }
+
+    public Form<T> getForm() {
+        return form;
+    }
+
+    public void setFormModel(final T modelObject) {
+        form.setModel(new CompoundPropertyModel<>(modelObject));
+    }
+
+    protected void onCancelInternal(final T modelObject) {
+    }
+
+    protected Serializable onApplyInternal(final T modelObject) {
+        // do nothing
+        return null;
+    }
+
+    protected abstract void buildLayout(T modelObject);
+
+    protected abstract void onFormSubmit(AjaxRequestTarget target);
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AbstractFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AbstractFormPanel.java
new file mode 100644
index 0000000..312ea27
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AbstractFormPanel.java
@@ -0,0 +1,63 @@
+/*
+ * 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.panels;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractFormPanel<T extends Serializable> extends Panel {
+
+    private static final long serialVersionUID = 6650311507433421554L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(AbstractFormPanel.class);
+
+    protected final PageReference pageRef;
+
+    protected final T defaultItem;
+
+    protected T item;
+
+    public AbstractFormPanel(final String id, final T defaultItem, final PageReference pageReference) {
+        super(id);
+        this.defaultItem = defaultItem;
+        this.pageRef = pageReference;
+    }
+
+    protected T getOriginalItem() {
+        return item;
+    }
+
+    protected T newModelObject() {
+        if (item == null) {
+            // keep the original item: the which one before the changes performed during wizard browsing
+            item = SerializationUtils.clone(defaultItem);
+        }
+
+        // instantiate a new model object and return it
+        return SerializationUtils.clone(item);
+    }
+
+    public PageReference getPageReference() {
+        return pageRef;
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java
new file mode 100644
index 0000000..ed9e79b
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/AnyFormPanel.java
@@ -0,0 +1,184 @@
+/*
+ * 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.panels;
+
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.enduser.panels.captcha.CaptchaPanel;
+import org.apache.syncope.client.ui.commons.panels.CardPanel;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.GroupableRelatableTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import java.util.List;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.panels.any.DerAttrs;
+import org.apache.syncope.client.enduser.panels.any.Details;
+import org.apache.syncope.client.enduser.panels.any.Groups;
+import org.apache.syncope.client.enduser.panels.any.PlainAttrs;
+import org.apache.syncope.client.enduser.panels.any.Resources;
+import org.apache.syncope.client.enduser.panels.any.VirAttrs;
+
+public abstract class AnyFormPanel extends AbstractAnyFormPanel<UserWrapper> {
+
+    private static final long serialVersionUID = -2720486919461006370L;
+
+    protected final List<String> anyTypeClasses;
+
+    protected CaptchaPanel<Void> captcha;
+
+    protected UserFormLayoutInfo formLayoutInfo;
+
+    public AnyFormPanel(final String id,
+            final UserTO anyTO,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageReference) {
+
+        super(id, new UserWrapper(anyTO), pageReference);
+
+        this.formLayoutInfo = formLayoutInfo;
+        this.anyTypeClasses = anyTypeClasses;
+    }
+
+    @SuppressWarnings("unchecked")
+    public AnyFormPanel(final String id,
+            final UserWrapper wrapper,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageReference) {
+
+        super(id, wrapper, pageReference);
+
+        this.formLayoutInfo = formLayoutInfo;
+        this.anyTypeClasses = anyTypeClasses;
+    }
+
+    protected Details<UserTO> addOptionalDetailsPanel(final UserWrapper modelObject) {
+        Details<UserTO> details = new Details<>(EnduserConstants.CONTENT_PANEL, modelObject, false, true, pageRef);
+        details.setOutputMarkupId(true);
+        return details;
+    }
+
+    @Override
+    protected void buildLayout(final UserWrapper modelObject) {
+        form.add(new CardPanel.Builder<>()
+                .setName("details")
+                .setComponent(addOptionalDetailsPanel(modelObject))
+                .isVisible(formLayoutInfo.isDetailsManagement()).build("userDetailsPanelCard"));
+
+        Groups groups = new Groups(EnduserConstants.CONTENT_PANEL, modelObject, false);
+        setOutputMarkupId(true);
+
+        form.add(new CardPanel.Builder<Groups>()
+                .setName("groups")
+                .setComponent(groups)
+                .isVisible(formLayoutInfo.isGroups()).build("groupsPanelCard"));
+
+        PlainAttrs plainAttrs = new PlainAttrs(EnduserConstants.CONTENT_PANEL,
+                modelObject, anyTypeClasses, formLayoutInfo.getWhichPlainAttrs());
+        plainAttrs.setOutputMarkupId(true);
+
+        form.add(new CardPanel.Builder<PlainAttrs>()
+                .setName("attributes.plain")
+                .setComponent(plainAttrs)
+                .isVisible(formLayoutInfo.isPlainAttrs() && plainAttrs.isPanelVisible()).build("plainAttrsPanelCard"));
+
+        DerAttrs derAttrs = new DerAttrs(EnduserConstants.CONTENT_PANEL,
+                modelObject, anyTypeClasses, formLayoutInfo.getWhichDerAttrs());
+        derAttrs.setOutputMarkupId(true);
+
+        form.add(new CardPanel.Builder<DerAttrs>()
+                .setName("attributes.derived")
+                .setComponent(derAttrs)
+                .isVisible(formLayoutInfo.isVirAttrs() && derAttrs.isPanelVisible()).build("derAttrsPanelCard"));
+
+        VirAttrs virAttrs = new VirAttrs(EnduserConstants.CONTENT_PANEL,
+                modelObject, anyTypeClasses, formLayoutInfo.getWhichVirAttrs());
+        virAttrs.setOutputMarkupId(true);
+
+        form.add(new CardPanel.Builder<VirAttrs>()
+                .setName("attributes.virtual")
+                .setComponent(virAttrs)
+                .isVisible(formLayoutInfo.isVirAttrs() && virAttrs.isPanelVisible()).build("virAttrsPanelCard"));
+
+        Resources resources = new Resources(EnduserConstants.CONTENT_PANEL, modelObject);
+        resources.setOutputMarkupId(true);
+
+        form.add(new CardPanel.Builder<Resources>()
+                .setName("resources")
+                .setComponent(resources)
+                .isVisible(formLayoutInfo.isResources()).build("resourcesPanelCard"));
+
+        // add captcha
+        captcha = new CaptchaPanel<>(EnduserConstants.CONTENT_PANEL);
+        captcha.setOutputMarkupPlaceholderTag(true);
+
+        form.add(new CardPanel.Builder<CaptchaPanel<Void>>()
+                .setName("captcha")
+                .setComponent(captcha)
+                .isVisible(SyncopeWebApplication.get().isCaptchaEnabled()).build("captchaPanelCard"));
+    }
+
+    protected void fixPlainAndVirAttrs(final AnyTO updated, final AnyTO original) {
+        // re-add to the updated object any missing plain or virtual attribute (compared to original): this to cope with
+        // form layout, which might have not included some plain or virtual attributes
+        for (Attr plainAttr : original.getPlainAttrs()) {
+            if (!updated.getPlainAttr(plainAttr.getSchema()).isPresent()) {
+                updated.getPlainAttrs().add(plainAttr);
+            }
+        }
+        for (Attr virAttr : original.getVirAttrs()) {
+            if (!updated.getVirAttr(virAttr.getSchema()).isPresent()) {
+                updated.getVirAttrs().add(virAttr);
+            }
+        }
+
+        if (updated instanceof GroupableRelatableTO && original instanceof GroupableRelatableTO) {
+            GroupableRelatableTO.class
+                    .cast(original).getMemberships().forEach(oMemb -> {
+                GroupableRelatableTO.class
+                        .cast(updated).getMembership(oMemb.getGroupKey()).ifPresent(uMemb -> {
+                    oMemb.getPlainAttrs()
+                            .stream().
+                            filter(attr -> !uMemb.getPlainAttr(attr.getSchema()).isPresent()).
+                            forEach(attr -> uMemb.getPlainAttrs().add(attr));
+                    oMemb.getVirAttrs()
+                            .stream().
+                            filter(attr -> !uMemb.getVirAttr(attr.getSchema()).isPresent()).
+                            forEach(attr -> uMemb.getVirAttrs().add(attr));
+                }
+                );
+            });
+        }
+
+        // remove from the updated object any plain or virtual attribute without values, thus triggering for removal in
+        // the generated patch
+        updated.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty());
+        updated.getVirAttrs().removeIf(attr -> attr.getValues().isEmpty());
+        if (updated instanceof GroupableRelatableTO) {
+            GroupableRelatableTO.class.cast(updated).getMemberships().forEach(memb -> {
+                memb.getPlainAttrs().removeIf(attr -> attr.getValues().isEmpty());
+                memb.getVirAttrs().removeIf(attr -> attr.getValues().isEmpty());
+            });
+        }
+    }
+}
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
new file mode 100644
index 0000000..c5e7fc1
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.java
@@ -0,0 +1,199 @@
+/*
+ * 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.panels;
+
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthConfig;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.panels.captcha.CaptchaPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AbstractFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
+import org.apache.syncope.client.ui.commons.panels.CardPanel;
+import org.apache.syncope.client.ui.commons.panels.NotificationPanel;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.AttributeModifier;
+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.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.PasswordTextField;
+import org.apache.wicket.markup.html.form.StatelessForm;
+import org.apache.wicket.markup.html.form.validation.EqualPasswordInputValidator;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.util.string.AppendingStringBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class ChangePasswordPanel extends Panel {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(ChangePasswordPanel.class);
+
+    private static final long serialVersionUID = -8937593602426944714L;
+
+    protected static final String FORM_SUFFIX = "form_";
+
+    protected StatelessForm<Void> form;
+
+    protected AjaxPasswordFieldPanel passwordField;
+
+    protected AjaxPasswordFieldPanel confirmPasswordField;
+
+    protected CaptchaPanel<Void> captcha;
+
+    public ChangePasswordPanel(final String id, final NotificationPanel notificationPanel) {
+        super(id);
+        form = new StatelessForm<Void>("changePassword") {
+
+            private static final long serialVersionUID = 418292023846536149L;
+
+            @Override
+            protected void appendDefaultButtonField() {
+                AppendingStringBuffer buffer = new AppendingStringBuffer();
+
+                String cssClass = getString(CssUtils.key(Form.class, "hidden-fields"));
+
+                // div that is not visible (but not display:none either)
+                buffer.append(String.format(
+                        "<div style=\"width:0px;height:0px;position:absolute;"
+                        + "left:-100px;top:-100px;overflow:hidden\" class=\"%s\">",
+                        cssClass));
+
+                // add an empty textfield (otherwise IE doesn't work)
+                buffer.append("<input title=\"text_hidden\" "
+                        + "aria-label=\"text_hidden\" type=\"text\" "
+                        + "tabindex=\"-1\" autocomplete=\"off\"/>");
+
+                // add the submitting component
+                final Component submittingComponent = (Component) getDefaultButton();
+                buffer.append("<input title=\"submit_hidden\" aria-label=\"submit_hidden\" "
+                        + "type=\"submit\" tabindex=\"-1\" name=\"");
+                buffer.append(getDefaultButton().getInputName());
+                buffer.append("\" onclick=\" var b=document.getElementById('");
+                buffer.append(submittingComponent.getMarkupId());
+                buffer.append(
+                        "'); if (b!=null&amp;&amp;b.onclick!=null&amp;&amp;typeof(b.onclick) != 'undefined') "
+                        + "{  var r = Wicket.bind(b.onclick, b)(); if (r != false) b.click(); } "
+                        + "else { b.click(); };  return false;\" ");
+                buffer.append(" />");
+
+                // close div
+                buffer.append("</div>");
+
+                getResponse().write(buffer);
+            }
+        };
+        form.setOutputMarkupId(true);
+        add(form);
+
+        passwordField = new AjaxPasswordFieldPanel(
+                "password",
+                getString("password"),
+                new Model<>(),
+                false,
+                new PasswordStrengthBehavior(
+                        new PasswordStrengthConfig()
+                                .withDebug(true)
+                                .withShowVerdictsInsideProgressBar(true)
+                                .withShowProgressBar(true)));
+        passwordField.setRequired(true);
+        passwordField.setMarkupId("password");
+        passwordField.setPlaceholder("password");
+
+        Label passwordLabel = (Label) passwordField.get(AbstractFieldPanel.LABEL);
+        passwordLabel.add(new AttributeModifier("for", FORM_SUFFIX + "password"));
+
+        ((PasswordTextField) passwordField.getField()).setResetPassword(true);
+        form.add(passwordField);
+
+        confirmPasswordField = new AjaxPasswordFieldPanel("confirmPassword",
+                getString("confirmPassword"), new Model<>());
+        confirmPasswordField.setRequired(true);
+        confirmPasswordField.setMarkupId("confirmPassword");
+        confirmPasswordField.setPlaceholder("confirmPassword");
+
+        Label confirmPasswordLabel = (Label) confirmPasswordField.get(AbstractFieldPanel.LABEL);
+        confirmPasswordLabel.add(new AttributeModifier("for", FORM_SUFFIX + "confirmPassword"));
+
+        ((PasswordTextField) confirmPasswordField.getField()).setResetPassword(true);
+        form.add(confirmPasswordField);
+
+        form.add(new EqualPasswordInputValidator(passwordField.getField(), confirmPasswordField.getField()));
+
+        captcha = new CaptchaPanel<>(EnduserConstants.CONTENT_PANEL);
+        captcha.setOutputMarkupPlaceholderTag(true);
+
+        form.add(new CardPanel.Builder<CaptchaPanel<Void>>()
+                .setName("captcha")
+                .setComponent(captcha)
+                .isVisible(SyncopeWebApplication.get().isCaptchaEnabled()).build("captchaPanelCard"));
+
+        AjaxButton submitButton = new AjaxButton("submit", new Model<>(getString("submit"))) {
+
+            private static final long serialVersionUID = 429178684321093953L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                doSubmit(target, passwordField);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target) {
+                notificationPanel.refresh(target);
+            }
+        };
+        form.add(submitButton);
+        form.setDefaultButton(submitButton);
+
+        Button cancel = new Button("cancel") {
+
+            private static final long serialVersionUID = 3669569969172391336L;
+
+            @Override
+            public void onSubmit() {
+                doCancel();
+            }
+        };
+        cancel.setOutputMarkupId(true);
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+    }
+
+    public StatelessForm<Void> getForm() {
+        return form;
+    }
+
+    public AjaxPasswordFieldPanel getPasswordField() {
+        return passwordField;
+    }
+
+    public AjaxPasswordFieldPanel getConfirmPasswordField() {
+        return confirmPasswordField;
+    }
+
+    protected abstract void doSubmit(AjaxRequestTarget target, AjaxPasswordFieldPanel passwordField);
+
+    protected abstract void doCancel();
+
+    protected abstract UserTO getLoggedUser();
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.java
deleted file mode 100644
index 595ff0c..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * 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.panels;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.SyncopeWebApplication;
-import org.apache.syncope.client.enduser.pages.BaseEnduserWebPage;
-import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
-import org.apache.syncope.client.enduser.wizards.any.CaptchaPanel;
-import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.DomainDropDown;
-import org.apache.syncope.common.keymaster.client.api.DomainOps;
-import org.apache.syncope.common.keymaster.client.api.model.Domain;
-import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.to.SecurityQuestionTO;
-import org.apache.syncope.common.rest.api.service.SecurityQuestionService;
-import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
-import org.apache.wicket.ajax.markup.html.AjaxLink;
-import org.apache.wicket.ajax.markup.html.form.AjaxButton;
-import org.apache.wicket.event.IEventSource;
-import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.LoadableDetachableModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.model.PropertyModel;
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-import org.apache.wicket.spring.injection.annot.SpringBean;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class SelfPwdResetPanel extends Panel implements IEventSource {
-
-    private static final long serialVersionUID = -2841210052053545578L;
-
-    private static final Logger LOG = LoggerFactory.getLogger(SelfPwdResetPanel.class);
-
-    @SpringBean
-    private DomainOps domainOps;
-
-    private final LoadableDetachableModel<List<String>> domains = new LoadableDetachableModel<List<String>>() {
-
-        private static final long serialVersionUID = 4659376149825914247L;
-
-        @Override
-        protected List<String> load() {
-            List<String> current = new ArrayList<>();
-            current.addAll(domainOps.list().stream().map(Domain::getKey).sorted().collect(Collectors.toList()));
-            current.add(0, SyncopeConstants.MASTER_DOMAIN);
-            return current;
-        }
-    };
-
-    private String usernameText;
-
-    private String securityAnswerText;
-
-    private final TextField<String> securityQuestion;
-
-    private final CaptchaPanel<Void> captcha;
-
-    public SelfPwdResetPanel(final String id, final PageReference pageRef) {
-        super(id);
-
-        DomainDropDown domainSelect = new DomainDropDown("domain", domains);
-        domainSelect.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) {
-
-            private static final long serialVersionUID = -1107858522700306810L;
-
-            @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
-                // nothing to do
-            }
-        }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
-
-            private static final long serialVersionUID = -1107858522700306810L;
-
-            @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
-                // nothing to do
-            }
-        });
-        add(domainSelect);
-
-        TextField<String> username =
-                new TextField<>("username", new PropertyModel<>(this, "usernameText"), String.class);
-        username.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_BLUR) {
-
-            private static final long serialVersionUID = -1107858522700306810L;
-
-            @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
-                loadSecurityQuestion(pageRef, target);
-            }
-        });
-        username.setRequired(true);
-        add(username);
-
-        securityQuestion =
-                new TextField<>("securityQuestion", new PropertyModel<>(Model.of(), "content"), String.class);
-        securityQuestion.setOutputMarkupId(true);
-        securityQuestion.setEnabled(false);
-        add(securityQuestion);
-
-        AjaxLink<Void> reloadLink = new AjaxLink<>("reloadLink") {
-
-            private static final long serialVersionUID = -817438685948164787L;
-
-            @Override
-            public void onClick(final AjaxRequestTarget target) {
-                loadSecurityQuestion(pageRef, target);
-            }
-        };
-        add(reloadLink);
-
-        TextField<String> securityAnswer =
-                new TextField<>("securityAnswer", new PropertyModel<>(this, "securityAnswerText"), String.class);
-        securityAnswer.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
-
-            private static final long serialVersionUID = -1107858522700306810L;
-
-            @Override
-            protected void onUpdate(final AjaxRequestTarget target) {
-                // do nothing
-            }
-        });
-        securityAnswer.setRequired(true);
-        add(securityAnswer);
-
-        captcha = new CaptchaPanel<>("captchaPanel");
-        captcha.setOutputMarkupPlaceholderTag(true);
-        captcha.setVisible(SyncopeWebApplication.get().isCaptchaEnabled());
-        add(captcha);
-
-        AjaxButton submitButton = new AjaxButton("submit") {
-
-            private static final long serialVersionUID = 4284361595033427185L;
-
-            @Override
-            protected void onSubmit(final AjaxRequestTarget target) {
-                boolean checked = true;
-                if (SyncopeWebApplication.get().isCaptchaEnabled()) {
-                    checked = captcha.captchaCheck();
-                }
-                if (!checked) {
-                    SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
-                    ((BaseEnduserWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
-                } else {
-                    try {
-                        UserSelfRestClient.requestPasswordReset(usernameText, securityAnswerText);
-                        PageParameters parameters = new PageParameters();
-                        parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.pwd.reset.success"));
-                        setResponsePage(getApplication().getHomePage(), parameters);
-                    } catch (SyncopeClientException sce) {
-                        LOG.error("Unable to reset password of [{}]", usernameText, sce);
-                        SyncopeEnduserSession.get().onException(sce);
-                        ((BaseEnduserWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
-                    }
-                }
-            }
-
-        };
-        submitButton.setOutputMarkupId(true);
-        submitButton.setDefaultFormProcessing(false);
-        add(submitButton);
-
-        Button cancel = new Button("cancel") {
-
-            private static final long serialVersionUID = 3669569969172391336L;
-
-            @Override
-            public void onSubmit() {
-                setResponsePage(getApplication().getHomePage());
-            }
-
-        };
-        cancel.setOutputMarkupId(true);
-        cancel.setDefaultFormProcessing(false);
-        add(cancel);
-    }
-
-    protected void loadSecurityQuestion(final PageReference pageRef, final AjaxRequestTarget target) {
-        try {
-            SecurityQuestionTO securityQuestionTO = SyncopeEnduserSession.get().getService(
-                    SecurityQuestionService.class).readByUser(usernameText);
-            // set security question field model
-            securityQuestion.setModel(Model.of(securityQuestionTO.getContent()));
-            target.add(securityQuestion);
-        } catch (Exception e) {
-            LOG.error("Unable to get security question for [{}]", usernameText, e);
-            SyncopeEnduserSession.get().onException(e);
-            ((BaseEnduserWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
-        }
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/Sidebar.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/Sidebar.java
new file mode 100644
index 0000000..bd31754
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/Sidebar.java
@@ -0,0 +1,200 @@
+/*
+ * 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.panels;
+
+import java.util.List;
+import java.util.stream.StreamSupport;
+import org.apache.syncope.client.enduser.BookmarkablePageLinkBuilder;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.pages.BasePage;
+import org.apache.syncope.client.enduser.pages.Dashboard;
+import org.apache.syncope.client.enduser.pages.EditChangePassword;
+import org.apache.syncope.client.enduser.pages.EditSecurityQuestion;
+import org.apache.syncope.client.enduser.pages.EditUser;
+import org.apache.syncope.client.ui.commons.annotations.ExtPage;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+
+public class Sidebar extends Panel {
+
+    private static final long serialVersionUID = 8091307811313529503L;
+
+    protected WebMarkupContainer dashboardLIContainer;
+
+    protected WebMarkupContainer profileULContainer;
+
+    protected WebMarkupContainer profileLIContainer;
+
+    public Sidebar(
+            final String id,
+            final PageReference pageRef,
+            final List<Class<? extends BasePage>> extPageClasses) {
+
+        super(id);
+
+        buildBaseSidebar();
+
+        // set 'active' menu item for everything but extensions
+        // 1. check if current class is set to top-level menu        
+        WebMarkupContainer containingLI = null;
+        if (dashboardLIContainer.getId().equals(
+                getLIContainerId(pageRef.getPage().getClass().getSimpleName().toLowerCase()))) {
+
+            containingLI = dashboardLIContainer;
+        }
+        // 2. if not, check if it is under 'Configuration'
+        if (containingLI == null) {
+            containingLI = (WebMarkupContainer) profileULContainer.get(
+                    getLIContainerId(pageRef.getPage().getClass().getSimpleName().toLowerCase()));
+        }
+        // 3. when found, set CSS coordinates for menu
+        if (containingLI != null) {
+            StreamSupport.stream(containingLI.spliterator(), false).filter(Link.class::isInstance).
+                    forEach(child -> child.add(new Behavior() {
+
+                private static final long serialVersionUID = -5775607340182293596L;
+
+                @Override
+                public void onComponentTag(final Component component, final ComponentTag tag) {
+                    tag.append("class", "active", " ");
+                }
+            }));
+
+            if (profileULContainer.getId().equals(containingLI.getParent().getId())) {
+                profileULContainer.add(new Behavior() {
+
+                    private static final long serialVersionUID = 3109256773218160485L;
+
+                    @Override
+                    public void renderHead(final Component component, final IHeaderResponse response) {
+                        response.render(OnDomReadyHeaderItem.forScript(
+                                "$('#profileLink').addClass('active')"));
+                    }
+
+                    @Override
+                    public void onComponentTag(final Component component, final ComponentTag tag) {
+                        tag.put("class", "nav nav-treeview");
+                        tag.put("style", "display: block;");
+                    }
+                });
+
+                profileLIContainer.add(new Behavior() {
+
+                    private static final long serialVersionUID = 3109256773218160485L;
+
+                    @Override
+                    public void onComponentTag(final Component component, final ComponentTag tag) {
+                        tag.put("class", "nav-item has-treeview menu-open");
+                    }
+                });
+            }
+        }
+
+        ListView<Class<? extends BasePage>> extPages =
+                new ListView<Class<? extends BasePage>>("extPages", extPageClasses) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<Class<? extends BasePage>> item) {
+                WebMarkupContainer containingLI = new WebMarkupContainer("extPageLI");
+                item.add(containingLI);
+
+                ExtPage ann = item.getModelObject().getAnnotation(ExtPage.class);
+
+                BookmarkablePageLink<Page> link = new BookmarkablePageLink<>("extPage", item.getModelObject());
+
+                link.add(new Label("extPageLabel", ann.label()));
+
+                if (item.getModelObject().equals(pageRef.getPage().getClass())) {
+                    link.add(new Behavior() {
+
+                        private static final long serialVersionUID = 1469628524240283489L;
+
+                        @Override
+                        public void renderHead(final Component component, final IHeaderResponse response) {
+                            response.render(OnDomReadyHeaderItem.forScript(
+                                    "$('#extensionsLink').addClass('active')"));
+                        }
+
+                        @Override
+                        public void onComponentTag(final Component component, final ComponentTag tag) {
+                            tag.append("class", "active", " ");
+                        }
+                    });
+                }
+                containingLI.add(link);
+
+                Label extPageIcon = new Label("extPageIcon");
+                extPageIcon.add(new AttributeModifier("class", "nav-icon " + ann.icon()));
+                link.add(extPageIcon);
+            }
+        };
+
+        add(extPages.setRenderBodyOnly(true).setOutputMarkupId(true));
+    }
+
+    protected void buildBaseSidebar() {
+        dashboardLIContainer = new WebMarkupContainer(getLIContainerId("dashboard"));
+        add(dashboardLIContainer);
+        dashboardLIContainer.add(BookmarkablePageLinkBuilder.build(
+                "home", SyncopeWebApplication.get().getPageClass("profile", Dashboard.class)));
+
+        profileLIContainer = new WebMarkupContainer(getLIContainerId("profile"));
+        add(profileLIContainer);
+        profileULContainer = new WebMarkupContainer(getULContainerId("profile"));
+        profileLIContainer.add(profileULContainer);
+
+        WebMarkupContainer liContainer = new WebMarkupContainer(getLIContainerId("edituser"));
+        profileULContainer.add(liContainer);
+        liContainer.add(BookmarkablePageLinkBuilder.build("edituser", EditUser.class));
+
+        liContainer = new WebMarkupContainer(getLIContainerId("editchangepassword"));
+        profileULContainer.add(liContainer);
+        liContainer.add(BookmarkablePageLinkBuilder.build("editchangepassword", EditChangePassword.class));
+
+        liContainer = new WebMarkupContainer(getLIContainerId("editsecurityquestion"));
+        profileULContainer.add(liContainer);
+        liContainer.add(BookmarkablePageLinkBuilder.build("editsecurityquestion", EditSecurityQuestion.class));
+        liContainer.setOutputMarkupPlaceholderTag(true);
+        liContainer.setVisible(SyncopeEnduserSession.get().getPlatformInfo().isPwdResetRequiringSecurityQuestions());
+    }
+
+    protected String getLIContainerId(final String linkId) {
+        return linkId + "LI";
+    }
+
+    protected String getULContainerId(final String linkId) {
+        return linkId + "UL";
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java
new file mode 100644
index 0000000..0a91fa7
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserFormPanel.java
@@ -0,0 +1,161 @@
+/*
+ * 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.panels;
+
+import java.util.List;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.enduser.pages.BasePage;
+import org.apache.syncope.client.enduser.pages.Dashboard;
+import org.apache.syncope.client.enduser.pages.SelfResult;
+import org.apache.syncope.client.enduser.panels.any.Details;
+import org.apache.syncope.client.enduser.panels.any.UserDetails;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.layout.UserForm;
+import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.client.ui.commons.wizards.ModalPanelBuilder;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.AnyOperations;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.IEventSink;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+public class UserFormPanel extends AnyFormPanel implements UserForm {
+
+    private static final long serialVersionUID = 6763365006334514387L;
+
+    private final UserSelfRestClient userSelfRestClient = new UserSelfRestClient();
+
+    public UserFormPanel(
+            final String id,
+            final UserTO userTO,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageReference) {
+        super(id, new UserWrapper(userTO), anyTypeClasses, formLayoutInfo, pageReference);
+
+        UserWrapper modelObj = newModelObject();
+        buildLayout(modelObj);
+    }
+
+    public UserFormPanel(
+            final String id,
+            final UserTO previousUserTO,
+            final UserTO userTO,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageReference) {
+        super(id, new UserWrapper(previousUserTO, userTO), anyTypeClasses, formLayoutInfo, pageReference);
+
+        UserWrapper modelObj = newModelObject();
+        setFormModel(modelObj);
+        buildLayout(modelObj);
+
+    }
+
+    @Override
+    protected Details<UserTO> addOptionalDetailsPanel(final UserWrapper modelObject) {
+        return new UserDetails(
+                EnduserConstants.CONTENT_PANEL,
+                UserWrapper.class.cast(modelObject),
+                false,
+                false,
+                pageRef);
+    }
+
+    @Override
+    protected void onFormSubmit(final AjaxRequestTarget target) {
+        // captcha check
+        boolean checked = true;
+        if (SyncopeWebApplication.get().isCaptchaEnabled()) {
+            checked = captcha.check();
+        }
+        if (!checked) {
+            SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
+            ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        } else {
+            ProvisioningResult<UserTO> result;
+            PageParameters parameters = new PageParameters();
+            try {
+                AnyWrapper<UserTO> updatedWrapper = form.getModelObject();
+                UserTO userTO = updatedWrapper.getInnerObject();
+
+                fixPlainAndVirAttrs(userTO, getOriginalItem().getInnerObject());
+                UserUR req = AnyOperations.diff(userTO, getOriginalItem().getInnerObject(), false);
+
+                // update just if it is changed
+                if (req.isEmpty()) {
+                    result = new ProvisioningResult<>();
+                    result.setEntity(userTO);
+                } else {
+                    result = userSelfRestClient.update(getOriginalItem().getInnerObject().getETagValue(), req);
+                    LOG.debug("User {} has been modified", result.getEntity().getUsername());
+                }
+                parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+                parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.profile.change.success"));
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.profile.change.success.msg"));
+            } catch (SyncopeClientException sce) {
+                parameters.add(EnduserConstants.STATUS, Constants.ERROR);
+                parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.profile.change.error"));
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.profile.change.error.msg"));
+                SyncopeEnduserSession.get().onException(sce);
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+            parameters.add(
+                    EnduserConstants.LANDING_PAGE,
+                    SyncopeWebApplication.get().getPageClass("profile", Dashboard.class).getName());
+            setResponsePage(SelfResult.class, parameters);
+        }
+    }
+
+    @Override
+    public IEventSink getEventSink() {
+        return null;
+    }
+
+    @Override
+    public ModalPanelBuilder<AnyWrapper<UserTO>> setEventSink(final IEventSink eventSink) {
+        return null;
+    }
+
+    @Override
+    public ModalPanelBuilder<AnyWrapper<UserTO>> setItem(final AnyWrapper<UserTO> item) {
+        return null;
+    }
+
+    @Override
+    public AnyWrapper<UserTO> getDefaultItem() {
+        return null;
+    }
+
+    @Override
+    public WizardModalPanel<AnyWrapper<UserTO>> build(final String id, final int index, final AjaxWizard.Mode mode) {
+        return null;
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
new file mode 100644
index 0000000..a3a610d
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserSelfFormPanel.java
@@ -0,0 +1,135 @@
+/*
+ * 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.panels;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.SyncopeWebApplication;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
+import org.apache.syncope.client.enduser.pages.BasePage;
+import org.apache.syncope.client.enduser.pages.Login;
+import org.apache.syncope.client.enduser.pages.SelfResult;
+import org.apache.syncope.client.enduser.panels.any.Details;
+import org.apache.syncope.client.enduser.panels.any.SelfUserDetails;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.EntityTOUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.request.UserCR;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.service.SecurityQuestionService;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import java.util.List;
+import org.apache.syncope.client.enduser.commons.EnduserConstants;
+
+public class UserSelfFormPanel extends UserFormPanel {
+
+    private static final long serialVersionUID = 6763365006334514387L;
+
+    private final UserSelfRestClient userSelfRestClient = new UserSelfRestClient();
+
+    private TextField<String> securityQuestion;
+
+    private String usernameText;
+
+    public UserSelfFormPanel(
+            final String id,
+            final UserTO previousUserTO,
+            final UserTO userTO,
+            final List<String> anyTypeClasses,
+            final UserFormLayoutInfo formLayoutInfo,
+            final PageReference pageReference) {
+        super(id, previousUserTO, userTO, anyTypeClasses, formLayoutInfo, pageReference);
+    }
+
+    @Override
+    protected Details<UserTO> addOptionalDetailsPanel(final UserWrapper modelObject) {
+        return new SelfUserDetails(
+                EnduserConstants.CONTENT_PANEL,
+                UserWrapper.class.cast(modelObject),
+                false,
+                false,
+                UserFormLayoutInfo.class.cast(formLayoutInfo).isPasswordManagement(),
+                pageRef);
+    }
+
+    @Override
+    protected void onFormSubmit(final AjaxRequestTarget target) {
+        // captcha check
+        boolean checked = true;
+        if (SyncopeWebApplication.get().isCaptchaEnabled()) {
+            checked = captcha.check();
+        }
+        if (!checked) {
+            SyncopeEnduserSession.get().error(getString(Constants.CAPTCHA_ERROR));
+            ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        } else {
+            ProvisioningResult<UserTO> result;
+            PageParameters parameters = new PageParameters();
+            try {
+                AnyWrapper<UserTO> updatedWarapper = form.getModelObject();
+                UserTO userTO = updatedWarapper.getInnerObject();
+
+                UserCR req = new UserCR();
+                EntityTOUtils.toAnyCR(userTO, req);
+                req.setStorePassword(updatedWarapper instanceof UserWrapper
+                        ? UserWrapper.class.cast(updatedWarapper).isStorePasswordInSyncope()
+                        : StringUtils.isNotBlank(userTO.getPassword()));
+
+                result = userSelfRestClient.create(req, true);
+                LOG.debug("User {} has been created", result.getEntity().getUsername());
+
+                parameters.add(EnduserConstants.STATUS, Constants.OPERATION_SUCCEEDED);
+                parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.profile.change.success"));
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.profile.change.success.msg"));
+            } catch (SyncopeClientException sce) {
+                parameters.add(EnduserConstants.STATUS, Constants.ERROR);
+                parameters.add(Constants.NOTIFICATION_TITLE_PARAM, getString("self.profile.change.error"));
+                parameters.add(Constants.NOTIFICATION_MSG_PARAM, getString("self.profile.change.error.msg"));
+                SyncopeEnduserSession.get().onException(sce);
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+            parameters.add(EnduserConstants.LANDING_PAGE, Login.class);
+            setResponsePage(SelfResult.class, parameters);
+        }
+    }
+
+    protected void loadSecurityQuestion(final PageReference pageRef, final AjaxRequestTarget target) {
+        try {
+            SecurityQuestionTO securityQuestionTO = SyncopeEnduserSession.get().getService(
+                    SecurityQuestionService.class).readByUser(usernameText);
+            // set security question field model
+            securityQuestion.setModel(Model.of(securityQuestionTO.getContent()));
+            target.add(securityQuestion);
+        } catch (Exception e) {
+            LOG.error("Unable to get security question for [{}]", usernameText, e);
+            SyncopeEnduserSession.get().onException(e);
+            ((BaseWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        }
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
similarity index 85%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
index be2c63d..9f6e4ec 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.java
@@ -16,41 +16,41 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.wizards.any;
+package org.apache.syncope.client.enduser.panels.any;
 
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.syncope.client.enduser.layout.CustomizationOption;
 import org.apache.syncope.client.enduser.rest.SchemaRestClient;
 import org.apache.syncope.client.enduser.rest.SyncopeRestClient;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.common.lib.to.SchemaTO;
-import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.SchemaTO;
 import org.apache.syncope.common.lib.types.SchemaType;
-import org.apache.wicket.PageReference;
 import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.core.util.lang.PropertyResolver;
-import org.apache.wicket.extensions.wizard.WizardModel.ICondition;
-import org.apache.wicket.extensions.wizard.WizardStep;
-import org.apache.wicket.markup.head.IHeaderResponse;
-import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.event.IEvent;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.util.ListModel;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
-public abstract class AbstractAttrs<S extends SchemaTO> extends WizardStep implements ICondition {
+public abstract class AbstractAttrs<S extends SchemaTO> extends Panel {
 
     private static final long serialVersionUID = -5387344116983102292L;
 
+    protected static final String FORM_SUFFIX = "form_";
+
     protected final Comparator<Attr> attrComparator = new AttrComparator();
 
     protected final AnyTO anyTO;
@@ -68,18 +68,21 @@
     private final List<String> anyTypeClasses;
 
     public AbstractAttrs(
+            final String id,
             final AnyWrapper<?> modelObject,
             final List<String> anyTypeClasses,
             final Map<String, CustomizationOption> whichAttrs) {
-        super();
+        super(id);
         this.anyTypeClasses = anyTypeClasses;
-        this.attrs = new ListModel<>(List.of());
-        this.membershipTOs = new ListModel<>(List.of());
+        this.attrs = new ListModel<>(Collections.emptyList());
+        this.membershipTOs = new ListModel<>(Collections.emptyList());
 
         this.setOutputMarkupId(true);
 
         this.anyTO = modelObject.getInnerObject();
         this.whichAttrs = whichAttrs;
+
+        evaluate();
     }
 
     private List<Attr> loadAttrs() {
@@ -99,9 +102,8 @@
 
             for (MembershipTO membership : (List<MembershipTO>) PropertyResolver.getPropertyField(
                     "memberships", anyTO).get(anyTO)) {
-                setSchemas(
-                        Pair.of(membership.getGroupKey(), membership.getGroupName()),
-                        getMembershipAuxClasses(membership));
+                setSchemas(Pair.of(membership.getGroupKey(), membership.getGroupName()), getMembershipAuxClasses(
+                        membership, anyTO.getType()));
                 setAttrs(membership);
 
                 if (AbstractAttrs.this instanceof PlainAttrs && !membership.getPlainAttrs().isEmpty()) {
@@ -142,7 +144,7 @@
                 : groupName + '#')
                 + schema;
         return whichAttrs.get(schemaName) == null
-                ? List.of()
+                ? Collections.emptyList()
                 : whichAttrs.get(schemaName).getDefaultValues();
     }
 
@@ -185,15 +187,8 @@
         allSchemas.forEach(schemaTO -> scs.put(schemaTO.getKey(), schemaTO));
     }
 
-    @Override
-    public void renderHead(final IHeaderResponse response) {
-        super.renderHead(response);
-        if (org.apache.cxf.common.util.CollectionUtils.isEmpty(attrs.getObject())
-                && org.apache.cxf.common.util.CollectionUtils.isEmpty(membershipTOs.getObject())) {
-            response.render(OnDomReadyHeaderItem.forScript(
-                    String.format("$('#emptyPlaceholder').append(\"%s\"); $('#attributes').hide();",
-                            getString("attribute.empty.list"))));
-        }
+    public boolean isPanelVisible() {
+        return !attrs.getObject().isEmpty() || !membershipTOs.getObject().isEmpty();
     }
 
     protected abstract void setAttrs();
@@ -204,27 +199,26 @@
 
     protected abstract List<Attr> getAttrsFromTO(MembershipTO membershipTO);
 
-    protected static List<String> getMembershipAuxClasses(final MembershipTO membershipTO) {
+    protected static List<String> getMembershipAuxClasses(final MembershipTO membershipTO, final String anyType) {
         try {
             return SyncopeRestClient.searchUserTypeExtensions(membershipTO.getGroupName());
         } catch (Exception e) {
-            return List.of();
+            return Collections.emptyList();
         }
     }
 
     @Override
+    protected void onInitialize() {
+        evaluate();
+        super.onInitialize();
+    }
+
     public boolean evaluate() {
         this.attrs.setObject(loadAttrs());
         this.membershipTOs.setObject(loadMembershipAttrs());
         return !attrs.getObject().isEmpty() || !membershipTOs.getObject().isEmpty();
     }
 
-    public PageReference getPageReference() {
-        // SYNCOPE-1213
-        // default implementation does not require to pass page reference, override this method of want otherwise
-        return null;
-    }
-
     private class AttrComparator implements Comparator<Attr>, Serializable {
 
         private static final long serialVersionUID = -5105030477767941060L;
@@ -253,6 +247,16 @@
         }
     }
 
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        super.onEvent(event);
+        if (event.getPayload() instanceof AjaxPalettePanel.UpdateActionEvent) {
+            evaluate();
+            AjaxPalettePanel.UpdateActionEvent updateEvent = (AjaxPalettePanel.UpdateActionEvent) event.getPayload();
+            updateEvent.getTarget().add(this);
+        }
+    }
+
     public static class Schemas extends Panel {
 
         private static final long serialVersionUID = -2447602429647965090L;
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/DerAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java
similarity index 90%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/DerAttrs.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java
index 952b834..fea60ce 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/DerAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/DerAttrs.java
@@ -16,27 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.wizards.any;
+package org.apache.syncope.client.enduser.panels.any;
 
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.layout.CustomizationOption;
-import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.to.DerSchemaTO;
 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.MarkupStream;
 import org.apache.wicket.markup.html.WebMarkupContainer;
@@ -47,30 +48,21 @@
 import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.model.util.ListModel;
+import java.util.stream.Collectors;
 
 public class DerAttrs extends AbstractAttrs<DerSchemaTO> {
 
     private static final long serialVersionUID = -5387344116983102292L;
 
     public <T extends AnyTO> DerAttrs(
+            final String id,
             final AnyWrapper<T> modelObject,
             final List<String> anyTypeClasses,
             final Map<String, CustomizationOption> whichDerAttrs) {
 
-        super(modelObject, anyTypeClasses, whichDerAttrs);
-        setTitleModel(new ResourceModel("attributes.derived"));
+        super(id, modelObject, anyTypeClasses, whichDerAttrs);
 
-        add(new Accordion("derSchemas", List.of(new AbstractTab(
-                new ResourceModel("attributes.accordion", "Derived Attributes")) {
-
-            private static final long serialVersionUID = 1037272333056449378L;
-
-            @Override
-            public WebMarkupContainer getPanel(final String panelId) {
-                return new DerAttrs.DerSchemas(panelId, schemas, attrs);
-            }
-        }), Model.of(0)).setOutputMarkupId(true));
-
+        add(new DerAttrs.DerSchemas("derSchemas", schemas, attrs).setOutputMarkupId(true));
         add(new ListView<MembershipTO>("membershipsDerSchemas", membershipTOs) {
 
             private static final long serialVersionUID = 6741044372185745296L;
@@ -78,7 +70,7 @@
             @Override
             protected void populateItem(final ListItem<MembershipTO> item) {
                 final MembershipTO membershipTO = item.getModelObject();
-                item.add(new Accordion("membershipDerSchemas", List.of(new AbstractTab(
+                item.add(new Accordion("membershipDerSchemas", Collections.<ITab>singletonList(new AbstractTab(
                         new StringResourceModel(
                                 "attributes.membership.accordion",
                                 DerAttrs.this,
@@ -159,7 +151,7 @@
         membershipTO.getDerAttrs().addAll(derAttrs);
     }
 
-    public static class DerSchemas extends Schemas {
+    public class DerSchemas extends Schemas {
 
         private static final long serialVersionUID = -4730563859116024676L;
 
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java
new file mode 100644
index 0000000..23c9bdf
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Details.java
@@ -0,0 +1,45 @@
+/*
+ * 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.panels.any;
+
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Details<T extends AnyTO> extends Panel {
+
+    private static final long serialVersionUID = -8995647450549098844L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(Details.class);
+
+    protected final PageReference pageRef;
+
+    public Details(
+            final String id,
+            final AnyWrapper<T> wrapper,
+            final boolean templateMode,
+            final boolean includeStatusPanel,
+            final PageReference pageRef) {
+        super(id);
+        this.pageRef = pageRef;
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Groups.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
similarity index 71%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Groups.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
index 7626f73..5b3912a 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Groups.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Groups.java
@@ -16,35 +16,79 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.wizards.any;
+package org.apache.syncope.client.enduser.panels.any;
 
-import java.util.List;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.rest.GroupRestClient;
-import org.apache.syncope.client.lib.SyncopeClient;
-import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroups;
 import org.apache.syncope.client.ui.commons.wizards.any.AbstractGroupsModel;
 import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.to.GroupTO;
-import org.apache.syncope.common.lib.to.GroupableRelatableTO;
-import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.util.ListModel;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.commons.collections4.ListUtils;
+import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.GroupableRelatableTO;
+import org.apache.syncope.common.lib.to.MembershipTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
 
-public class Groups extends AbstractGroups {
+public class Groups extends Panel {
 
     private static final long serialVersionUID = 552437609667518888L;
 
+    protected static final int MAX_GROUP_LIST_CARDINALITY = 30;
+
     private final EnduserGroupsModel groupsModel;
 
-    public <T extends AnyTO> Groups(final AnyWrapper<T> modelObject) {
-        super(modelObject);
+    protected final AnyTO anyTO;
+
+    protected WebMarkupContainer dyngroupsContainer;
+
+    protected WebMarkupContainer dynrealmsContainer;
+
+    protected WebMarkupContainer groupsContainer;
+
+    public <T extends AnyTO> Groups(final String id,
+            final AnyWrapper<T> modelObject,
+            final boolean templateMode) {
+
+        super(id);
+        this.anyTO = modelObject.getInnerObject();
+
+        setOutputMarkupId(true);
+
+        groupsContainer = new WebMarkupContainer("groupsContainer");
+        groupsContainer.setOutputMarkupId(true);
+        groupsContainer.setOutputMarkupPlaceholderTag(true);
+        add(groupsContainer);
+
+        // ------------------
+        // insert changed label if needed
+        // ------------------
+        if (modelObject instanceof UserWrapper
+                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
+                && !ListUtils.isEqualList(
+                        UserWrapper.class.cast(modelObject).getInnerObject().getMemberships(),
+                        UserWrapper.class.cast(modelObject).getPreviousUserTO().getMemberships())) {
+            groupsContainer.add(new LabelInfo("changed", StringUtils.EMPTY));
+        } else {
+            groupsContainer.add(new Label("changed", StringUtils.EMPTY));
+        }
+        // ------------------
+
         this.groupsModel = new EnduserGroupsModel();
 
         setOutputMarkupId(true);
@@ -54,7 +98,14 @@
         addDynamicRealmsContainer();
     }
 
-    @Override
+    private Function<AjaxRequestTarget, Boolean> getEventFunction() {
+        return (Function<AjaxRequestTarget, Boolean> & Serializable) (target) -> {
+            send(Groups.this.getPage(), Broadcast.BREADTH,
+                    new AjaxPalettePanel.UpdateActionEvent((UserTO) anyTO, target));
+            return true;
+        };
+    }
+
     protected void addGroupsPanel() {
         if (anyTO instanceof GroupTO) {
             groupsContainer.add(new Label("groups").setVisible(false));
@@ -82,7 +133,7 @@
                             return choices.getObject().stream().
                                     filter(object -> id.equalsIgnoreCase(object.getGroupName())).findAny().orElse(null);
                         }
-                    });
+                    }).event(getEventFunction());
 
             groupsContainer.add(builder.setAllowOrder(true).withFilter().build("groups",
                     new ListModel<MembershipTO>() {
@@ -104,8 +155,7 @@
                             ? groupsModel.getObject()
                             : GroupRestClient.searchAssignableGroups(
                                     anyTO.getRealm(),
-                                    SyncopeClient.getGroupSearchConditionBuilder().
-                                            isAssignable().and().is(Constants.NAME_FIELD_NAME).equalTo(filter).query(),
+                                    filter,
                                     1, MAX_GROUP_LIST_CARDINALITY)).stream()
                             .map(input -> new MembershipTO.Builder(input.getKey())
                             .groupName(input.getName()).build()).collect(Collectors.toList());
@@ -115,11 +165,9 @@
         }
     }
 
-    @Override
     protected void addDynamicRealmsContainer() {
     }
 
-    @Override
     protected void addDynamicGroupsContainer() {
     }
 
@@ -167,7 +215,7 @@
 
         @Override
         public List<String> getDynMemberships() {
-            return List.of();
+            return Collections.emptyList();
         }
 
         /**
@@ -193,4 +241,7 @@
             }
         }
     }
+
+    public interface SerializableFunction extends Function<AjaxRequestTarget, Boolean>, Serializable {
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java
similarity index 75%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java
index 281ca92..a25c76d 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/PlainAttrs.java
@@ -16,113 +16,86 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.wizards.any;
+package org.apache.syncope.client.enduser.panels.any;
 
 import java.util.ArrayList;
-import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.FastDateFormat;
 import org.apache.syncope.client.enduser.layout.CustomizationOption;
-import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateFieldPanel;
 import org.apache.syncope.client.enduser.markup.html.form.BinaryFieldPanel;
 import org.apache.syncope.client.enduser.markup.html.form.MultiFieldPanel;
-import org.apache.syncope.client.ui.commons.markup.html.form.EncryptedFieldPanel;
 import org.apache.syncope.client.ui.commons.SchemaUtils;
+import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.Attributable;
+import org.apache.syncope.common.lib.EntityTOUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.SchemaType;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.extensions.markup.html.tabs.ITab;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.util.ListModel;
+import java.util.stream.Collectors;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.ui.commons.markup.html.form.AbstractFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateTimeFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.EncryptedFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
-import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
-import org.apache.syncope.common.lib.EntityTOUtils;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.Attr;
-import org.apache.syncope.common.lib.Attributable;
-import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.PlainSchemaTO;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.lib.types.AttrSchemaType;
-import org.apache.syncope.common.lib.types.SchemaType;
-import org.apache.wicket.PageReference;
-import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
-import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.markup.html.form.IChoiceRenderer;
-import org.apache.wicket.markup.html.list.ListItem;
-import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
-import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.StringResourceModel;
-import org.apache.wicket.model.util.ListModel;
 
 public class PlainAttrs extends AbstractAttrs<PlainSchemaTO> {
 
     private static final long serialVersionUID = 552437609667518888L;
 
-    protected final AjaxWizard.Mode mode;
-
     protected final AnyTO previousObject;
 
     protected String fileKey = "";
 
-    public <T extends AnyTO> PlainAttrs(
-            final AnyWrapper<T> modelObject,
-            final AjaxWizard.Mode mode,
+    public PlainAttrs(
+            final String id,
+            final UserWrapper modelObject,
             final List<String> anyTypeClasses,
             final Map<String, CustomizationOption> whichPlainAttrs) throws IllegalArgumentException {
 
-        super(modelObject, anyTypeClasses, whichPlainAttrs);
-        this.mode = mode;
+        super(id, modelObject, anyTypeClasses, whichPlainAttrs);
 
-        if (modelObject.getInnerObject() instanceof UserTO) {
-            fileKey = UserTO.class.cast(modelObject.getInnerObject()).getUsername();
-        } else if (modelObject.getInnerObject() instanceof GroupTO) {
-            fileKey = GroupTO.class.cast(modelObject.getInnerObject()).getName();
-        } else if (modelObject.getInnerObject() instanceof AnyObjectTO) {
-            fileKey = AnyObjectTO.class.cast(modelObject.getInnerObject()).getName();
-        }
+        fileKey = modelObject.getInnerObject().getUsername();
 
-        if (modelObject instanceof UserWrapper) {
-            previousObject = UserWrapper.class.cast(modelObject).getPreviousUserTO();
-        } else {
-            previousObject = null;
-        }
+        previousObject = modelObject.getPreviousUserTO();
 
-        setTitleModel(new ResourceModel("attributes.plain"));
-
-        add(new Accordion("plainSchemas", List.of(new AbstractTab(
-                new ResourceModel("attributes.accordion", "Plain Attributes")) {
-
-            private static final long serialVersionUID = 1037272333056449378L;
-
-            @Override
-            public WebMarkupContainer getPanel(final String panelId) {
-                return new PlainSchemasOwn(panelId, schemas, attrs);
-            }
-        }), Model.of(0)).setOutputMarkupId(true));
-
+        add(new PlainSchemasOwn("plainSchemas", schemas, attrs).setOutputMarkupId(true));
         add(new ListView<MembershipTO>("membershipsPlainSchemas", membershipTOs) {
 
-            private static final long serialVersionUID = 1749643897846L;
+            private static final long serialVersionUID = 6741044372185745296L;
 
             @Override
             protected void populateItem(final ListItem<MembershipTO> item) {
                 final MembershipTO membershipTO = item.getModelObject();
-                item.add(new Accordion("membershipPlainSchemas", List.of(new AbstractTab(
+                item.add(new Accordion("membershipPlainSchemas", Collections.<ITab>singletonList(new AbstractTab(
                         new StringResourceModel(
                                 "attributes.membership.accordion",
                                 PlainAttrs.this,
@@ -158,11 +131,6 @@
     }
 
     @Override
-    protected boolean filterSchemas() {
-        return super.filterSchemas() && mode != AjaxWizard.Mode.TEMPLATE;
-    }
-
-    @Override
     protected List<Attr> getAttrsFromTO() {
         return anyTO.getPlainAttrs().stream().sorted(attrComparator).collect(Collectors.toList());
     }
@@ -174,11 +142,11 @@
 
     @Override
     protected void setAttrs() {
-        List<Attr> attrs = new ArrayList<>();
+        List<Attr> plainAttrs = new ArrayList<>();
 
         Map<String, Attr> attrMap = EntityTOUtils.buildAttrMap(anyTO.getPlainAttrs());
 
-        attrs.addAll(schemas.values().stream().map(schema -> {
+        plainAttrs.addAll(schemas.values().stream().map(schema -> {
             Attr attrTO = new Attr();
             attrTO.setSchema(schema.getKey());
             if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) {
@@ -190,7 +158,7 @@
         }).collect(Collectors.toList()));
 
         anyTO.getPlainAttrs().clear();
-        anyTO.getPlainAttrs().addAll(attrs);
+        anyTO.getPlainAttrs().addAll(plainAttrs);
     }
 
     @Override
@@ -205,17 +173,16 @@
             attrMap = new HashMap<>();
         }
 
-        plainAttrs.addAll(membershipSchemas.get(membershipTO.getGroupKey()).values().stream().
-                map(schema -> {
-                    Attr attrTO = new Attr();
-                    attrTO.setSchema(schema.getKey());
-                    if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) {
-                        attrTO.getValues().add(StringUtils.EMPTY);
-                    } else {
-                        attrTO.getValues().addAll(attrMap.get(schema.getKey()).getValues());
-                    }
-                    return attrTO;
-                }).collect(Collectors.toList()));
+        plainAttrs.addAll(membershipSchemas.get(membershipTO.getGroupKey()).values().stream().map(schema -> {
+            Attr attr = new Attr();
+            attr.setSchema(schema.getKey());
+            if (attrMap.get(schema.getKey()) == null || attrMap.get(schema.getKey()).getValues().isEmpty()) {
+                attr.getValues().add(StringUtils.EMPTY);
+            } else {
+                attr.getValues().addAll(attrMap.get(schema.getKey()).getValues());
+            }
+            return attr;
+        }).collect(Collectors.toList()));
 
         membershipTO.getPlainAttrs().clear();
         membershipTO.getPlainAttrs().addAll(plainAttrs);
@@ -238,7 +205,7 @@
             case Boolean:
                 panel = new AjaxCheckBoxPanel(
                         "panel",
-                        schemaTO.getLabel(getLocale()),
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()),
                         new Model<>(),
                         true);
                 panel.setRequired(required);
@@ -252,13 +219,13 @@
                 if (datePattern.contains("H")) {
                     panel = new AjaxDateTimeFieldPanel(
                             "panel",
-                            schemaTO.getLabel(getLocale()),
+                            schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()),
                             new Model<>(),
                             FastDateFormat.getInstance(datePattern));
                 } else {
                     panel = new AjaxDateFieldPanel(
                             "panel",
-                            schemaTO.getLabel(getLocale()),
+                            schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()),
                             new Model<>(),
                             FastDateFormat.getInstance(datePattern));
                 }
@@ -271,7 +238,7 @@
 
             case Enum:
                 panel = new AjaxDropDownChoicePanel<>("panel",
-                        schemaTO.getLabel(getLocale()), new Model<>(), true);
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()), new Model<>(), true);
                 ((AjaxDropDownChoicePanel<String>) panel).setChoices(SchemaUtils.getEnumeratedValues(schemaTO));
 
                 if (StringUtils.isNotBlank(schemaTO.getEnumerationKeys())) {
@@ -307,7 +274,7 @@
             case Long:
                 panel = new AjaxSpinnerFieldPanel.Builder<Long>().enableOnChange().build(
                         "panel",
-                        schemaTO.getLabel(getLocale()),
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()),
                         Long.class,
                         new Model<>());
 
@@ -319,7 +286,7 @@
             case Double:
                 panel = new AjaxSpinnerFieldPanel.Builder<Double>().enableOnChange().step(0.1).build(
                         "panel",
-                        schemaTO.getLabel(getLocale()),
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()),
                         Double.class,
                         new Model<>());
 
@@ -329,22 +296,12 @@
                 break;
 
             case Binary:
-                final PageReference pageRef = getPageReference();
                 panel = new BinaryFieldPanel(
                         "panel",
-                        schemaTO.getLabel(getLocale()),
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()),
                         new Model<>(),
                         schemaTO.getMimeType(),
-                        fileKey) {
-
-                    private static final long serialVersionUID = -3268213909514986831L;
-
-                    @Override
-                    protected PageReference getPageReference() {
-                        return pageRef;
-                    }
-
-                };
+                        fileKey);
                 if (required) {
                     panel.addRequiredLabel();
                 }
@@ -352,7 +309,7 @@
 
             case Encrypted:
                 panel = new EncryptedFieldPanel("panel",
-                        schemaTO.getLabel(getLocale()), new Model<>(), true);
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()), new Model<>(), true);
 
                 if (required) {
                     panel.addRequiredLabel();
@@ -361,7 +318,7 @@
 
             default:
                 panel = new AjaxTextFieldPanel("panel",
-                        schemaTO.getLabel(getLocale()), new Model<>(), true);
+                        schemaTO.getLabel(SyncopeEnduserSession.get().getLocale()), new Model<>(), true);
 
                 if (jexlHelp) {
                     AjaxTextFieldPanel.class.cast(panel).enableJexlHelp();
@@ -375,6 +332,10 @@
         panel.setReadOnly(readOnly);
         panel.setMarkupId(StringUtils.isBlank(groupName) ? schemaTO.getKey() : groupName + '.' + schemaTO.getKey());
 
+        Label label = (Label) panel.get(AbstractFieldPanel.LABEL);
+        label.add(new AttributeModifier("for", FORM_SUFFIX
+                + (StringUtils.isBlank(groupName) ? schemaTO.getKey() : groupName + '.' + schemaTO.getKey())));
+
         return panel;
     }
 
@@ -400,25 +361,25 @@
                 @Override
                 @SuppressWarnings({ "unchecked", "rawtypes" })
                 protected void populateItem(final ListItem<Attr> item) {
-                    Attr attr = item.getModelObject();
-                    PlainSchemaTO schema = schemas.get(attr.getSchema());
+                    Attr attrTO = item.getModelObject();
+                    PlainSchemaTO schema = schemas.get(attrTO.getSchema());
 
                     // set default values, if any
-                    if (attr.getValues().stream().noneMatch(StringUtils::isNotBlank)) {
-                        attr.getValues().clear();
-                        attr.getValues().addAll(getDefaultValues(attr.getSchema(), groupName));
+                    if (attrTO.getValues().stream().noneMatch(StringUtils::isNotBlank)) {
+                        attrTO.getValues().clear();
+                        attrTO.getValues().addAll(getDefaultValues(attrTO.getSchema(), groupName));
                     }
 
-                    AbstractFieldPanel<?> panel = getFieldPanel(schemas.get(attr.getSchema()));
-                    if (schemas.get(attr.getSchema()).isMultivalue()) {
+                    AbstractFieldPanel<?> panel = getFieldPanel(schemas.get(attrTO.getSchema()));
+                    if (schemas.get(attrTO.getSchema()).isMultivalue()) {
                         panel = new MultiFieldPanel.Builder<>(
                                 new PropertyModel<>(
-                                        attributableTO.getObject().getPlainAttr(attr.getSchema()), "values"))
-                                .build("panel", attr.getSchema(), FieldPanel.class.cast(panel));
+                                        attributableTO.getObject().getPlainAttr(attrTO.getSchema()), "values"))
+                                .build("panel", attrTO.getSchema(), FieldPanel.class.cast(panel));
                         // SYNCOPE-1215 the entire multifield panel must be readonly, not only its field
                         ((MultiFieldPanel) panel).setReadOnly(schema == null ? false : schema.isReadonly());
                     } else {
-                        FieldPanel.class.cast(panel).setNewModel(attr.getValues()).
+                        FieldPanel.class.cast(panel).setNewModel(attrTO.getValues()).
                                 setReadOnly(schema == null ? false : schema.isReadonly());
                     }
 
@@ -446,26 +407,26 @@
                 @Override
                 @SuppressWarnings({ "unchecked", "rawtypes" })
                 protected void populateItem(final ListItem<Attr> item) {
-                    Attr attr = item.getModelObject();
-                    PlainSchemaTO schema = schemas.get(attr.getSchema());
+                    Attr attrTO = item.getModelObject();
+                    PlainSchemaTO schema = schemas.get(attrTO.getSchema());
 
                     // set default values, if any
-                    if (attr.getValues().stream().noneMatch(StringUtils::isNotBlank)) {
-                        attr.getValues().clear();
-                        attr.getValues().addAll(getDefaultValues(attr.getSchema()));
+                    if (attrTO.getValues().stream().noneMatch(StringUtils::isNotBlank)) {
+                        attrTO.getValues().clear();
+                        attrTO.getValues().addAll(getDefaultValues(attrTO.getSchema()));
                     }
 
-                    AbstractFieldPanel<?> panel = getFieldPanel(schemas.get(attr.getSchema()));
-                    if (schemas.get(attr.getSchema()).isMultivalue()) {
+                    AbstractFieldPanel<?> panel = getFieldPanel(schemas.get(attrTO.getSchema()));
+                    if (schemas.get(attrTO.getSchema()).isMultivalue()) {
                         panel = new MultiFieldPanel.Builder<>(
-                                new PropertyModel<>(attr, "values")).build(
+                                new PropertyModel<>(attrTO, "values")).build(
                                 "panel",
-                                attr.getSchema(),
+                                attrTO.getSchema(),
                                 FieldPanel.class.cast(panel));
                         // SYNCOPE-1215 the entire multifield panel must be readonly, not only its field
                         ((MultiFieldPanel) panel).setReadOnly(schema == null ? false : schema.isReadonly());
                     } else {
-                        FieldPanel.class.cast(panel).setNewModel(attr.getValues()).
+                        FieldPanel.class.cast(panel).setNewModel(attrTO.getValues()).
                                 setReadOnly(schema == null ? false : schema.isReadonly());
                     }
                     item.add(panel);
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
new file mode 100644
index 0000000..93265e8
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/Resources.java
@@ -0,0 +1,87 @@
+/*
+ * 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.panels.any;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.to.AnyTO;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.util.ListModel;
+
+public class Resources extends Panel {
+
+    private static final long serialVersionUID = 702900610508752856L;
+
+    protected final ListModel<String> available;
+
+    public <T extends AnyTO> Resources(final String id, final AnyWrapper<T> modelObject) {
+        super(id);
+        final T entityTO = modelObject.getInnerObject();
+
+        if (modelObject instanceof UserWrapper
+                && UserWrapper.class.cast(modelObject).getPreviousUserTO() != null
+                && !modelObject.getInnerObject().getResources().equals(
+                        UserWrapper.class.cast(modelObject).getPreviousUserTO().getResources())) {
+
+            add(new LabelInfo("changed", StringUtils.EMPTY));
+        } else {
+            add(new Label("changed", StringUtils.EMPTY));
+        }
+
+        this.setOutputMarkupId(true);
+        this.available = new ListModel<>(List.of());
+
+        add(new AjaxPalettePanel.Builder<String>().build("resources",
+                new PropertyModel<List<String>>(entityTO, "resources") {
+
+            private static final long serialVersionUID = 3799387950428254072L;
+
+            @Override
+            public List<String> getObject() {
+                return new ArrayList<>(entityTO.getResources());
+            }
+
+            @Override
+            public void setObject(final List<String> object) {
+                entityTO.getResources().clear();
+                entityTO.getResources().addAll(object);
+            }
+        }, available).hideLabel().setOutputMarkupId(true));
+    }
+
+    @Override
+    protected void onInitialize() {
+        super.onInitialize();
+        available.setObject(SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getResources());
+    }
+
+    public boolean evaluate() {
+        available.setObject(SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getResources());
+        return !available.getObject().isEmpty();
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java
new file mode 100644
index 0000000..3918934
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.java
@@ -0,0 +1,46 @@
+/*
+ * 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.panels.any;
+
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.wicket.PageReference;
+
+public class SelfUserDetails extends UserDetails {
+
+    private static final long serialVersionUID = 6592027822510220469L;
+
+    public SelfUserDetails(
+            final String id,
+            final UserWrapper wrapper,
+            final boolean templateMode,
+            final boolean includeStatusPanel,
+            final boolean showPasswordManagement,
+            final PageReference pageRef) {
+
+        super(id, wrapper, templateMode, includeStatusPanel, pageRef);
+
+        // ------------------------
+        // Password
+        // ------------------------
+        EditUserPasswordPanel panel = new EditUserPasswordPanel("password", wrapper, templateMode);
+        panel.setVisible(showPasswordManagement);
+
+        add(panel);
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
new file mode 100644
index 0000000..8897db7
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/UserDetails.java
@@ -0,0 +1,113 @@
+/*
+ * 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.panels.any;
+
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthConfig;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.rest.RealmRestClient;
+import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
+import org.apache.syncope.client.ui.commons.wizards.any.PasswordPanel;
+import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
+import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+
+public class UserDetails extends Details<UserTO> {
+
+    private static final long serialVersionUID = 6592027822510220463L;
+
+    private final FieldPanel<String> realm;
+
+    protected final AjaxTextFieldPanel username;
+
+    protected final UserTO userTO;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public UserDetails(
+            final String id,
+            final UserWrapper wrapper,
+            final boolean templateMode,
+            final boolean includeStatusPanel,
+            final PageReference pageRef) {
+
+        super(id, wrapper, templateMode, includeStatusPanel, pageRef);
+
+        userTO = wrapper.getInnerObject();
+        // ------------------------
+        // Username
+        // ------------------------
+        username = new AjaxTextFieldPanel("username", "username", new PropertyModel<>(userTO, "username"), false);
+
+        if (wrapper.getPreviousUserTO() != null && StringUtils.
+                compare(wrapper.getPreviousUserTO().getUsername(), wrapper.getInnerObject().getUsername()) != 0) {
+            username.showExternAction(new LabelInfo("externalAction", wrapper.getPreviousUserTO().getUsername()));
+        }
+
+        if (templateMode) {
+            username.enableJexlHelp();
+        } else {
+            username.addRequiredLabel();
+        }
+        add(username);
+        // ------------------------
+
+        // ------------------------
+        // Realm
+        // ------------------------
+        realm = new AjaxDropDownChoicePanel<>(
+                "destinationRealm", "destinationRealm", new PropertyModel<>(userTO, "realm"), false);
+
+        ((AjaxDropDownChoicePanel<String>) realm).setChoices(
+                RealmRestClient.list().stream().map(RealmTO::getFullPath).collect(Collectors.toList()));
+        add(realm);
+    }
+
+    protected static class EditUserPasswordPanel extends Panel {
+
+        private static final long serialVersionUID = -8198836979773590078L;
+
+        protected EditUserPasswordPanel(
+                final String id,
+                final UserWrapper wrapper,
+                final boolean templateMode) {
+
+            super(id);
+            setOutputMarkupId(true);
+            add(new Label("warning", new ResourceModel("password.change.warning")));
+            add(new PasswordPanel(
+                    "passwordPanel",
+                    wrapper,
+                    templateMode,
+                    wrapper.getInnerObject().getKey() == null,
+                    new PasswordStrengthBehavior(new PasswordStrengthConfig().
+                            withDebug(false).
+                            withShowVerdictsInsideProgressBar(true).
+                            withShowProgressBar(true))));
+        }
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/VirAttrs.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java
similarity index 82%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/VirAttrs.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java
index 86c55b2..65386d9 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/VirAttrs.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/any/VirAttrs.java
@@ -16,86 +16,77 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.wizards.any;
+package org.apache.syncope.client.enduser.panels.any;
 
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.layout.CustomizationOption;
 import org.apache.syncope.client.enduser.markup.html.form.MultiFieldPanel;
-import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
 import org.apache.syncope.client.ui.commons.markup.html.form.AbstractFieldPanel;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
+import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
+import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.EntityTOUtils;
 import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.to.GroupableRelatableTO;
 import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.VirSchemaTO;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.html.WebMarkupContainer;
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 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;
 import org.apache.wicket.model.util.ListModel;
+import java.util.stream.Collectors;
 
 public class VirAttrs extends AbstractAttrs<VirSchemaTO> {
 
     private static final long serialVersionUID = -7982691107029848579L;
 
     public <T extends AnyTO> VirAttrs(
+            final String id,
             final AnyWrapper<T> modelObject,
             final List<String> anyTypeClasses,
             final Map<String, CustomizationOption> whichVirAttrs) {
 
-        super(modelObject, anyTypeClasses, whichVirAttrs);
+        super(id, modelObject, anyTypeClasses, whichVirAttrs);
 
-        setTitleModel(new ResourceModel("attributes.virtual"));
-
-        add(new Accordion("virSchemas", List.of(new AbstractTab(
-                new ResourceModel("attributes.accordion", "Virtual Attributes")) {
-
-            private static final long serialVersionUID = 1037272333056449378L;
-
-            @Override
-            public WebMarkupContainer getPanel(final String panelId) {
-                return new VirAttrs.VirSchemas(panelId, schemas, attrs);
-            }
-        }), Model.of(0)).setOutputMarkupId(true));
-
+        add(new VirAttrs.VirSchemas("virSchemas", schemas, attrs).setOutputMarkupId(true));
         add(new ListView<MembershipTO>("membershipsVirSchemas", membershipTOs) {
 
             private static final long serialVersionUID = 9101744072914090143L;
 
             @Override
             protected void populateItem(final ListItem<MembershipTO> item) {
-                MembershipTO membTO = item.getModelObject();
-                item.add(new Accordion("membershipVirSchemas", List.of(new AbstractTab(
-                        new StringResourceModel("attributes.membership.accordion", VirAttrs.this, Model.of(membTO))) {
+                final MembershipTO membershipTO = item.getModelObject();
+                item.add(new Accordion("membershipVirSchemas",
+                        Collections.<ITab>singletonList(new AbstractTab(new StringResourceModel(
+                                "attributes.membership.accordion", VirAttrs.this, Model.of(membershipTO))) {
 
-                    private static final long serialVersionUID = 1037272333056449378L;
+                            private static final long serialVersionUID = 1037272333056449378L;
 
-                    @Override
-                    public WebMarkupContainer getPanel(final String panelId) {
-                        return new VirAttrs.VirSchemas(
-                                panelId,
-                                membTO.getGroupName(),
-                                membershipSchemas.get(membTO.getGroupKey()),
-                                new ListModel<>(getAttrsFromTO(membTO)));
-                    }
-                }), Model.of(-1)).setOutputMarkupId(true));
+                            @Override
+                            public WebMarkupContainer getPanel(final String panelId) {
+                                return new VirAttrs.VirSchemas(
+                                        panelId,
+                                        membershipTO.getGroupName(),
+                                        membershipSchemas.get(membershipTO.getGroupKey()),
+                                        new ListModel<>(getAttrsFromTO(membershipTO)));
+                            }
+                        }), Model.of(-1)).setOutputMarkupId(true));
             }
-        });
+        }).setOutputMarkupId(true);
     }
 
     @Override
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.java
similarity index 83%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.java
index 4b886bf..007e26b 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.wizards.any;
+package org.apache.syncope.client.enduser.panels.captcha;
 
 import java.security.SecureRandom;
 import org.apache.commons.lang3.StringUtils;
@@ -31,7 +31,7 @@
 
 public class CaptchaPanel<T> extends Panel {
 
-    private static final long serialVersionUID = 1169850573252481471L;
+    private static final long serialVersionUID = -450657681453274465L;
 
     private static final SecureRandom RANDOM = new SecureRandom();
 
@@ -40,8 +40,6 @@
             withinRange('a', 'z').
             build();
 
-    private String randomText;
-
     private final Model<String> captchaText = new Model<>();
 
     private final CaptchaImageResource captchaImageResource;
@@ -55,8 +53,7 @@
 
             @Override
             protected byte[] render() {
-                randomText = RANDOM_LETTERS.generate(6);
-                getChallengeIdModel().setObject(randomText);
+                getChallengeIdModel().setObject(RANDOM_LETTERS.generate(6));
                 return super.render();
             }
         };
@@ -82,13 +79,13 @@
                 setOutputMarkupPlaceholderTag(true));
     }
 
-    public void reload() {
-        this.captchaImageResource.invalidate();
-    }
-
-    public boolean captchaCheck() {
-        return StringUtils.isBlank(captchaText.getObject()) || StringUtils.isBlank(randomText)
+    public boolean check() {
+        boolean check = StringUtils.isBlank(captchaText.getObject())
+                || StringUtils.isBlank(captchaImageResource.getChallengeId())
                 ? false
-                : captchaText.getObject().equals(randomText);
+                : captchaText.getObject().equals(captchaImageResource.getChallengeId());
+
+        captchaImageResource.invalidate();
+        return check;
     }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java
deleted file mode 100644
index 7cbb5f5..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/resources/CaptchaResource.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.resources;
-
-import java.security.SecureRandom;
-import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.text.RandomStringGenerator;
-import org.apache.syncope.client.enduser.SyncopeEnduserConstants;
-import org.apache.wicket.extensions.markup.html.captcha.CaptchaImageResource;
-import org.apache.wicket.request.cycle.RequestCycle;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-public class CaptchaResource extends CaptchaImageResource {
-
-    private static final long serialVersionUID = 8293404296348102926L;
-
-    private static final SecureRandom RANDOM = new SecureRandom();
-
-    private static final Logger LOG = LoggerFactory.getLogger(CaptchaResource.class);
-
-    private static final RandomStringGenerator RANDOM_LETTERS = new RandomStringGenerator.Builder().
-            usingRandom(RANDOM::nextInt).
-            withinRange('a', 'z').
-            build();
-
-    @Override
-    protected byte[] render() {
-        LOG.debug("Generate captcha");
-
-        String captcha = RANDOM_LETTERS.generate(6);
-        HttpServletRequest request = ((HttpServletRequest) RequestCycle.get().getRequest().getContainerRequest());
-        // store the captcha in the current session
-        request.getSession().setAttribute(SyncopeEnduserConstants.CAPTCHA_SESSION_KEY, captcha);
-
-        getChallengeIdModel().setObject(captcha);
-        return super.render();
-    }
-
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java
index 8f3c823..09c6ea9 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/AnyTypeRestClient.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.client.enduser.rest;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import org.apache.commons.lang3.ObjectUtils;
@@ -47,7 +48,7 @@
     }
 
     public static List<AnyTypeTO> listAnyTypes() {
-        List<AnyTypeTO> types = List.of();
+        List<AnyTypeTO> types = Collections.emptyList();
 
         try {
             types = getService(AnyTypeService.class).list();
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java
index 6f9e4ff..97196c0 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java
@@ -18,10 +18,17 @@
  */
 package org.apache.syncope.client.enduser.rest;
 
+import java.net.URI;
+import javax.ws.rs.core.HttpHeaders;
+import org.apache.cxf.jaxrs.client.WebClient;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.rest.RestClient;
 import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
+import org.apache.syncope.common.lib.types.ExecStatus;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.slf4j.Logger;
@@ -49,7 +56,7 @@
         SyncopeEnduserSession.get().resetClient(serviceClass);
     }
 
-    protected static String toOrderBy(final SortParam<String> sort) {
+    public static String toOrderBy(final SortParam<String> sort) {
         OrderByClauseBuilder builder = SyncopeClient.getOrderByClauseBuilder();
 
         String property = sort.getProperty();
@@ -65,4 +72,22 @@
 
         return builder.build();
     }
+
+    protected static <E extends JAXRSService, T> T getObject(
+            final E service, final URI location, final Class<T> resultClass) {
+
+        WebClient webClient = WebClient.fromClient(WebClient.client(service));
+        webClient.accept(SyncopeEnduserSession.get().getMediaType()).to(location.toASCIIString(), false);
+        return webClient.
+                header(RESTHeaders.DOMAIN, SyncopeEnduserSession.get().getDomain()).
+                header(HttpHeaders.AUTHORIZATION, "Bearer " + SyncopeEnduserSession.get().getJWT()).
+                get(resultClass);
+    }
+
+    protected static String getStatus(final int httpStatus) {
+        ExecStatus execStatus = ExecStatus.fromHttpStatus(httpStatus);
+        return execStatus == null
+                ? Constants.UNKNOWN
+                : execStatus.name();
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
index f9e22c3..23c98fd 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/GroupRestClient.java
@@ -18,9 +18,8 @@
  */
 package org.apache.syncope.client.enduser.rest;
 
-import java.util.List;
-import javax.ws.rs.core.GenericType;
-import javax.ws.rs.core.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.request.GroupCR;
 import org.apache.syncope.common.lib.request.GroupUR;
 import org.apache.syncope.common.lib.to.GroupTO;
@@ -29,6 +28,9 @@
 import org.apache.syncope.common.rest.api.service.AnyService;
 import org.apache.syncope.common.rest.api.service.GroupService;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+import java.util.List;
 
 /**
  * Console client for invoking Rest Group's services.
@@ -42,16 +44,16 @@
         return GroupService.class;
     }
 
-    public static ProvisioningResult<GroupTO> create(final GroupCR groupCR) {
-        Response response = getService(GroupService.class).create(groupCR);
+    public static ProvisioningResult<GroupTO> create(final GroupCR groupTO) {
+        Response response = getService(GroupService.class).create(groupTO);
         return response.readEntity(new GenericType<ProvisioningResult<GroupTO>>() {
         });
     }
 
-    public ProvisioningResult<GroupTO> update(final String etag, final GroupUR updateReq) {
+    public ProvisioningResult<GroupTO> update(final String etag, final GroupUR groupPatch) {
         ProvisioningResult<GroupTO> result;
         synchronized (this) {
-            result = getService(etag, GroupService.class).update(updateReq).
+            result = getService(etag, GroupService.class).update(groupPatch).
                     readEntity(new GenericType<ProvisioningResult<GroupTO>>() {
                     });
             resetClient(getAnyServiceClass());
@@ -65,7 +67,8 @@
         final int page,
         final int size) {
 
-        return getService(SyncopeService.class).searchAssignableGroups(realm, term, page, size).getResult();
+        return getService(SyncopeService.class).searchAssignableGroups(
+                StringUtils.isNotEmpty(realm) ? realm : SyncopeConstants.ROOT_REALM, term, page, size).getResult();
     }
 
     @Override
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java
deleted file mode 100644
index 6b3aea3..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/RoleRestClient.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.rest;
-
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.lib.to.RoleTO;
-import org.apache.syncope.common.rest.api.service.RoleService;
-
-/**
- * Console client for invoking Rest Role's services.
- */
-public class RoleRestClient extends BaseRestClient {
-
-    private static final long serialVersionUID = -3161863874876938094L;
-
-    public static void delete(final String key) {
-        getService(RoleService.class).delete(key);
-    }
-
-    public static RoleTO read(final String key) {
-        return getService(RoleService.class).read(key);
-    }
-
-    public static void update(final RoleTO roleTO) {
-        getService(RoleService.class).update(roleTO);
-    }
-
-    public static void create(final RoleTO roleTO) {
-        getService(RoleService.class).create(roleTO);
-    }
-
-    public static List<RoleTO> list() {
-        return getService(RoleService.class).list();
-    }
-
-    public static String readAnyLayout(final String roleKey) {
-        try {
-            return IOUtils.toString(InputStream.class.cast(
-                    getService(RoleService.class).getAnyLayout(roleKey).getEntity()),
-                    StandardCharsets.UTF_8);
-        } catch (Exception e) {
-            LOG.error("Error retrieving console layout info for role {}", roleKey, e);
-            return StringUtils.EMPTY;
-        }
-    }
-
-    public static void setAnyLayout(final String roleKey, final String content) {
-        getService(RoleService.class).setAnyLayout(
-                roleKey, IOUtils.toInputStream(content, StandardCharsets.UTF_8));
-    }
-
-    public static void removeAnyLayout(final String roleKey) {
-        getService(RoleService.class).removeAnyLayout(roleKey);
-    }
-
-    public static List<String> getAllAvailableEntitlements() {
-        return getSyncopeService().platform().getEntitlements().stream().sorted().collect(Collectors.toList());
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
index c89e35b..666b273 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SchemaRestClient.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.client.enduser.rest;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
@@ -86,7 +87,7 @@
     }
 
     public static List<String> getSchemaNames(final SchemaType schemaType) {
-        List<String> schemaNames = List.of();
+        List<String> schemaNames = Collections.emptyList();
 
         try {
             schemaNames = getSchemas(schemaType, null, new String[0]).stream().
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java
index 74429c8..233dd51 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/SyncopeRestClient.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.client.enduser.rest;
 
+import java.util.Collections;
 import java.util.List;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.TypeExtensionTO;
@@ -28,7 +29,7 @@
     private static final long serialVersionUID = -2211371717449597247L;
 
     public static List<String> listAnyTypeClasses() {
-        List<String> types = List.of();
+        List<String> types = Collections.emptyList();
 
         try {
             types = getService(SyncopeService.class).platform().getAnyTypeClasses();
@@ -39,13 +40,14 @@
     }
 
     public static List<String> searchUserTypeExtensions(final String groupName) {
-        List<String> types = List.of();
+        List<String> types = Collections.emptyList();
         try {
             TypeExtensionTO typeExtensionTO = getService(SyncopeService.class).readUserTypeExtension(groupName);
             types = typeExtensionTO == null ? types : typeExtensionTO.getAuxClasses();
-        } catch (SyncopeClientException e) {
+        } catch (Exception e) {
             LOG.error("While reading all any type classes for group [{}]", groupName, e);
         }
         return types;
     }
+
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
index 95a35ce..f7cf9cf 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserSelfRestClient.java
@@ -18,46 +18,17 @@
  */
 package org.apache.syncope.client.enduser.rest;
 
-import javax.ws.rs.core.GenericType;
-import javax.ws.rs.core.Response;
-import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
 import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
+import javax.ws.rs.core.GenericType;
 
-/**
- * Console client for invoking rest users services.
- */
 public class UserSelfRestClient extends BaseRestClient {
 
     private static final long serialVersionUID = -1575748964398293968L;
 
-    public static ProvisioningResult<UserTO> create(final UserCR createReq) {
-        Response response = getService(UserSelfService.class).create(createReq);
-        return response.readEntity(new GenericType<ProvisioningResult<UserTO>>() {
-        });
-    }
-
-    public ProvisioningResult<UserTO> update(final String etag, final UserUR updateReq) {
-        ProvisioningResult<UserTO> result;
-        synchronized (this) {
-            result = getService(etag, UserSelfService.class).update(updateReq).
-                    readEntity(new GenericType<ProvisioningResult<UserTO>>() {
-                    });
-            resetClient(UserSelfService.class);
-        }
-        return result;
-    }
-
-    public ProvisioningResult<UserTO> mustChangePassword(final String etag, final boolean value, final String key) {
-        UserUR userUR = new UserUR();
-        userUR.setKey(key);
-        userUR.setMustChangePassword(new BooleanReplacePatchItem.Builder().value(value).build());
-        return update(etag, userUR);
-    }
-
     public static void changePassword(final String password) {
         getService(UserSelfService.class).mustChangePassword(password);
     }
@@ -65,4 +36,23 @@
     public static void requestPasswordReset(final String username, final String securityAnswer) {
         getService(UserSelfService.class).requestPasswordReset(username, securityAnswer);
     }
+
+    public ProvisioningResult<UserTO> create(final UserCR createReq, final boolean storePassword) {
+        ProvisioningResult<UserTO> result;
+        result = getService(UserSelfService.class).create(createReq).readEntity(
+                new GenericType<ProvisioningResult<UserTO>>() {
+        });
+        return result;
+    }
+
+    public ProvisioningResult<UserTO> update(final String etag, final UserUR userPatch) {
+        ProvisioningResult<UserTO> result;
+        synchronized (this) {
+            result = getService(etag, UserSelfService.class).update(userPatch).
+                    readEntity(new GenericType<ProvisioningResult<UserTO>>() {
+                    });
+            resetClient(UserSelfService.class);
+        }
+        return result;
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wicket/markup/head/MetaHeaderItem.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wicket/markup/head/MetaHeaderItem.java
new file mode 100644
index 0000000..90ccf60
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wicket/markup/head/MetaHeaderItem.java
@@ -0,0 +1,49 @@
+/*
+ * 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.wicket.markup.head;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import org.apache.wicket.markup.head.HeaderItem;
+import org.apache.wicket.request.Response;
+
+public class MetaHeaderItem extends HeaderItem implements Serializable {
+
+    private static final long serialVersionUID = 7578609827530302053L;
+
+    private final String key;
+
+    private final String value;
+
+    public MetaHeaderItem(final String key, final String value) {
+        this.key = key;
+        this.value = value;
+    }
+
+    @Override
+    public Iterable<?> getRenderTokens() {
+        return Arrays.asList("meta-" + key + "-" + value);
+    }
+
+    @Override
+    public void render(final Response response) {
+        response.write("<meta http-equiv=\"" + key + "\" content=\"" + value + "\"/>");
+        response.write("\n");
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/widgets/BaseWidget.java
similarity index 71%
rename from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java
rename to client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/widgets/BaseWidget.java
index 7c86c2d..8426ae5 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/widgets/BaseWidget.java
@@ -16,16 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.pages;
+package org.apache.syncope.client.enduser.widgets;
 
-import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.markup.html.panel.Panel;
 
-public class SelfUpdate extends BaseEnduserWebPage {
+public abstract class BaseWidget extends Panel {
 
-    private static final long serialVersionUID = 164651008547631054L;
+    private static final long serialVersionUID = -4186604985011430091L;
 
-    public SelfUpdate(final PageParameters parameters) {
-        super(parameters);
+    public BaseWidget(final String id) {
+        super(id);
     }
 
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/widgets/UserProfileWidget.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/widgets/UserProfileWidget.java
new file mode 100644
index 0000000..9f52674
--- /dev/null
+++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/widgets/UserProfileWidget.java
@@ -0,0 +1,65 @@
+/*
+ * 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.widgets;
+
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+
+public class UserProfileWidget extends BaseWidget {
+
+    private static final long serialVersionUID = 4437711189800676363L;
+
+    protected UserTO userTO;
+
+    public UserProfileWidget(final String id) {
+        super(id);
+
+        userTO = SyncopeEnduserSession.get().getSelfTO(true);
+
+        WebMarkupContainer userProfile = new WebMarkupContainer("userProfile");
+        userProfile.setOutputMarkupId(true);
+        add(userProfile);
+
+        Label welcome = new Label("welcome", userTO.getUsername());
+        welcome.setOutputMarkupId(true);
+        userProfile.add(welcome);
+
+        addBaseFields(userProfile);
+        addExtFields(userProfile);
+    }
+
+    protected void addBaseFields(final WebMarkupContainer userProfile) {
+        Label username = new Label("username", userTO.getUsername());
+        username.setOutputMarkupId(true);
+        userProfile.add(username);
+
+        Label changePwdDate = new Label("changePwdDate", userTO.getChangePwdDate());
+        changePwdDate.setOutputMarkupId(true);
+        userProfile.add(changePwdDate);
+
+        Label lastLoginDate = new Label("lastLoginDate", userTO.getLastLoginDate());
+        lastLoginDate.setOutputMarkupId(true);
+        userProfile.add(lastLoginDate);
+    }
+
+    protected void addExtFields(final WebMarkupContainer userProfile) {
+    }
+}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java
deleted file mode 100644
index 922554e..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AnyWizardBuilder.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * 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.wizards.any;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Future;
-import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.client.enduser.SyncopeWebApplication;
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
-import org.apache.syncope.client.ui.commons.wizards.AjaxWizardMgtButtonBar;
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractAnyWizardBuilder;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.wicket.Component;
-import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.extensions.wizard.FinishButton;
-import org.apache.wicket.extensions.wizard.IWizard;
-import org.apache.wicket.extensions.wizard.IWizardModel;
-import org.apache.wicket.extensions.wizard.IWizardStep;
-import org.apache.wicket.extensions.wizard.WizardModel;
-
-public abstract class AnyWizardBuilder extends AbstractAnyWizardBuilder<UserTO> {
-
-    private static final long serialVersionUID = -2480279868319546243L;
-
-    protected final List<String> anyTypeClasses;
-
-    protected UserFormLayoutInfo formLayoutInfo;
-
-    protected Captcha captcha;
-
-    /**
-     * Construct.
-     *
-     * @param anyTO any
-     * @param anyTypeClasses any type classes
-     * @param formLayoutInfo form layout info
-     * @param pageRef caller page reference.
-     */
-    public AnyWizardBuilder(
-            final UserTO anyTO,
-            final List<String> anyTypeClasses,
-            final UserFormLayoutInfo formLayoutInfo,
-            final PageReference pageRef) {
-
-        super(new AnyWrapper<>(anyTO), pageRef);
-        this.anyTypeClasses = anyTypeClasses;
-        this.formLayoutInfo = formLayoutInfo;
-    }
-
-    /**
-     * Construct.
-     *
-     * @param wrapper any wrapper
-     * @param anyTypeClasses any type classes
-     * @param formLayoutInfo form layout info
-     * @param pageRef caller page reference.
-     */
-    public AnyWizardBuilder(
-            final UserWrapper wrapper,
-            final List<String> anyTypeClasses,
-            final UserFormLayoutInfo formLayoutInfo,
-            final PageReference pageRef) {
-
-        super(wrapper, pageRef);
-        this.anyTypeClasses = anyTypeClasses;
-        this.formLayoutInfo = formLayoutInfo;
-    }
-
-    @Override
-    protected WizardModel buildModelSteps(final AnyWrapper<UserTO> modelObject, final WizardModel wizardModel) {
-        wizardModel.add(new UserDetails(
-                UserWrapper.class.cast(modelObject),
-                mode == AjaxWizard.Mode.TEMPLATE,
-                UserFormLayoutInfo.class.cast(formLayoutInfo).isPasswordManagement()));
-
-        if (formLayoutInfo.isAuxClasses()) {
-            wizardModel.add(new EnduserAuxClasses(modelObject, anyTypeClasses));
-        }
-
-        if (formLayoutInfo.isGroups()) {
-            wizardModel.add(new Groups(modelObject));
-        }
-
-        // attributes panel steps
-        if (formLayoutInfo.isPlainAttrs()) {
-            wizardModel.add(new PlainAttrs(
-                    modelObject,
-                    mode,
-                    anyTypeClasses,
-                    formLayoutInfo.getWhichPlainAttrs()) {
-
-                private static final long serialVersionUID = 8167894751609598306L;
-
-                @Override
-                public PageReference getPageReference() {
-                    return pageRef;
-                }
-
-            });
-        }
-        if (formLayoutInfo.isDerAttrs()) {
-            wizardModel.add(new DerAttrs(modelObject, anyTypeClasses, formLayoutInfo.getWhichDerAttrs()));
-        }
-        if (formLayoutInfo.isVirAttrs()) {
-            wizardModel.add(new VirAttrs(modelObject, anyTypeClasses, formLayoutInfo.getWhichVirAttrs()));
-        }
-        if (formLayoutInfo.isResources()) {
-            wizardModel.add(new Resources(modelObject));
-        }
-        if (SyncopeWebApplication.get().isCaptchaEnabled()) {
-            // add captcha
-            captcha = new Captcha();
-            captcha.setOutputMarkupId(true);
-            wizardModel.add(captcha);
-        }
-
-        return wizardModel;
-    }
-
-    @Override
-    protected long getMaxWaitTimeInSeconds() {
-        return SyncopeWebApplication.get().getMaxWaitTimeInSeconds();
-    }
-
-    @Override
-    protected void sendError(final Exception exception) {
-        SyncopeEnduserSession.get().onException(exception);
-    }
-
-    @Override
-    protected void sendWarning(final String message) {
-        SyncopeEnduserSession.get().warn(message);
-    }
-
-    @Override
-    protected Future<Pair<Serializable, Serializable>> execute(
-            final Callable<Pair<Serializable, Serializable>> future) {
-        return SyncopeEnduserSession.get().execute(future);
-    }
-
-    @Override
-    public AjaxWizard<AnyWrapper<UserTO>> build(final String id, final AjaxWizard.Mode mode) {
-        this.mode = mode;
-
-        // get the specified item if available
-        final AnyWrapper<UserTO> modelObject = newModelObject();
-
-        return new AjaxWizard<AnyWrapper<UserTO>>(
-                id, modelObject, buildModelSteps(modelObject, new WizardModel()), mode, this.pageRef) {
-
-            private static final long serialVersionUID = 7770507663760640735L;
-
-            @Override
-            protected void onCancelInternal() {
-                AnyWizardBuilder.this.onCancelInternal(modelObject);
-            }
-
-            @Override
-            protected Pair<Serializable, Serializable> onApplyInternal(final AjaxRequestTarget target) {
-                Serializable res = AnyWizardBuilder.this.onApplyInternal(modelObject);
-
-                Serializable payload;
-                switch (mode) {
-                    case CREATE:
-                        payload = getCreateCustomPayloadEvent(res, target);
-                        break;
-                    case EDIT:
-                    case TEMPLATE:
-                        payload = getEditCustomPayloadEvent(res, target);
-                        break;
-                    default:
-                        payload = null;
-                }
-
-                return Pair.of(payload, res);
-            }
-
-            @Override
-            protected long getMaxWaitTimeInSeconds() {
-                return AnyWizardBuilder.this.getMaxWaitTimeInSeconds();
-            }
-
-            @Override
-            protected void sendError(final Exception exception) {
-                SyncopeEnduserSession.get().onException(exception);
-            }
-
-            @Override
-            protected void sendWarning(final String message) {
-                AnyWizardBuilder.this.sendWarning(message);
-            }
-
-            @Override
-            protected Future<Pair<Serializable, Serializable>> execute(
-                    final Callable<Pair<Serializable, Serializable>> future) {
-                return AnyWizardBuilder.this.execute(future);
-            }
-
-            @Override
-            protected Component newButtonBar(final String id) {
-                return new AjaxWizardMgtButtonBar<>(id, this, mode) {
-
-                    private static final long serialVersionUID = -3041152400413815333L;
-
-                    @Override
-                    protected FinishButton newFinishButton(final String id, final IWizard wizard) {
-                        return new FinishButton(id, wizard) {
-
-                            private static final long serialVersionUID = 864248301720764819L;
-
-                            @Override
-                            public boolean isEnabled() {
-                                switch (mode) {
-                                    case EDIT:
-                                    case TEMPLATE:
-                                        return true;
-                                    case READONLY:
-                                        return false;
-                                    default:
-                                        if (!completed) {
-                                            final IWizardStep activeStep = getWizardModel().getActiveStep();
-                                            completed = (activeStep != null)
-                                                    && getWizardModel().isLastStep(activeStep)
-                                                    && super.isEnabled();
-                                        }
-                                        return completed;
-                                }
-                            }
-
-                            @Override
-                            public boolean isVisible() {
-                                switch (mode) {
-                                    case READONLY:
-                                        return false;
-                                    default:
-                                        return true;
-                                }
-                            }
-
-                            @Override
-                            public void onClick() {
-                                IWizardModel wizardModel = getWizardModel();
-                                IWizardStep activeStep = wizardModel.getActiveStep();
-
-                                // let the step apply any state
-                                activeStep.applyState();
-
-                                // if the step completed after applying the state, notify the wizard
-                                if (activeStep.isComplete()
-                                        && SyncopeWebApplication.get().isCaptchaEnabled()
-                                        && !getWizardModel().isLastStep(activeStep)) {
-                                    // go to last step
-                                    getWizardModel().last();
-                                } else if (activeStep.isComplete()) {
-                                    getWizardModel().finish();
-                                } else {
-                                    error(getLocalizer().getString(
-                                            "org.apache.wicket.extensions.wizard.FinishButton.step.did.not.complete",
-                                            this));
-                                }
-                            }
-                        };
-                    }
-
-                };
-            }
-
-        }.setEventSink(eventSink).addOuterObject(outerObjects);
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Captcha.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Captcha.java
deleted file mode 100644
index c46755c..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Captcha.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.wizards.any;
-
-import org.apache.syncope.client.enduser.SyncopeWebApplication;
-import org.apache.wicket.extensions.wizard.WizardModel;
-import org.apache.wicket.extensions.wizard.WizardStep;
-
-public class Captcha extends WizardStep implements WizardModel.ICondition {
-
-    private static final long serialVersionUID = 702900610508752856L;
-
-    private final CaptchaPanel<Void> captchaPanel;
-
-    public Captcha() {
-        captchaPanel = new CaptchaPanel<>("captchaPanel");
-        captchaPanel.setOutputMarkupId(true);
-        add(captchaPanel);
-    }
-
-    public boolean captchaCheck() {
-        return captchaPanel.captchaCheck();
-    }
-
-    public void reload() {
-        captchaPanel.reload();
-    }
-
-    @Override
-    public boolean evaluate() {
-        return SyncopeWebApplication.get().isCaptchaEnabled();
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.java
deleted file mode 100644
index bdd6f44..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.wizards.any;
-
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractAuxClasses;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.syncope.client.enduser.rest.SyncopeRestClient;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.lib.to.AnyTypeClassTO;
-
-public class EnduserAuxClasses extends AbstractAuxClasses {
-
-    private static final long serialVersionUID = 552437609667518888L;
-
-    public <T extends AnyTO> EnduserAuxClasses(final AnyWrapper<T> modelObject, final List<String> anyTypeClasses) {
-        super(modelObject, anyTypeClasses);
-    }
-
-    @Override
-    protected final List<AnyTypeClassTO> listAnyTypecClasses() {
-        return SyncopeRestClient.listAnyTypeClasses().stream().map(name -> {
-            AnyTypeClassTO anyTypeClassTO = new AnyTypeClassTO();
-            anyTypeClassTO.setKey(name);
-            return anyTypeClassTO;
-        }).collect(Collectors.toList());
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Resources.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Resources.java
deleted file mode 100644
index c4e7857..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/Resources.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.wizards.any;
-
-import org.apache.syncope.client.enduser.SyncopeEnduserSession;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.client.ui.commons.wizards.any.AbstractResources;
-import org.apache.syncope.common.lib.to.AnyTO;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
-
-public class Resources extends AbstractResources {
-
-    private static final long serialVersionUID = 702900610508752856L;
-
-    public <T extends AnyTO> Resources(final AnyWrapper<T> modelObject) {
-        super(modelObject);
-    }
-
-    @Override
-    public boolean evaluate() {
-        available.setObject(SyncopeEnduserSession.get().getService(SyncopeService.class).platform().getResources());
-        return !available.getObject().isEmpty();
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/UserDetails.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/UserDetails.java
deleted file mode 100644
index 8f07432..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/UserDetails.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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.wizards.any;
-
-import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
-import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthConfig;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.enduser.rest.RealmRestClient;
-import org.apache.syncope.client.enduser.rest.SecurityQuestionRestClient;
-import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.ajax.markup.html.LabelInfo;
-import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
-import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
-import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
-import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
-import org.apache.syncope.client.ui.commons.wizards.any.PasswordPanel;
-import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.common.lib.to.RealmTO;
-import org.apache.syncope.common.lib.to.SecurityQuestionTO;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.wicket.Component;
-import org.apache.wicket.ajax.AjaxEventBehavior;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.markup.html.AjaxLink;
-import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
-import org.apache.wicket.extensions.markup.html.tabs.ITab;
-import org.apache.wicket.extensions.wizard.WizardStep;
-import org.apache.wicket.markup.ComponentTag;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.IChoiceRenderer;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.model.PropertyModel;
-import org.apache.wicket.model.ResourceModel;
-
-public class UserDetails extends WizardStep {
-
-    private static final long serialVersionUID = 6592027822510220463L;
-
-    private static final String PASSWORD_CONTENT_PATH = "body:content";
-
-    private final FieldPanel<String> realm;
-
-    protected final AjaxTextFieldPanel username;
-
-    private final FieldPanel<String> securityQuestion;
-
-    private final FieldPanel<String> securityAnswer;
-
-    protected final UserTO userTO;
-
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public UserDetails(
-            final UserWrapper wrapper,
-            final boolean templateMode,
-            final boolean showPasswordManagement) {
-
-        super();
-
-        userTO = wrapper.getInnerObject();
-        // ------------------------
-        // Username
-        // ------------------------
-        username = new AjaxTextFieldPanel(Constants.USERNAME_FIELD_NAME, Constants.USERNAME_FIELD_NAME,
-                new PropertyModel<>(userTO, Constants.USERNAME_FIELD_NAME), false);
-
-        if (wrapper.getPreviousUserTO() != null && StringUtils.
-                compare(wrapper.getPreviousUserTO().getUsername(), wrapper.getInnerObject().getUsername()) != 0) {
-            username.showExternAction(new LabelInfo("externalAction", wrapper.getPreviousUserTO().getUsername()));
-        }
-
-        if (templateMode) {
-            username.enableJexlHelp();
-        } else {
-            username.addRequiredLabel();
-        }
-        add(username);
-        // ------------------------
-
-        // ------------------------
-        // Realm
-        // ------------------------
-        realm = new AjaxDropDownChoicePanel<>(
-                "destinationRealm", "destinationRealm", new PropertyModel<>(userTO, "realm"), false);
-
-        ((AjaxDropDownChoicePanel<String>) realm).setChoices(
-                RealmRestClient.list().stream().map(RealmTO::getFullPath).collect(Collectors.toList()));
-        add(realm);
-
-        // ------------------------
-        // Password
-        // ------------------------
-        Model<Integer> model = Model.of(-1);
-
-        Accordion accordion = new Accordion("accordionPanel", List.of(
-                new AbstractTab(new ResourceModel("password.change", "Change password")) {
-
-            private static final long serialVersionUID = 1037272333056449378L;
-
-            @Override
-            public Panel getPanel(final String panelId) {
-                EditUserPasswordPanel panel = new EditUserPasswordPanel(panelId, wrapper, templateMode);
-                panel.setEnabled(model.getObject() >= 0);
-                return panel;
-            }
-        }), model) {
-
-            private static final long serialVersionUID = -2898628183677758699L;
-
-            @Override
-            protected Component newTitle(final String markupId, final ITab tab, final Accordion.State state) {
-                return new AjaxLink<Integer>(markupId) {
-
-                    private static final long serialVersionUID = 7021195294339489084L;
-
-                    @Override
-                    protected void onComponentTag(final ComponentTag tag) {
-                        super.onComponentTag(tag);
-                        tag.put("style", "color: #337ab7");
-                    }
-
-                    @Override
-                    public void onClick(final AjaxRequestTarget target) {
-                        model.setObject(model.getObject() == 0 ? -1 : 0);
-                        Component passwordPanel = getParent().get(PASSWORD_CONTENT_PATH);
-                        passwordPanel.setEnabled(model.getObject() >= 0);
-                        target.add(passwordPanel);
-                    }
-                }.setBody(new ResourceModel("password.change", "Change password ..."));
-            }
-        };
-
-        accordion.setOutputMarkupId(true);
-        accordion.setVisible(showPasswordManagement);
-        add(accordion);
-        // ------------------------
-
-        // ------------------------
-        // Security Question
-        // ------------------------
-        securityQuestion = new AjaxDropDownChoicePanel("securityQuestion", "securityQuestion", new PropertyModel<>(
-                userTO, "securityQuestion"));
-        ((AjaxDropDownChoicePanel) securityQuestion).setNullValid(true);
-
-        final List<SecurityQuestionTO> securityQuestions = SecurityQuestionRestClient.list();
-        ((AjaxDropDownChoicePanel<String>) securityQuestion).setChoices(securityQuestions.stream().map(
-                SecurityQuestionTO::getKey).collect(Collectors.toList()));
-        ((AjaxDropDownChoicePanel<String>) securityQuestion).setChoiceRenderer(
-                new IChoiceRenderer<String>() {
-
-            private static final long serialVersionUID = -4421146737845000747L;
-
-            @Override
-            public Object getDisplayValue(final String value) {
-                return securityQuestions.stream().filter(sq -> value.equals(sq.getKey()))
-                        .map(SecurityQuestionTO::getContent).findFirst().orElse(null);
-            }
-
-            @Override
-            public String getIdValue(final String value, final int index) {
-                return value;
-            }
-
-            @Override
-            public String getObject(
-                    final String id,
-                    final IModel<? extends List<? extends String>> choices) {
-                return id;
-            }
-        });
-
-        securityQuestion.add(new AjaxEventBehavior(Constants.ON_CHANGE) {
-
-            private static final long serialVersionUID = 192359260308762078L;
-
-            @Override
-            protected void onEvent(final AjaxRequestTarget target) {
-                securityAnswer.setEnabled(StringUtils.isNotBlank(securityQuestion.getModelObject()));
-                target.add(securityAnswer);
-            }
-        });
-
-        add(securityQuestion);
-        // ------------------------
-
-        // ------------------------
-        // Security Answer
-        // ------------------------
-        securityAnswer =
-                new AjaxTextFieldPanel("securityAnswer", "securityAnswer",
-                        new PropertyModel<>(userTO, "securityAnswer"), false);
-        add(securityAnswer.setOutputMarkupId(true).setOutputMarkupPlaceholderTag(true).setEnabled(StringUtils.
-                isNotBlank(securityQuestion.getModelObject())));
-        // ------------------------
-    }
-
-    public static class EditUserPasswordPanel extends Panel {
-
-        private static final long serialVersionUID = -8198836979773590078L;
-
-        public EditUserPasswordPanel(
-                final String id,
-                final UserWrapper wrapper,
-                final boolean templateMode) {
-            super(id);
-            setOutputMarkupId(true);
-            add(new Label("warning", new ResourceModel("password.change.warning")));
-            add(new PasswordPanel("passwordPanel", wrapper, templateMode, new PasswordStrengthBehavior(
-                    new PasswordStrengthConfig()
-                            .withDebug(true)
-                            .withShowVerdictsInsideProgressBar(true)
-                            .withShowProgressBar(true))));
-        }
-
-    }
-}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/UserWizardBuilder.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/UserWizardBuilder.java
deleted file mode 100644
index 714cacb..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/UserWizardBuilder.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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.wizards.any;
-
-import java.io.Serializable;
-import java.util.List;
-import java.util.Optional;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.ui.commons.layout.UserForm;
-import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo;
-import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
-import org.apache.syncope.client.ui.commons.wizards.any.AnyWrapper;
-import org.apache.syncope.client.ui.commons.wizards.any.UserWrapper;
-import org.apache.syncope.client.ui.commons.wizards.exception.CaptchaNotMatchingException;
-import org.apache.syncope.common.lib.AnyOperations;
-import org.apache.syncope.common.lib.EntityTOUtils;
-import org.apache.syncope.common.lib.request.PasswordPatch;
-import org.apache.syncope.common.lib.request.UserCR;
-import org.apache.syncope.common.lib.request.UserUR;
-import org.apache.syncope.common.lib.to.ProvisioningResult;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.wicket.PageReference;
-
-public class UserWizardBuilder extends AnyWizardBuilder implements UserForm {
-
-    private static final long serialVersionUID = 6716803168859873877L;
-
-    protected final UserSelfRestClient userSelfRestClient = new UserSelfRestClient();
-
-    /**
-     * Constructor to be used for templating only.
-     *
-     * @param anyTypeClasses any type classes.
-     * @param formLayoutInfo form layout.
-     * @param pageRef reference page.
-     */
-    public UserWizardBuilder(
-            final List<String> anyTypeClasses,
-            final UserFormLayoutInfo formLayoutInfo,
-            final PageReference pageRef) {
-
-        super(new UserWrapper(null), anyTypeClasses, formLayoutInfo, pageRef);
-    }
-
-    /**
-     * Constructor to be used for Approval and Remediation details only.
-     *
-     * @param previousUserTO previous user status.
-     * @param userTO new user status to be approved.
-     * @param anyTypeClasses any type classes.
-     * @param formLayoutInfo from layout.
-     * @param pageRef reference page.
-     */
-    public UserWizardBuilder(
-            final UserTO previousUserTO,
-            final UserTO userTO,
-            final List<String> anyTypeClasses,
-            final UserFormLayoutInfo formLayoutInfo,
-            final PageReference pageRef) {
-
-        super(new UserWrapper(previousUserTO, userTO), anyTypeClasses, formLayoutInfo, pageRef);
-    }
-
-    @Override
-    protected Serializable onApplyInternal(final AnyWrapper<UserTO> modelObject) {
-        // captcha check
-        if (captcha != null && captcha.evaluate() && !captcha.captchaCheck()) {
-            throw new CaptchaNotMatchingException();
-        }
-        UserTO inner = modelObject.getInnerObject();
-
-        ProvisioningResult<UserTO> result;
-        if (inner.getKey() == null) {
-            UserCR req = new UserCR();
-            EntityTOUtils.toAnyCR(inner, req);
-            req.setStorePassword(modelObject instanceof UserWrapper
-                    ? UserWrapper.class.cast(modelObject).isStorePasswordInSyncope()
-                    : StringUtils.isNotBlank(inner.getPassword()));
-
-            result = UserSelfRestClient.create(req);
-        } else {
-            fixPlainAndVirAttrs(inner, getOriginalItem().getInnerObject());
-            UserUR userUR = AnyOperations.diff(inner, getOriginalItem().getInnerObject(), false);
-
-            if (StringUtils.isNotBlank(inner.getPassword())) {
-                PasswordPatch passwordPatch = new PasswordPatch.Builder().
-                        value(inner.getPassword()).onSyncope(true).resources(inner.getResources()).build();
-                userUR.setPassword(passwordPatch);
-            }
-
-            // update just if it is changed
-            if (userUR.isEmpty()) {
-                result = new ProvisioningResult<>();
-                result.setEntity(inner);
-            } else {
-                result = userSelfRestClient.update(getOriginalItem().getInnerObject().getETagValue(), userUR);
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * Overrides default setItem() in order to clean statusModel as well.
-     *
-     * @param item item to be set.
-     * @return the current wizard.
-     */
-    @Override
-    public UserWizardBuilder setItem(final AnyWrapper<UserTO> item) {
-        super.setItem(Optional.ofNullable(item)
-            .map(userTOAnyWrapper -> new UserWrapper(userTOAnyWrapper.getInnerObject())).orElse(null));
-        return this;
-    }
-
-}
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/AdminLTE_plugins/dataTables.bootstrap4.min.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/AdminLTE_plugins/dataTables.bootstrap4.min.css
new file mode 100644
index 0000000..f1930be
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/AdminLTE_plugins/dataTables.bootstrap4.min.css
@@ -0,0 +1 @@
+table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important;border-spacing:0}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:auto;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:0.85em;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap;justify-content:flex-end}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:before,table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:0.9em;display:block;opacity:0.3}table.dataTable thead .sorting:before,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:before{right:1em;content:"\2191"}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{right:0.5em;content:"\2193"}table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:after{opacity:1}table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{opacity:0}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:before,div.dataTables_scrollBody table thead .sorting_asc:before,div.dataTables_scrollBody table thead .sorting_desc:before,div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-sm>thead>tr>th{padding-right:20px}table.dataTable.table-sm .sorting:before,table.dataTable.table-sm .sorting_asc:before,table.dataTable.table-sm .sorting_desc:before{top:5px;right:0.85em}table.dataTable.table-sm .sorting:after,table.dataTable.table-sm .sorting_asc:after,table.dataTable.table-sm .sorting_desc:after{top:5px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0}
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility.css
similarity index 70%
copy from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java
copy to client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility.css
index 7c86c2d..21b9201 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility.css
@@ -16,16 +16,29 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.pages;
 
-import org.apache.wicket.request.mapper.parameter.PageParameters;
+.btn-accessibility {
+  position: absolute;
+  right: 0;
+  font-size: 12px;
+  padding: 15px 15px 0 0;
+}
 
-public class SelfUpdate extends BaseEnduserWebPage {
+.btn-accessibility i {
+  font-size: 30px;
+}
 
-    private static final long serialVersionUID = 164651008547631054L;
+.btn-accessibility:hover {
+  cursor:pointer;
+}
 
-    public SelfUpdate(final PageParameters parameters) {
-        super(parameters);
-    }
+.control-sidebar-menu a:focus {
+  outline: 1px #949494 solid;
+}
 
+#change_contrast {
+  top: 0;
+}
+#change_fontSize {
+  top: 40px;
 }
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility/accessibilityFont.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility/accessibilityFont.css
new file mode 100644
index 0000000..da26456
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility/accessibilityFont.css
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+body,
+p {
+  font-size: 200%;
+}
+
+.menu a,
+.treeview-menu a,
+.control-sidebar-subheading {
+  font-size: 80% !important;
+}
+
+.box-header > .fa, 
+.box-header > .glyphicon, 
+.box-header > .ion, .box-header .box-title,
+.dropdown-toggle .filter-option,
+.dropdown-menu a,
+.menu-info span,
+.user-header,
+.header,
+.modal-title,
+.small-box p,
+.alert-widget,
+.alert-widget > a > .label,
+.alert h4,
+#mappings .fa,
+.popover-content,
+.box .dropdown-toggle .glyphicon,
+.box-header button,
+.toggle .toggle-group .btn,
+.input-group .form-control label {
+  font-size: 120%;
+}
+
+input,
+select,
+.footer a,
+.modal-footer button,
+.dropdown-menu:not([role='menu']),
+.dropdown-menu > li.header,
+div#tablehandling ul.menu i,
+.content-header > .breadcrumb,
+.btn-primary:not(.btn-circle),
+.modal-content .box .dialog pre {
+  font-size: 100% !important;
+}
+
+#topology .window {
+  height: 90px;
+}
+
+.dataTables_length select {
+  font-size: 85% !important;
+}
+
+button.close {
+  font-size: 2em;
+}
+
+.details-footer .information {
+  font-size: 12px;
+}
+
+.btn-file i,
+.btn-file span,
+.input-group-btn button.btn-primary {
+  font-size: 20px !important;
+}
+
+.checkbox input[type=checkbox], 
+.checkbox-inline input[type=checkbox], 
+.radio input[type=radio], 
+.radio-inline input[type=radio],
+input[type=checkbox], 
+input[type=radio] {
+  width: 20px;
+  height: 20px;
+}
+
+.k-timepicker, 
+.k-datepicker {
+  width: 200px !important;
+}
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility/accessibilityHC.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility/accessibilityHC.css
new file mode 100644
index 0000000..aa4d5b9
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/accessibility/accessibilityHC.css
@@ -0,0 +1,296 @@
+/*
+ * 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.
+ */
+
+/* General
+============================================================================= */
+.content-wrapper,
+.content-wrapper .box,
+.modal-content,
+.modal-header,
+.background-footer,
+input:not(:disabled):not([type="file"]),
+select,
+select option,
+button:not(.close),
+.table .sorting,
+.dataTables_paginate a,
+.modal-footer,
+.box-header,
+.dropdown-menu,
+.main-footer,
+.circular-actions a,
+.k-select,
+.file-caption.kv-fileinput-caption {
+  color: #f7f7f7 !important;
+  background-color: #0f1417 !important;
+}
+
+
+.dataTable thead th:after,
+.close {
+  color: #f7f7f7 !important;
+  opacity: 0.8 !important;
+}
+
+
+.input-group-addon a,
+.input-group-addon i,
+.dropdown-menu > li:not(.disabled) > a:hover,
+.dropdown-menu > li:not(.disabled) > a:hover span,
+.dropdown-menu > li:not(.disabled) > a:focus,
+.dropdown-menu > li:not(.disabled) > a:focus span,
+#startAtContainer .input-group-addon span,
+#startAtContainer .k-widget span,
+#startAtContainer .k-widget i,
+#startAtContainer .k-widget input,
+.modal-footer i,
+.navbar-nav > .user-menu > .dropdown-menu > .user-footer .btn-default,
+.content-wrapper .nav-tabs a,
+.wrapper .content-wrapper .nav.nav-tabs li.active a span,
+.wrapper .content-wrapper .nav.nav-tabs li.active a,
+.wrapper .content-wrapper .nav.nav-tabs li a:hover span,
+.wrapper .content-wrapper .nav.nav-tabs li a:hover,
+.wrapper .content-wrapper .nav.nav-tabs li a:active span,
+.wrapper .content-wrapper .nav.nav-tabs li a:active,
+.wrapper .content-wrapper .nav.nav-tabs li a:focus span,
+.wrapper .content-wrapper .nav.nav-tabs li a:focus,
+.wrapper .content-wrapper .tab-content .btn-primary .fa-download {
+  color: #000000 !important;
+}
+
+
+.content-wrapper a:not(.btn-primary),
+.content-wrapper span:not(.label-info):not([role="presentation"]):not([class^='cm-']),
+.content-wrapper p,
+.content-wrapper .box-title a,
+.realm-choice .dropdown-menu > li:not(.disabled) > a:hover,
+.realm-choice .dropdown-menu > li:not(.disabled) > a:hover span,
+.realm-choice .dropdown-menu > li:not(.disabled) > a:focus,
+.realm-choice .dropdown-menu > li:not(.disabled) > a:focus span,
+.box-header,
+.breadcrumb .active,
+table tbody tr:hover button span,
+.dropdown-menu > li > a,
+.modal-header button,
+.circular-actions a i,
+.wizard-form .input-group-addon i,
+#startAtContainer .input-group-addon i,
+.attribute .input-group-addon i {
+  color: #f7f7f7 !important;
+}
+
+
+.box.box-primary {
+  border-top-color: #f7f7f7;
+}
+
+
+img,
+.content-wrapper div.btn.btn-file span.hidden-xs {
+  background-color: #f7f7f7;
+  color: #000000 !important;
+}
+.btn-primary,
+.callout.callout-info,
+.alert-info,
+.label-info,
+.modal-info .modal-body {
+  background-color: #f7f7f7 !important;
+  color: #000000 !important;
+}
+
+
+.logo img,
+.modal-content .input-group .input-group-addon {
+  background-color: transparent;
+}
+
+
+.modal-dialog {
+  border: 2px solid white;
+}
+
+
+#veil:not(:required):after {
+  -webkit-box-shadow: #f7f7f7 1.5em 0 0 0, 
+    #f7f7f7 1.1em 1.1em 0 0, 
+    #f7f7f7 0 1.5em 0 0,
+    #f7f7f7 -1.1em 1.1em 0 0, 
+    rgba(0, 0, 0, 0.5) -1.5em 0 0 0, 
+    rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, 
+    #f7f7f7 0 -1.5em 0 0, 
+    #f7f7f7 1.1em -1.1em 0 0;
+  box-shadow: #f7f7f7 1.5em 0 0 0, 
+    #f7f7f7 1.1em 1.1em 0 0, 
+    #f7f7f7 0 1.5em 0 0, 
+    #f7f7f7 -1.1em 1.1em 0 0, 
+    #f7f7f7 -1.5em 0 0 0, 
+    #f7f7f7 -1.1em -1.1em 0 0, 
+    #f7f7f7 0 -1.5em 0 0, 
+    #f7f7f7 1.1em -1.1em 0 0; 
+}
+
+
+.input-group input:disabled,
+.input-group input[disabled],
+.control-sidebar-dark .control-sidebar-menu > li > a:hover,
+.table-hover > tbody > tr:hover,
+.skin-blue .main-header .navbar .sidebar-toggle:hover,
+.main-header .navbar .sidebar-toggle:hover,
+.skin-blue .main-header .logo:hover,
+.main-header .logo:hover,
+div.toggle-menu ul li:hover,
+.skin-blue .main-header .navbar .nav > li > a:hover, 
+.skin-blue .main-header .navbar .nav > li > a:active, 
+.skin-blue .main-header .navbar .nav > li > a:focus, 
+.skin-blue .main-header .navbar .nav .open > a:hover, 
+.skin-blue .main-header .navbar .nav .open > a:focus,
+.skin-blue .sidebar-menu > li:hover > a, 
+.skin-blue .sidebar-menu > li.active > a,
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus, 
+.dropdown-menu > li > a:hover,
+.k-block, 
+.k-draghandle, 
+.k-grid-header, 
+.k-grouping-header, 
+.k-header, 
+.k-pager-wrap, 
+.k-toolbar, 
+.k-treemap-tile,
+.k-picker-wrap,
+.k-state-disabled,
+.km-pane-wrapper .k-header {
+  background-color: rgba(108, 115, 117, 0.55);
+}
+
+
+.sidebar-mini .main-header .navbar {
+  background-color: #222d32; /* color from 'skin-blue' */
+  border-bottom: 1px white solid;
+  box-sizing: border-box;
+}
+.sidebar-mini .main-header .logo,
+div.toggle-menu,
+.skin-blue .main-header li.user-header {
+  background-color: #222d32;
+}
+
+
+div.toggle-menu {
+  border: 1px solid #f7f7f7;
+}
+
+
+a,
+.pagination > .active > a, 
+.pagination > .active > a:focus, 
+.pagination > .active > a:hover, 
+.pagination > .active > span, 
+.pagination > .active > span:focus, 
+.pagination > .active > span:hover {
+  border-color: #76abd9;
+}
+a {
+  color: #76abd9;
+}
+
+
+.logs button.btn-primary {
+  border-color: #f7f7f7;
+}
+.logs button.btn-primary:hover {
+  border-color: #adadad;
+}
+div.infolabel,
+.input-group input:disabled,
+.input-group input[disabled] {
+  color: #d2d2d2;
+}
+
+
+.bg-red,
+.callout.callout-danger,
+.alert-danger,
+.alert-error,
+.label-danger,
+.modal-danger .modal-body,
+.btn-danger.active, 
+.btn-danger:active, 
+.open>.dropdown-toggle.btn-danger {
+  background-color: #942819 !important;
+}
+.bg-yellow {
+  background-color: #6F4706 !important;
+}
+.bg-green,
+.copy-clipboard-feedback,
+.btn-success,
+.callout.callout-success, 
+.alert-success, 
+.label-success, 
+.modal-success .modal-body {
+  background-color: #005C32 !important;
+}
+.bg-aqua {
+  background-color: #004E61 !important;
+}
+.bg-yellow, 
+.callout.callout-warning, 
+.alert-warning, 
+.label-warning, 
+.modal-warning .modal-body {
+  background-color: #6F4706 !important;
+}
+.callout.callout-warning {
+  border-color: #6F4706;
+}
+
+
+.bootstrap-select .btn.btn-default {
+  background-color: rgba(101, 101, 101, 0.7) !important;
+}
+
+
+/* Login page
+============================================================================= */
+.login-body {
+  background-image: linear-gradient(rgb(31, 109, 142), #004626);
+}
+
+.login-logo {
+  background: transparent;
+}
+
+.btn-accessibility {
+  color: #f7f7f7 !important;
+}
+
+.form-signin .btn-primary, 
+.form-signin .btn-primary {
+  border: 2px solid white;
+}
+.form-signin .btn-primary.focus, 
+.form-signin .btn-primary:focus {
+  border-color: #8c8c8c;
+}
+
+.login-card {
+  background-color: #0f1417;
+}
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts.css
new file mode 100644
index 0000000..da7bdf8
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts.css
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+/* source-sans-pro-300 - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 300;
+  src: local('Source Sans Pro Light'), local('SourceSansPro-Light'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* source-sans-pro-300italic - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: italic;
+  font-weight: 300;
+  src: local('Source Sans Pro Light Italic'), local('SourceSansPro-LightItalic'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* source-sans-pro-regular - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* source-sans-pro-italic - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Source Sans Pro Italic'), local('SourceSansPro-Italic'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* source-sans-pro-600 - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 600;
+  src: local('Source Sans Pro SemiBold'), local('SourceSansPro-SemiBold'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* source-sans-pro-600italic - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: italic;
+  font-weight: 600;
+  src: local('Source Sans Pro SemiBold Italic'), local('SourceSansPro-SemiBoldItalic'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
+/* source-sans-pro-700 - latin-ext_cyrillic_latin_cyrillic-ext */
+@font-face {
+  font-family: 'Source Sans Pro';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'),
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
+    url('./fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
+}
+
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff
new file mode 100644
index 0000000..2132b5e
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff2
new file mode 100644
index 0000000..943f826
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff
new file mode 100644
index 0000000..aa25cd3
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff2
new file mode 100644
index 0000000..441997f
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-300italic.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff
new file mode 100644
index 0000000..24d2824
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff2
new file mode 100644
index 0000000..9ec7d25
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff
new file mode 100644
index 0000000..ce5a1cc
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff2
new file mode 100644
index 0000000..7ed2f82
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-600italic.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff
new file mode 100644
index 0000000..9fbfe68
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff2
new file mode 100644
index 0000000..096dcb1
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-700.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff
new file mode 100644
index 0000000..c1cf1ea
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff2
new file mode 100644
index 0000000..ff006be
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-italic.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff
new file mode 100644
index 0000000..e8a1ac7
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff2 b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff2
new file mode 100644
index 0000000..1b0bc46
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/fonts/source-sans-pro-v11-latin-ext_cyrillic_latin_cyrillic-ext-regular.woff2
Binary files differ
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/login.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/login.css
new file mode 100644
index 0000000..931cb47
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/login.css
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+body, html {
+  height: 100% !important;
+  background-repeat: no-repeat;
+  background-image: linear-gradient(rgb(70, 30, 30), #dd4b39);
+}
+
+.card-container.card {
+  width: 350px;
+  padding: 40px 40px;
+}
+
+.btn {
+  font-weight: 700;
+  height: 36px;
+  -moz-user-select: none;
+  -webkit-user-select: none;
+  user-select: none;
+  cursor: default;
+}
+
+/*
+ * Card component
+ */
+.card {
+  background-color: #F7F7F7;
+  /* just in case there no content*/
+  padding: 20px 25px 30px;
+  margin: 0 auto 25px;
+  margin-top: 50px;
+  /* shadows and rounded borders */
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  border-radius: 2px;
+  -moz-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+  -webkit-box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+  box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3);
+}
+
+.login-logo {
+  width: 200px;
+  margin: 0 auto 10px;
+  display: block;
+}
+
+/*
+ * Form styles
+ */
+.profile-name-card {
+  font-size: 16px;
+  font-weight: bold;
+  text-align: center;
+  margin: 10px 0 0;
+  min-height: 1em;
+}
+
+.form-signin #inputPassword {
+  direction: ltr;
+  height: 44px;
+  font-size: 16px;
+}
+
+.form-signin input[type=password],
+.form-signin input[type=text],
+.form-signin button {
+  width: 100%;
+  display: block;
+  margin-bottom: 10px;
+  z-index: 1;
+  position: relative;
+  -moz-box-sizing: border-box;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+}
+
+.form-signin .form-control:focus {
+  border-color: rgb(104, 145, 162);
+  outline: 0;
+  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162);
+  box-shadow: inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgb(104, 145, 162);
+}
+
+.btn.btn-signin {
+  background-color: rgb(104, 145, 162);
+  padding: 0px;
+  font-weight: 700;
+  font-size: 14px;
+  height: 36px;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  border-radius: 3px;
+  border: none;
+  -o-transition: all 0.218s;
+  -moz-transition: all 0.218s;
+  -webkit-transition: all 0.218s;
+  transition: all 0.218s;
+}
+
+.btn.btn-signin:hover,
+.btn.btn-signin:active,
+.btn.btn-signin:focus {
+  background-color: #00a65a;
+}
+
+.btn.btn-sso {
+  padding: 0px;
+  font-weight: 700;
+  font-size: 14px;
+  height: 36px;
+  -moz-border-radius: 3px;
+  -webkit-border-radius: 3px;
+  border-radius: 3px;
+  border: none;
+  -o-transition: all 0.218s;
+  -moz-transition: all 0.218s;
+  -webkit-transition: all 0.218s;
+  transition: all 0.218s;
+}
+
+.btn.btn-sso:hover,
+.btn.btn-sso:active,
+.btn.btn-sso:focus {
+  background-color: #00a65a;
+}
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/search.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/search.css
new file mode 100644
index 0000000..ece4f4a
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/search.css
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+.searchBox .col-xs-12{
+  padding-left: 0px !important;
+  padding-right: 0px !important;
+}
+
+.clause{
+  display: block;
+  line-height: 34px;
+  width: 100%;
+}
+
+.clause .operator{
+  width: 65px !important;
+}
+
+.clause .operator .checkbox{
+  margin: 0px !important;
+}
+
+.clause .field {
+  line-height: 34px;
+  float: left;
+  padding: 0 3px 0px 0px;
+  display: inline-block !important;
+}
+.clause .type{
+  width: 120px !important;
+}
+
+.clause .property{
+  width: 190px;
+}
+
+.clause .comparator{
+  width: 100px;
+}
+
+.clause .comparator button{
+  width: 100px !important;
+}
+
+.clause .value{
+  width: 220px;
+}
+
+.clause .textvalue{
+  width: 45px;
+}
+
+.clause .action{
+  float: left;
+  padding: 0px 7px 0px;
+}
+
+.searchBox .input-group-addon:last-child{
+  border: 1px solid #ccc !important;
+}
+
+.searchBox .input-group{
+  margin-top: 1px;
+}
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/css/syncopeEnduser.css b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/syncopeEnduser.css
new file mode 100644
index 0000000..0b487d3
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/css/syncopeEnduser.css
@@ -0,0 +1,1377 @@
+/*
+ * 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.
+ */
+
+pre {
+  white-space: -moz-pre-wrap; /* Mozilla, supported since 1999 */
+  white-space: -pre-wrap; /* Opera */
+  white-space: -o-pre-wrap; /* Opera */
+  white-space: pre-wrap; /* CSS3 - Text module (Candidate Recommendation) http://www.w3.org/TR/css3-text/#white-space */
+  word-wrap: break-word; /* IE 5.5+ */
+}
+
+/* Absolute Center Spinner */
+#veil {
+  display:none;
+  position: fixed;
+  z-index:99999;
+  height: 2em;
+  width: 2em;
+  overflow: show;
+  margin: auto;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+}
+
+/* Transparent Overlay */
+#veil:before {
+  content: '';
+  display: block;
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0,0,0,0.3);
+}
+
+/* :not(:required) hides these rules from IE9 and below */
+#veil:not(:required) {
+  /* hide "loading..." text */
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+#veil:not(:required):after {
+  content: '';
+  display: block;
+  font-size: 10px;
+  width: 1em;
+  height: 1em;
+  margin-top: -0.5em;
+  -webkit-animation: spinner 2000ms infinite linear;
+  -moz-animation: spinner 2000ms infinite linear;
+  -ms-animation: spinner 2000ms infinite linear;
+  -o-animation: spinner 2000ms infinite linear;
+  animation: spinner 2000ms infinite linear;
+  border-radius: 0.5em;
+  -webkit-box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.5) -1.5em 0 0 0, rgba(0, 0, 0, 0.5) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
+  box-shadow: rgba(0, 0, 0, 0.75) 1.5em 0 0 0, rgba(0, 0, 0, 0.75) 1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) 0 1.5em 0 0, rgba(0, 0, 0, 0.75) -1.1em 1.1em 0 0, rgba(0, 0, 0, 0.75) -1.5em 0 0 0, rgba(0, 0, 0, 0.75) -1.1em -1.1em 0 0, rgba(0, 0, 0, 0.75) 0 -1.5em 0 0, rgba(0, 0, 0, 0.75) 1.1em -1.1em 0 0;
+}
+
+/* Animation */
+
+@-webkit-keyframes spinner {
+  0% {
+    -webkit-transform: rotate(0deg);
+    -moz-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(360deg);
+    -moz-transform: rotate(360deg);
+    -ms-transform: rotate(360deg);
+    -o-transform: rotate(360deg);
+    transform: rotate(360deg);
+  }
+}
+@-moz-keyframes spinner {
+  0% {
+    -webkit-transform: rotate(0deg);
+    -moz-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(360deg);
+    -moz-transform: rotate(360deg);
+    -ms-transform: rotate(360deg);
+    -o-transform: rotate(360deg);
+    transform: rotate(360deg);
+  }
+}
+@-o-keyframes spinner {
+  0% {
+    -webkit-transform: rotate(0deg);
+    -moz-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(360deg);
+    -moz-transform: rotate(360deg);
+    -ms-transform: rotate(360deg);
+    -o-transform: rotate(360deg);
+    transform: rotate(360deg);
+  }
+}
+@keyframes spinner {
+  0% {
+    -webkit-transform: rotate(0deg);
+    -moz-transform: rotate(0deg);
+    -ms-transform: rotate(0deg);
+    -o-transform: rotate(0deg);
+    transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(360deg);
+    -moz-transform: rotate(360deg);
+    -ms-transform: rotate(360deg);
+    -o-transform: rotate(360deg);
+    transform: rotate(360deg);
+  }
+}
+
+.block-sidebar {
+  max-height: 100% !important; 
+  overflow: auto !important; 
+  padding-top: 90px !important; 
+  padding-bottom: 50px !important; 
+  position: fixed !important;
+  width: 245px !important;
+}
+
+.inner-control-sidebar {
+  position: fixed; 
+  height: auto;
+}
+
+.content-margin-layout {
+  margin: 0px 230px 0px 0px !important;
+  padding: 20px !important;
+}
+
+.admin-content-page {
+  padding: 20px;
+  background: #ecf0f5
+}
+
+.realms {
+  min-height: 554px
+}
+
+.realm-header {
+  clear: both;
+  display:block;
+  display: inline-table;
+  margin: 0 0 10px;
+  line-height: 25px;
+}
+
+.realm-label {
+  float: left;
+  font-size: 16px;
+}
+
+.realm-label label {
+  font-weight: 600 !important;
+}
+
+.realm-choice {
+  right: 0px;
+  position: absolute;
+}
+
+.realm-header .dropdown-menu li a {
+  text-align: left !important;
+  white-space: pre !important;
+  line-height: 7px;
+}
+
+.block-header {
+  position: fixed !important;
+  width: 100% !important;
+  top: 0 !important;
+}
+
+.block-footer {
+  position: fixed !important;
+  width: 100% !important;
+  bottom: 0px !important;
+}
+
+.logo-pos {
+  padding-top: 4px !important;
+  overflow: visible !important;
+}
+
+.angle {
+  border: medium none !important;
+  cursor: pointer;
+  display: inline-table !important;
+  float: right;
+  height: 30px;
+  overflow: hidden;
+  position: relative !important;
+  right: 0;
+  top: -30px;
+  width: 25%;
+  z-index: 3;
+}
+
+.main-header .logo {
+  height: 55px !important;
+}
+
+.w_caption h3 {
+  font-size: 16px;
+}
+
+div.wicket-modal div.w_content_3 {
+  border: 1px solid #eee;
+  border-radius: 20px;
+  padding: 5px;
+}
+
+.tab-content {
+  margin-bottom: 10px;
+  margin-top: 5px;
+  position: relative;
+  overflow-x: hidden;
+  overflow-y: auto;
+  padding: 20px 20px 5px 20px;
+}
+
+.modal-body .tab-content .information {
+  position: relative !important;
+}
+
+.scrollable-tab-content {
+  overflow-y: auto;
+  max-height: 480px;
+}
+
+.inner-scrollable-tab-content {
+  height: 430px;
+  margin-top: 20px;
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+a.help {
+  position: relative;
+  display: inline;
+  text-decoration: none;
+}
+
+a.help span {
+  position: absolute;
+  width:19em;
+  color: #000000;
+  background: whitesmoke;
+  visibility: hidden;
+  border-radius: 0px;
+  padding: 3px;
+}
+
+a.help span:after {
+  position: absolute;
+  top: 50%;
+  left: 100%;
+  margin-top: -8px;
+  width: 0;
+  height: 0;
+}
+
+a.help span {
+  visibility: visible;
+  opacity: none;
+  right: 100%;
+  top: 50%;
+  margin-right: 4px;
+  margin-top: -11px;
+  border: 1px solid black;
+  z-index: 1000000;
+}
+
+a.help span a {
+  color: #463;
+  text-decoration: none;
+}
+
+.modal-open .modal {
+  overflow: hidden;
+}
+
+.modal-body {
+  max-height: 550px;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.modal {
+  background: rgba(0, 0, 0, 0) none repeat scroll 0 0;
+  z-index: 7000 !important;
+}
+
+.wizard-step-title {
+  font-size: 18px !important;
+  font-weight: normal !important;
+  margin-bottom: 10px !important;
+}
+
+div.wizard-view div.wizard-view{
+  max-height: 380px;
+  height: 380px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  padding: 50px 20px 50px 20px;
+}
+
+.wizard-view .wizard-form{
+  max-height: 450px;
+  height: 450px;
+}
+
+.modal-body .tab-content .wizard-view {
+  max-height: 330px !important;
+  height: 330px !important;
+}
+
+.modal-body .tab-content .wizard-form {
+  max-height: 400px !important;
+  height: 400px !important;
+}
+
+.wizard-view > div {
+  display: block;
+  height: 95%;
+  position: relative;
+}
+
+.wizard-view {
+  padding: 0px 5px;
+}
+
+.wizard-form {
+  height:480px;
+  position: relative;
+}
+
+.wizard-form > div {
+  max-height: 440px;
+  height: 440px;
+  overflow-y: auto;
+  overflow-x: hidden;
+  padding: 0px;
+}
+
+.box-body .wizard-form {
+  height:400px !important;
+}
+
+.box-body .wizard-form > div {
+  height: 360px !important;
+}
+
+div.modal-body div.box-body div.wizard-buttons {
+  bottom: 30px;
+}
+
+.wizard-buttons {
+  padding: 10px 0px 5px 0px;
+  position: absolute;
+  bottom: 4px;
+  width: 100%;
+}
+
+.wizard-buttons div.float-left {
+  position: absolute;
+  left: 15px;
+}
+
+.wizard-buttons div.float-right {
+  position: absolute;
+  right: 15px;
+}
+
+.wizard-step-title {
+  font-weight: bold; 
+  font-size:medium;
+}
+
+div.realms div.summarize {
+  margin: 50px 100px;
+}
+
+.navbar a {
+  height: 55px
+}
+
+.navbar .footer a {
+  height: 34px
+}
+
+.navbar .user-footer a {
+  height: 34px
+}
+
+span.overridable div.checkbox {
+  float: right; 
+  margin: 0px; 
+  padding: 0px;
+}
+
+span.overridable div.checkbox label div div.toggle-group label {
+  padding-left: 7px;
+}
+
+div#outer.modal-lg, div#utilityModal.modal-lg, section#notifications .modal-lg {
+  max-width: 1200px;
+  width: 97%;
+}
+
+.details {
+  max-height: 440px;
+  overflow-x: hidden;
+  overflow-y: auto;
+  display: block
+}
+
+th.checkGroupColumn {
+  width: 20px;
+  text-align: center;
+  padding-right: 8px !important;
+}
+
+td.checkGroupColumn {
+  text-align: center;
+}
+
+/**
+  BEGIN - Style for Information panel
+*/
+div.information{
+  margin: 30px 0px 0px 0px; 
+  border: 1px solid #EEE; 
+  font-size: 10px;
+  color: #888;
+  display: inline-table; 
+  width: 100%; 
+  clear: both; 
+  float:none;
+  position: absolute;
+  bottom: -90px;
+  left: 0px;
+  padding: 2px;
+}
+
+div.infolabel{
+  margin-left: 5px; 
+  float:left; 
+  width: 150px;
+  font-weight: bold;
+  color: #888;
+}
+
+div.infoleft{
+  float:left; 
+  display: inline-table; 
+  width: 50%
+}
+
+div.inforight{ 
+  display: inline-table; 
+  width: 50%
+}
+
+div.inforow{
+  display: inline-table;
+  width: 100%
+}
+
+div.wrap{
+  word-wrap: break-word; 
+  width: 550px; 
+  margin-left: 155px;
+}
+/**
+END - Style for Information panel
+*/
+
+#ownership div.toggle {
+  width: 110px !important;
+}
+
+.table > tbody > tr > td.list_view_panel_labels {
+  vertical-align: middle;
+}
+
+div.searchResult{
+  padding-top: 30px;
+  display: block;
+  clear: both;
+}
+
+.logs .input-group-addon .input-group-btn {
+  width: 130px !important;
+}
+
+.logs .col_width {
+  width: 90% !important;
+}
+
+.logs .box-header {
+  display: none !important;
+}
+
+.logs .box{
+  border-top: 0px !important;
+}
+
+*::after, *::before {
+  box-sizing: border-box;
+}
+
+.wicket-aa-container {
+  border-color: #eee;
+  box-shadow: none;
+  background-clip: padding-box;
+  background-color: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  border-radius: 4px;
+  box-shadow: 0 6px 12px rgba(0, 0, 0, 0.176);
+  float: left;
+  font-size: 14px;
+  min-width: 160px;
+  max-height: 250px;
+  z-index: 7001 !important;
+}
+
+div.wicket-aa ul {
+  list-style: none;
+  padding-left: 15px;
+}
+/**
+START - startAt
+*/
+div#startAt {
+  background-color: rgba(98, 98, 98, 0.98) !important;
+  color: #CCC;
+  right: 5px !important;
+  top: 100px !important;
+  min-width: 450px;
+  min-height: 130px !important;
+  z-index: 6000 !important;
+}
+
+div#itemTransformersTogglePanel {
+  min-width: 1000px;
+}
+
+div#startAtContainer {
+  padding: 15px;
+}
+
+div#startAtContainer input {
+  background-color: rgba(200, 200, 200, 0.60) !important;
+}
+/**
+END - startAt
+*/
+
+/**
+START - Notifications
+*/
+/*Temporany fix diagonal stacking*/
+.k-popup.k-notification {
+  box-shadow: none;
+}
+
+.k-notification-error.k-group {
+  background: rgba(100% , 0% , 0% , .7);
+  color: red;
+}
+
+.errorNotification {
+  width: 300px;
+  height: 100px;
+  vertical-align: middle;
+  display: table-cell;
+}
+
+.errorNotification #level {
+  float: left; 
+  padding-left: 10px; 
+  font-size: 2em; 
+  width: 2%;
+}
+
+.errorNotification #message {
+  float: right; 
+  padding-left: 0px; 
+  width:85%
+}
+
+.k-notification-success.k-group {
+  background: rgba(0% , 60% , 0% , .7);
+  color: #fff;
+}
+
+.successNotification {
+  width: 300px;
+  height: 100px;
+  vertical-align: middle;
+  display: table-cell;
+}
+
+.successNotification #level {
+  float: left; 
+  padding-left: 10px; 
+  font-size: 2em; 
+  width: 2%;
+}
+
+.successNotification #message {
+  float: right; 
+  padding-left: 0px; 
+  width:85%
+}
+/**
+EN - Notifications
+*/
+
+/**
+START - Actions
+*/
+.actions > li > a {
+  padding: 5px 0px 5px 0px !important;
+}
+
+div.listview-actions a {
+  float:left;
+  padding: 5px 0px 5px 0px !important;
+}
+
+.action a.btn {
+  padding: 0px;
+}
+
+.btn-circle, .circular-actions a {
+  border-radius: 15px !important;
+  font-size: 12px;
+  height: 30px;
+  line-height: 1.42857;
+  padding: 6px 0;
+  text-align: center;
+  width: 30px;
+}
+
+.circular-actions a.btn {
+  background-color: #3c8dbc;
+  border-color: #367fa9;
+  color: #fff;
+}
+
+.btn-circle i, .circular-actions a i {
+  margin: 0px;
+}
+
+.multipanel-btn-minus {
+  padding: 0px 0px 0px 6px;
+  border: 0 none !important;
+}
+
+.multipanel-btn-plus {
+  padding: 15px 0px 8px 6px;
+  border: 0 none !important;
+}
+
+.multipanel-box {
+  padding: 5px;
+  display: inline-table;
+  margin: 0px;
+}
+/**
+END - Actions
+*/
+
+/**
+START - DataTable
+*/
+.dataTable {
+  clear: both;
+}
+/**
+END - DataTable
+*/
+
+/**
+START - Result page
+*/
+.attribute {
+  padding: 0px 4px 0px 4px; 
+}
+
+span.highlight .attribute label {
+  color : red;
+}
+/**
+END - Result page
+*/
+
+.navbar-nav > .user-menu > .dropdown-menu > li.user-header {
+  height: auto !important;
+  padding: 10px;
+  text-align: center;
+}
+
+.nav-tabs-custom > .nav-tabs > li.active {
+  border-top-color: #d2d6de !important;
+}
+
+.code-deletion {
+  background-color: #ffdddd;
+  border-color: #f1c0c0;
+}
+
+.code-addition {
+  background-color: #dbffdb;
+  border-color: #c1e9c1;
+}
+
+/**
+START - AjaxDateTimePicker
+*/
+.input-auto-width {
+  width: auto !important;
+}
+
+.icon-top-position {
+  top: 5px !important;
+}
+
+/**
+END - AjaxDateTimePicker
+*/
+
+/**
+START - Search - AjaxDateTimePicker
+*/
+.searchBox .col-xs-12{
+  padding-left: 0px !important;
+  padding-right: 0px !important;
+}
+
+.clause{
+  display: block;
+  line-height: 34px;
+  width: 100%;
+}
+
+.clause .operator{
+  width: 65px !important;
+}
+
+.clause .operator .checkbox{
+  margin: 0px !important;
+}
+
+.clause .field {
+  line-height: 34px;
+  float: left;
+  padding: 0 3px 0px 0px;
+  display: inline-block !important;
+}
+
+.clause .type{
+  width: 170px !important;
+}
+
+.clause .type button{
+  width: 170px !important;
+}
+
+.clause .property{
+  width: 300px;
+}
+
+.clause .property button{
+  width: 300px;
+}
+
+.clause .comparator{
+  width: 100px;
+}
+
+.clause .comparator button{
+  width: 100px !important;
+}
+
+.clause .value{
+  width: 220px;
+}
+
+.clause .date{
+  width: 160px;
+}
+
+.clause .hours{
+  width: 45px;
+}
+
+.clause .separator{
+  width: 20px;
+  padding-left: 12px;
+}
+
+.clause .action{
+  float: left;
+  padding: 0px 7px 0px;
+}
+
+.searchBox .input-group-addon:last-child{
+  border: 1px solid #ccc !important;
+}
+
+.searchBox .input-group{
+  margin-top: 1px;
+}
+
+.custom-autocomplete-box li.selected {
+  background-color: #eee;
+}
+
+/**
+END - Search - AjaxDateTimePicker
+*/
+/**
+START - Parameters Details
+*/
+div#parametersForm{
+  min-height: 220px;
+}
+/**
+END - Parameters Details
+*/
+
+li.todoitem a {
+  cursor: default;
+}
+
+.popover{
+  max-width: 100%;
+}
+
+#popover:hover {
+  cursor: pointer;
+}
+
+.popover-content {
+  color: black;
+}
+
+.form-palette {
+  display: inline-block;
+  width: 119%;
+}
+
+.form-group-inline {
+  display: inline-table;
+  width: 100%;
+}
+
+div#userFilter #title{
+  margin: 10px 0 0 8px;
+  font-weight: bold;
+}
+
+div#userFilter  #warning{
+  margin: 1px 0 5px 8px;
+  color: #e00000;
+  font-size: 9px;
+}
+
+div#userFilter  #check {
+  margin: 1px 0 5px 8px;
+}
+
+.keyColumn {
+  width: 20px;
+}
+
+/**
+START - Alert widget onside menu
+*/
+div.alert-widget > a > .label {
+  font-size: 9px;
+  line-height: 0.9;
+  padding: 2px 3px;
+  position: absolute;
+  margin-left: 10px;
+  text-align: center;
+  top: 0px;
+}
+
+div.alert-widget > a {
+  display: block;
+  position: relative;
+}
+
+div.alert-widget > a > i {
+  position: absolute;
+  top: 9px;
+}
+
+div.alert-widget{
+  text-align: center;
+  vertical-align: middle;
+  padding-right: 20px;
+}
+
+div.alert-widget .dropdown-menu > li.header {
+  background-color: #ffffff;
+  border-bottom: 1px solid #f4f4f4;
+  border-radius: 4px 4px 0 0;
+  color: #444444;
+  font-size: 14px;
+  padding: 7px 10px;
+}
+
+div.alert-widget .dropdown-menu > li.footer > a {
+  background-color: #fff;
+  border-radius: 0 0 4px 4px;
+  color: #444 !important;
+  font-size: 12px;
+  padding: 7px 10px;
+  text-align: center;
+}
+
+div.alert-widget .dropdown-menu > li .menu {
+  list-style: outside none none;
+  padding: 10px;
+}
+/**
+END - Alert widget onside menu
+*/
+
+/**
+START - Transformers toggle panel
+*/
+div.transformersTogglePanel {
+  background-color: rgba(140, 140, 140, 0.99) !important;
+  width: 1000px !important;
+}
+
+div.transformersContainer {
+  padding: 15px;
+}
+
+div.transformersContainer input {
+  background-color: rgba(200, 200, 200, 0.60) !important;
+}
+
+div.transformersContainer #footer {
+  clear: both;
+  display: block;
+  text-align: right;
+  margin-top: 10px;
+}
+
+div.transformersContainer #body {
+  clear: both;
+  display: block;
+}
+
+div.itemTransformersContainer #body select {
+  border: 1px solid #ccc !important;
+  background: rgba(240, 240, 240, 0.95) !important
+}
+
+div.itemTransformersContainer #body a {
+  color: #ccc !important;
+  cursor: pointer !important;
+}
+
+div#jexlTransformersTogglePanel {
+  right: 400px;
+}
+/**
+END - Transformers toggle panel
+*/
+
+/**
+START - CRONTAB
+*/
+div#schedule input, div#schedule fieldset {
+  width: 50px;
+  float: left
+}
+
+div#templates {
+  padding-top: 30px;
+  clear: both;
+}
+/**
+END - CRONTAB
+*/
+
+/**
+START - EVENTS
+*/
+.events {
+  display: table-row;
+  width: 990px;
+}
+
+.selectedEvents {
+  display: inline-block;
+  height: 100px;
+  margin: 10px 10px 0 10px;
+  overflow-y: auto;
+}
+
+.eventSelection {
+  display: inline-table;
+  float: right;
+  width: 380px;
+  min-width: 530px;
+}
+
+.eventSelection div#value {
+  height: auto;
+  overflow: hidden;
+}
+
+.eventSelection div#value div#custom {
+  width: auto;
+  overflow: hidden;
+}
+
+.eventSelection div#value div#customActions {
+  width: 85px;
+  float: right;
+}
+
+div#selectionContainer select {
+  width: 585px;
+  min-width: 585px;
+}
+/**
+END - EVENTS
+*/
+
+fieldset.input-group {
+  width: 100%;
+}
+
+div.modal-content > div.modal-header {
+  background-color: #f0f0f0;
+  height: 64px;
+}
+
+div.modal-content > div.modal-footer {
+  background-color: #f0f0f0;
+  height: 64px;
+}
+
+.modal-body .box-body div.background-footer {
+  display: none !important;
+}
+
+div.background-footer {
+  background-color: #f0f0f0;
+  display: block;
+  height: 64px;
+  margin-bottom: -15px;
+  margin-left: -30px;
+  margin-top: -25px;
+  width: 1300px;
+}
+
+.logviewer-btn {
+  padding: 0px 16px 20px 0px;
+  float: right;
+}
+
+div#editUserChangePassword > label {
+  color: #e00000;
+}
+
+div > li {
+  list-style-type: none; 
+}
+
+th ul.menu, div#inline-actions ul.menu, div#tablehandling ul.menu, div.listview-actions ul.menu {
+  list-style-type: none;  
+  margin: 0;
+  padding: 0;
+}
+
+div#tablehandling ul.menu{
+  float: right;
+}
+
+th ul.menu li, div#inline-actions ul.menu li, div#tablehandling ul.menu li, div.listview-actions ul.menu li {
+  display: inline-block;
+}
+
+th ul.menu li a, div#inline-actions ul.menu li a, div.listview-actions ul.menu li a, div#tablehandling ul.menu li a {
+  margin-left: 5px;
+  display: inline-block !important;
+}
+
+ul.menu li a {
+  display: block;
+  cursor: pointer !important;
+}
+
+ul.menu i {
+  width: 35px;
+}
+
+div#inline-actions ul.menu i, div#tablehandling ul.menu i {
+  width: auto !important;
+}
+
+.toggle-menu ul.menu li a {
+  padding: 0px;
+  text-align: left;
+}
+
+div#tablehandling ul.menu li a {
+  padding: 0px !important;
+}
+
+.realm-choice #realm-choice-field {
+  width: 200px;
+  padding-right: 20px;
+}
+
+.realm-choice #realm-choice-folder {
+  color: #3c8dbc;
+}
+
+.palette-scrollbar {
+  min-width: 100%;
+  overflow: scroll;
+}
+
+@-moz-document url-prefix(''){
+  .palette-scrollbar {
+    min-width: 100%;
+    width: auto;
+    overflow: scroll;
+  }
+}
+
+.widget-user-username-panel {
+  display: inline-block;
+  margin-left: 15px;
+}
+
+.widget-user-username-icon {
+  font-size: 32px;
+}
+
+/*                             User Requests
+============================================================================= */
+
+h4.card-title {
+  text-align: center;
+}
+.bpmn-process-span{
+  display: inline-block;
+  width: 80%;
+}
+
+/*                             Extensions
+============================================================================= */
+.subnav li a .fa{
+  width: 20px;
+} 
+
+
+.sidebar-mini.sidebar-collapse .sidebar-menu .subnav li a span {
+  display: none !important;
+}
+
+
+/*                             Status Icon
+============================================================================= */
+
+.checkmark_circle_ok {
+  stroke-dasharray: 166;
+  stroke-dashoffset: 166;
+  stroke-width: 2;
+  stroke-miterlimit: 10;
+  stroke: #7ac142;
+  fill: none;
+  animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
+}
+
+.checkmark_circle_error {
+  stroke-dasharray: 166;
+  stroke-dashoffset: 166;
+  stroke-width: 2;
+  stroke-miterlimit: 10;
+  stroke: #FF0000;
+  fill: none;
+  animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
+}
+
+.checkmark_ok {
+  width: 56px;
+  height: 56px;
+  border-radius: 50%;
+  display: block;
+  stroke-width: 2;
+  stroke: #fff;
+  stroke-miterlimit: 10;
+  margin: 10% auto;
+  box-shadow: inset 0px 0px 0px #7ac142;
+  animation: fill_ok .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
+}
+
+.checkmark_error {
+  width: 56px;
+  height: 56px;
+  border-radius: 50%;
+  display: block;
+  stroke-width: 2;
+  stroke: #fff;
+  stroke-miterlimit: 10;
+  margin: 10% auto;
+  box-shadow: inset 0px 0px 0px #FF0000;
+  animation: fill_error .4s ease-in-out .4s forwards, scale .3s ease-in-out .9s both;
+}
+
+.checkmark_check_ok .checkmark_check_error  {
+  transform-origin: 50% 50%;
+  stroke-dasharray: 48;
+  stroke-dashoffset: 48;
+  animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
+}
+
+.formcard, .card-hover-shadow:hover{
+  box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19);
+  margin-bottom: 15px;
+  padding: 0px !important;
+}
+
+.box {
+  position: relative;
+  border-radius: 3px;
+  background: #ffffff;
+  border-top: 3px solid #d2d6de;
+  margin-bottom: 20px;
+  width: 100%;
+  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
+}
+
+.no-border-top {
+  border-top: 0px !important;
+}
+
+.card-hover-blue:hover{
+  color:#fff!important;
+  background-color:#dd4b39!important
+}
+
+.card-container,.card-panel{
+  padding:0.01em 16px;
+}
+
+.card-container-body{
+  padding:1.01em 16px;
+}
+
+.card-container-body-request{
+  padding-bottom: 45px;
+}
+
+.card-container-padding{
+  padding-bottom: 45px;
+}
+
+.card-font-color a a:hover, a:active, a:focus {
+  color: #ffffff;
+}
+
+.card-panel{
+  margin-top:16px;
+  margin-bottom:16px
+}
+
+.paginator a {
+  color: #999;
+  float: left;
+  padding: 8px 16px;
+  text-decoration: none;
+  transition: background-color .3s;
+  border: 1px solid #ddd;
+}
+
+.paginator a.active {
+  background-color: #4CAF50;
+  color: white;
+  border: 1px solid #4CAF50;
+}
+
+.paginator a:hover:not(.active) {background-color: #ddd;}
+
+@keyframes stroke {
+  100% {
+    stroke-dashoffset: 0;
+  }
+}
+@keyframes scale {
+  0%, 100% {
+    transform: none;
+  }
+  50% {
+    transform: scale3d(1.1, 1.1, 1);
+  }
+}
+@keyframes fill_ok {
+  100% {
+    box-shadow: inset 0px 0px 0px 30px #7ac142;
+  }
+}
+
+@keyframes fill_error {
+  100% {
+    box-shadow: inset 0px 0px 0px 30px #FF0000;
+  }
+}
+
+.card-header-style {
+    font-weight: bold !important;
+    font-size: 18px;
+    color: white !important;
+    padding-top: 5px;
+}
+
+.brand-custom{
+  height: 56px; 
+  z-index: 1; 
+  position: absolute; 
+  top: 0; 
+  left: 0; 
+  width: 50%;
+}
+
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/js/accessibility.js b/client/idrepo/enduser/src/main/resources/META-INF/resources/js/accessibility.js
new file mode 100644
index 0000000..b10a48e
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/js/accessibility.js
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+
+$(document).ready(function () {
+
+  var locationDomain = window.location.origin + '/' + window.location.pathname.split('/')[1];
+
+  var initAssetsManager = function () {
+    var AssetsManager = {};
+
+    var createLink = function (id, url) {
+      if (!$('link#' + id).length && !$('link[href="' + url + '"').length) {
+        var link = document.createElement('link');
+        link.rel = 'stylesheet';
+        link.href = url;
+        link.type = "text/css";
+        $('head').append(link);
+      }
+    };
+
+    var createScript = function (id, url) {
+      if (!$('script#' + id).length && !$('script[src="' + url + '"').length) {
+        var script = document.createElement('script');
+        script.src = url;
+        $('body').append(script);
+      }
+    };
+
+    AssetsManager.checkAlreadyLoaded = function (url, type) {
+      var elems = (type === 'css') ? document.styleSheets : ((type === 'js') ? document.scripts : '');
+      var attr = (type === 'js') ? 'src' : ((type === 'css') ? 'href' : 'none');
+      for (var i in elems) {
+        var attrUrl = elems[i][attr] || "";
+        var assetName = attrUrl.split("/").slice(-1).join();
+        if (attrUrl !== ""
+                && (assetName === url.split("/").slice(-1).join() || assetName === url)) {
+          return true;
+        }
+      }
+      return false;
+    };
+
+    var removeLoaded = function (url, type) {
+      var tag = (type === 'js') ? 'script' : ((type === 'css') ? 'link' : '');
+      if (AssetsManager.checkAlreadyLoaded(url, type)) {
+        $(tag + '[href~="' + url + '"]').remove();
+      }
+    };
+
+    AssetsManager.inject = function (id, url, type) {
+      switch (type) {
+        case 'js':
+          createScript(id, url);
+          break;
+
+        case 'css':
+          createLink(id, url);
+          break;
+
+        default:
+          break;
+      }
+    };
+
+    AssetsManager.remove = function (url, type) {
+      removeLoaded(url, type);
+    };
+
+    return AssetsManager;
+  };
+
+  var initAccessibilityController = function () {
+    AccessibilityController = {};
+
+    var AssetsManager = initAssetsManager();
+
+    var fontSizeFiles = [
+      locationDomain + '/css/accessibility/accessibilityFont.css'
+    ];
+
+    var darkThemeFiles = [
+      locationDomain + '/css/AdminLTE_skins/skin-blue.css',
+      locationDomain + '/css/accessibility/accessibilityHC.css'
+    ];
+
+    var darkThemeMainClass = 'skin-blue';
+    var defaultThemeMainClass = 'skin-green-light';
+
+    var doSwitch = function (check, files) {
+      if (!check) {
+        for (var i = 0; i < files.length; i++) {
+          AssetsManager.remove(files[i], 'css');
+        }
+      } else {
+        for (var i = 0; i < files.length; i++) {
+          AssetsManager.inject('theme_css_' + i, files[i], 'css');
+        }
+      }
+    };
+
+    var doSwitchTheme = function (check, files) {
+      doSwitch(check, files);
+
+      if ($('body').hasClass(defaultThemeMainClass) && check) {
+        $('body').removeClass(defaultThemeMainClass).addClass(darkThemeMainClass);
+      } else {
+        $('body').removeClass(darkThemeMainClass).addClass(defaultThemeMainClass);
+      }
+    };
+
+    var savePreference = function (key, value) {
+      window.localStorage.setItem(key, value);
+    };
+
+    var getPreference = function (key) {
+      var storageValue = window.localStorage.getItem(key);
+      if (storageValue === null) {
+        savePreference(key, 'false');
+      }
+      return storageValue === 'true';
+    };
+
+    AccessibilityController.FONT_SIZE_PREF = 'font_size_pref';
+    AccessibilityController.HC_THEME_PREF = 'hc_theme_pref';
+
+    var isIncreasedFont = getPreference(AccessibilityController.FONT_SIZE_PREF);
+    var isHighContrast = getPreference(AccessibilityController.HC_THEME_PREF);
+
+    AccessibilityController.checkPref = function (pref) {
+      switch (pref) {
+        case AccessibilityController.FONT_SIZE_PREF:
+          doSwitch(isIncreasedFont, fontSizeFiles);
+          break;
+
+        case AccessibilityController.HC_THEME_PREF:
+          doSwitchTheme(isHighContrast, darkThemeFiles);
+          break;
+
+        default:
+          break;
+      }
+    };
+
+    AccessibilityController.switchIncreasedFont = function () {
+      isIncreasedFont = !isIncreasedFont;
+      doSwitch(isIncreasedFont, fontSizeFiles);
+      savePreference(AccessibilityController.FONT_SIZE_PREF, isIncreasedFont);
+    };
+
+    AccessibilityController.switchTheme = function () {
+      isHighContrast = !isHighContrast;
+      doSwitchTheme(isHighContrast, darkThemeFiles);
+      savePreference(AccessibilityController.HC_THEME_PREF, isHighContrast);
+    };
+
+    return AccessibilityController;
+  };
+
+  var AccessibilityController = initAccessibilityController();
+
+  AccessibilityController.checkPref(AccessibilityController.FONT_SIZE_PREF);
+  AccessibilityController.checkPref(AccessibilityController.HC_THEME_PREF);
+
+  $('#change_contrast').off('click.acc_hc');
+  $('#change_contrast').on('click.acc_hc', function () {
+    AccessibilityController.switchTheme();
+    return false;
+  });
+
+  $('#change_contrast').off('keydown.key_acc_hc keypress.key_acc_hc');
+  $('#change_contrast').on('keydown.key_acc_hc keypress.key_acc_hc', function (event) {
+    // check "enter" key pressed
+    if (event.which === 13) {
+      AccessibilityController.switchTheme();
+
+      event.preventDefault();
+      return false;
+    }
+  });
+
+  $('#change_fontSize').off('click.acc_f');
+  $('#change_fontSize').on('click.acc_f', function () {
+    AccessibilityController.switchIncreasedFont();
+    return false;
+  });
+
+  $('#change_fontSize').off('keydown.key_acc_f keypress.key_acc_f');
+  $('#change_fontSize').on('keydown.key_acc_f keypress.key_acc_f', function (event) {
+    // check "enter" key pressed
+    if (event.which === 13) {
+      AccessibilityController.switchIncreasedFont();
+
+      event.preventDefault();
+      return false;
+    }
+  });
+
+  $('body').off('keydown.acc_binding keypress.acc_binding');
+  $('body').on('keydown.acc_binding keypress.acc_binding', function (event) {
+    // alt - shift - F
+    // alt - shift - H
+    if (event.altKey && event.shiftKey) {
+      if (event.keyCode === 72) {
+        AccessibilityController.switchTheme();
+      } else if (event.keyCode === 70) {
+        AccessibilityController.switchIncreasedFont();
+      }
+      event.preventDefault();
+    }
+  });
+
+});
diff --git a/client/idrepo/enduser/src/main/resources/META-INF/resources/js/copyToClipboard.js b/client/idrepo/enduser/src/main/resources/META-INF/resources/js/copyToClipboard.js
new file mode 100644
index 0000000..f4618a6
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/js/copyToClipboard.js
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+if (typeof copyToClipboard === 'undefined') {
+  copyToClipboard = function (element, tag_value_to_copy, fake_textarea_ID, feedback_selector) {
+    var $elem = $(element);
+
+    // creating new textarea element and giveing it an ID
+    var temp = document.createElement('textarea');
+    temp.id = fake_textarea_ID;
+    temp.style.display = 'block';
+
+    // Make less noise in the page
+    temp.style.height = 0;
+
+    // Append it to the page somewhere, in this case <body>
+    $elem.append(temp);
+
+    // Copy whatever is in the div to our new textarea
+    temp.value = $elem.attr(tag_value_to_copy);
+    $(temp).text(temp.value);
+
+    // Copy whatever inside the textarea to clipboard
+    $(temp).focus().select();
+    document.execCommand('SelectAll');
+    document.execCommand("copy", false, null);
+
+    if ($.browser.mozilla && !$.browser.chrome) {
+      try {
+        var range = document.createRange();
+        range.selectNodeContents(temp);
+        var selection = window.getSelection();
+        selection.removeAllRanges();
+        selection.addRange(range);
+
+        document.execCommand("Copy", false, null);
+      } catch (e) {
+      }
+    }
+
+    // Remove the textarea
+    $(temp).remove();
+
+    $elem.siblings(feedback_selector).fadeIn();
+
+    // Remove feedback element
+    window.setTimeout(function () {
+      $elem.siblings(feedback_selector).fadeOut();
+    }, 1000);
+  };
+
+  function doCopyToClipboard(el) {
+    var feedback_selector = '.copy-clipboard-feedback';
+    var fake_textarea_selector = 'tttt';
+    var tag_value_to_copy = 'data-value';
+
+    if (!$(feedback_selector + ":visible").length) {
+      copyToClipboard(el, tag_value_to_copy, fake_textarea_selector, feedback_selector);
+    }
+  }
+}
+
+
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java b/client/idrepo/enduser/src/main/resources/META-INF/resources/js/syncopeEnduser.js
similarity index 70%
copy from client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java
copy to client/idrepo/enduser/src/main/resources/META-INF/resources/js/syncopeEnduser.js
index 7c86c2d..006c9a1 100644
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/pages/SelfUpdate.java
+++ b/client/idrepo/enduser/src/main/resources/META-INF/resources/js/syncopeEnduser.js
@@ -16,16 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.enduser.pages;
 
-import org.apache.wicket.request.mapper.parameter.PageParameters;
-
-public class SelfUpdate extends BaseEnduserWebPage {
-
-    private static final long serialVersionUID = 164651008547631054L;
-
-    public SelfUpdate(final PageParameters parameters) {
-        super(parameters);
-    }
-
-}
+$(document).ready(function (){
+  $('.card-header .card-title').on('click', function(){
+    $('.card.card-outline .collapse').css('padding', '2em');
+  });
+});
diff --git a/client/idrepo/enduser/src/main/resources/customFormAttributes.json b/client/idrepo/enduser/src/main/resources/customFormAttributes.json
deleted file mode 100644
index 9e26dfe..0000000
--- a/client/idrepo/enduser/src/main/resources/customFormAttributes.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/customFormLayout.json b/client/idrepo/enduser/src/main/resources/customFormLayout.json
new file mode 100644
index 0000000..af4f21c
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/customFormLayout.json
@@ -0,0 +1,14 @@
+{ 
+  "formClass": "org.apache.syncope.client.enduser.panels.UserFormPanel",
+  "auxClasses": true,
+  "groups": true,
+  "plainAttrs": true,
+  "derAttrs": true,
+  "virAttrs": true,
+  "resources": true,
+  "whichPlainAttrs": {},
+  "whichDerAttrs": {},
+  "whichVirAttrs": {},
+  "passwordManagement": true,
+  "detailsManagement": true
+}
diff --git a/client/idrepo/enduser/src/main/resources/enduser.properties b/client/idrepo/enduser/src/main/resources/enduser.properties
index 1d62418..b4061af 100644
--- a/client/idrepo/enduser/src/main/resources/enduser.properties
+++ b/client/idrepo/enduser/src/main/resources/enduser.properties
@@ -26,6 +26,15 @@
 captcha=true
 csrf=true
 
+# Sidebar
+sidebar=org.apache.syncope.client.enduser.panels.Sidebar
+
+# Page
+page.profile=org.apache.syncope.client.enduser.pages.Dashboard
+page.edituser=org.apache.syncope.client.enduser.pages.EditUser
+page.editchangepassword=org.apache.syncope.client.enduser.pages.EditChangePassword
+page.editsecurityquestion=org.apache.syncope.client.enduser.pages.EditSecurityQuestion
+
 security.headers.X-XSS-Protection=1; mode=block
 security.headers.Strict-Transport-Security=max-age=31536000; includeSubDomains; preload
 security.headers.X-Content-Type-Options=nosniff
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties b/client/idrepo/enduser/src/main/resources/oidcclient-agent.properties
similarity index 80%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
copy to client/idrepo/enduser/src/main/resources/oidcclient-agent.properties
index 7f39171..17775f8 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/oidcclient-agent.properties
@@ -14,5 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
-auxClasses.palette=\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b
+conf.directory=${conf.directory}
+
+anonymousUser=${anonymousUser}
+anonymousKey=${anonymousKey}
+
+scheme=https
+host=localhost
+port=8443
+rootPath=/syncope/rest/
+useGZIPCompression=true
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication.properties
similarity index 81%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication.properties
index 0c1754b..4a13869 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication.properties
@@ -73,7 +73,7 @@
 implementations=Implementations
 
 timeout=Operation is taking too long: it will be executed in background. Please check later for the result (errors won't be triggered).
-security=Security
+editsecurityquestion=Security question
 before=Before
 after=After
 
@@ -81,3 +81,26 @@
 
 invalid.security.answer=Invalid security answer
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+home=Home
+profile=Personal Information
+requests=User Requests
+edituser=Edit profile
+submit=Submit
+cancel=Cancel
+editchangepassword=Change password
+attributes.derived=Derived Attributes
+attributes.plain=Plain Attributes
+attributes.virtual=Virtual Attributes
+captcha=Captcha
+details=Details
+user_request_error=Error during user request. Please contact your administrator
+
+page.home=Home
+page.changePassword=Change Password
+page.editSecurityQuestion=Edit Security Question
+page.edituser=Edit User
+page.confirmPasswordReset=Confirm Password Reset
+page.selfPwdReset=Password Reset
+page.selfRegistration=Registration
+page.resultPage=Result Page
+page.userRequests=User Requests
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_fr_CA.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_fr_CA.properties
new file mode 100644
index 0000000..59104f0
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_fr_CA.properties
@@ -0,0 +1,105 @@
+#
+#  Copyright (C) 2020 Tirasa (info@tirasa.net)
+# 
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+users=Utilisateurs
+groups=Groupes
+configuration=Configuration
+resources=Ressources
+connectors=Raccordements
+reports=Rapports
+tasks=T\u00e2ches
+logout=D\u00e9connexion
+schema=Sch\u00e9ma
+operation_succeeded=Op\u00e9ration compl\u00e9t\u00e9e avec succ\u00e8s
+operation_error=Une erreur s'est produite pendant l'op\u00e9ration
+alert=Alerte:
+confirmDelete=Voulez-vous vraiment supprimer le(s) article(s) s\u00e9lectionn\u00e9(s)?
+confirmUnlink=Voulez-vous vraiment supprimer le lien entre le(s) article(s) s\u00e9lectionn\u00e9(s) et la ressource?
+confirmUnassign=Souhaitez-vous vraiment supprimer l'affectation entre le(s) article(s) s\u00e9lectionn\u00e9(s) et la ressource?
+confirmDeprovision=Souhaitez-vous vraiment d\u00e9provisionner le(s) article(s) s\u00e9lectionn\u00e9(s)?
+confirmProvision=Souhaitez-vous vraiment approvisionner le(s) article(s) s\u00e9lectionn\u00e9(s)?
+confirmClone=Voulez-vous vraiment cloner le(s) article(s) s\u00e9lectionn\u00e9(s)?
+
+dropDownChoiceField.nullValid=Faire un choix
+DateTimeField$HoursValidator=La valeur horaire doit \u00eatre dans la gamme (1, 12)
+error=Erreur
+generic_error=Une erreur s'est produite pendant l'op\u00e9ration
+id=Cl\u00e9
+name=Nom
+palette.available=Disponible
+palette.selected=S\u00e9lectionn\u00e9
+jexl_info=Ce champ requiert une expression JEXL, par exemple:
+jexl_ex1=nom de famille + ',' + pr\u00e9nom
+jexl_ex2=new.' + nom de famille
+jexl_syntax_url=R\u00e9f\u00e9rence JEXL compl\u00e8te
+create=Cr\u00e9er
+key=Cl\u00e9
+types=Types
+self-registration-link=Auto-inscription
+self-pwd-reset-link=Mot de passe de r\u00e9initialisation automatique
+user-requests-link=Demandes des utilisateurs
+realms=Domaines
+roles=R\u00f4les
+policies=Politiques
+workflow=Flux
+logs=Registres
+layouts=Dispositions
+notifications=Notifications
+parameters=Param\u00e8tres
+extensions=Extensions
+NavigatorLabel=Affichage des rang\u00e9es ${from} \u00e0 ${to} de ${of}
+displayRows=Rang\u00e9es \u00e0 afficher
+OrderByLink.CSS.ascending=tri_asc
+OrderByLink.CSS.descending=tri_desc
+OrderByLink.CSS.none=tri
+entitlements=Droits et privil\u00e8ges
+audit=Audit
+connectors.confirm.reload=Cette demande est potentiellement dangereuse pour le d\u00e9roulement des op\u00e9rations, continuer?
+intAttrNameInfo.help=En plus des attributs auto-compl\u00e9t\u00e9s, vous pouvez \u00e9galement faire r\u00e9f\u00e9rence \u00e0 des groupes, des objets, des appartenances ou des privil\u00e8ges, par exemple:
+confirmGlobalLogout=Voulez-vous vraiment effectuer une d\u00e9connexion compl\u00e8te?
+implementations=Impl\u00e9mentations
+
+timeout=L'op\u00e9ration prend trop de temps: elle sera ex\u00e9cut\u00e9e en arri\u00e8re-plan. Veuillez v\u00e9rifier plus tard le r\u00e9sultat (les erreurs ne seront pas d\u00e9tect\u00e9es).
+editsecurityquestion=Question de s\u00e9curit\u00e9
+before=Avant
+after=Apr\u00e8s
+
+captcha_error=Le captcha saisi ne correspond pas
+
+invalid.security.answer=R\u00e9ponse de s\u00e9curit\u00e9 non valide
+tooLargeFile=Le fichier est trop grand, la taille du fichier de t\u00e9l\u00e9chargement max est ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+home=Aper\u00e7u
+profile=Information personnelle
+requests=Demandes des utilisateurs
+edituser=Editer le profil
+submit=Soumettre
+cancel=Annuler
+editchangepassword=Changer mot de passe
+attributes.derived=Derived Attributes
+attributes.plain=Editer le profil
+attributes.virtual=Virtual Attributes
+captcha=Captcha
+details=Des d\u00e9tails
+user_request_error=Erreur lors de la demande de l'utilisateur. Veuillez contacter votre administrateur
+
+page.home=Aper\u00e7u
+page.changePassword=Changer mot de passe
+page.editSecurityQuestion=Modifier la question de s\u00e9curit\u00e9
+page.edituser=Modifier l'utilisateur
+page.confirmPasswordReset=Confirm Password Reset
+page.selfPwdReset=Password Reset
+page.selfRegistration=Enregistrement
+page.resultPage=Page de r\u00e9sultat
+page.userRequests=Demandes des utilisateurs
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_it.properties
similarity index 78%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_it.properties
index e8620a0..7330ec8 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_it.properties
@@ -70,7 +70,7 @@
 implementations=Implementazioni
 
 timeout=L'operazione sta durando troppo: sar\u00e0 eseguita in background. Verifica il risultato pi\u00f9 tardi (gli errori non saranno notificati).
-security=Sicurezza
+editsecurityquestion=Domanda segreta
 before=Prima
 after=Dopo
 
@@ -78,3 +78,29 @@
 
 invalid.security.answer=Risposta di sicurezza non valida
 tooLargeFile=File troppo grande, la dimensione massima ammessa \\u00e8 ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB).
+home=Home
+profile=Informazioni personali
+requests=User Requests
+edituser=Modifica profilo
+submit=Submit
+cancel=Cancel
+editchangepassword=Gestione password
+attributes.derived=Derived Attributes
+attributes.plain=Plain Attributes
+attributes.virtual=Virtual Attributes
+captcha=Captcha
+details=Details
+user-requests-link=User Requests
+self-pwd-reset-link=Self Password Reset
+self-registration-link=Self Registration
+user_request_error=Errore durante la richiesta. Si prega di contattare l'amministratore di sistema.
+
+page.home=Home
+page.changePassword=Cambio Password
+page.editSecurityQuestion=Modifica domanda segreta
+page.edituser=Modifica profilo
+page.confirmPasswordReset=Conferma Reset Password
+page.selfPwdReset=Password Reset
+page.selfRegistration=Self Registration
+page.resultPage=Risultato
+page.userRequests=User Requests
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_ja.properties
similarity index 83%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_ja.properties
index 553dfb9..10e8d09 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_ja.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_ja.properties
@@ -68,10 +68,36 @@
 confirmGlobalLogout=\u30b0\u30ed\u30fc\u30d0\u30eb\u30ed\u30b0\u30a2\u30a6\u30c8\u3057\u3066\u3082\u3088\u308d\u3057\u3044\u3067\u3059\u304b?
 implementations=\u5c0e\u5165
 timeout=\u64cd\u4f5c\u306b\u9577\u6642\u9593\u304b\u304b\u3063\u3066\u3044\u307e\u3059\: \u30d0\u30c3\u30af\u30b0\u30e9\u30a6\u30f3\u30c9\u3067\u5b9f\u884c\u3055\u308c\u307e\u3059\u3002 \u7d50\u679c\u3092\u5f8c\u3067\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044 (\u30a8\u30e9\u30fc\u306f\u5f15\u304d\u8d77\u3053\u3057\u307e\u305b\u3093)\u3002
-security=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3
+editsecurityquestion=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3
 before=\u524d
 after=\u5f8c
 
 captcha_error=\u5165\u529b\u3055\u308c\u305f\u30ad\u30e3\u30d7\u30c1\u30e3\u304c\u4e00\u81f4\u3057\u307e\u305b\u3093
 invalid.security.answer=Invalid security answer
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+home=Home
+profile=Personal Information
+requests=User Requests
+edituser=\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u7de8\u96c6
+submit=Submit
+cancel=Cancel
+editchangepassword=Change password
+attributes.derived=Derived Attributes
+attributes.plain=Plain Attributes
+attributes.virtual=Virtual Attributes
+captcha=Captcha
+details=Details
+user-requests-link=User Requests
+self-pwd-reset-link=Self Password Reset
+self-registration-link=Self Registration
+user_request_error=\u30e6\u30fc\u30b6\u30fc\u30ea\u30af\u30a8\u30b9\u30c8\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f\u3002 \u7ba1\u7406\u8005\u306b\u9023\u7d61\u3057\u3066\u304f\u3060\u3055\u3044
+
+page.home=Home
+page.changePassword=Change Password
+page.editSecurityQuestion=Edit Security Question
+page.edituser=Edit User
+page.confirmPasswordReset=Confirm Password Reset
+page.selfPwdReset=Password Reset
+page.selfRegistration=Registration
+page.resultPage=Result Page
+page.userRequests=User Requests
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_pt_BR.properties
similarity index 79%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_pt_BR.properties
index d1d66c5..9223e0b 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_pt_BR.properties
@@ -70,10 +70,36 @@
 implementations=Implementa\u00e7\u00f5es
 
 timeout=Operation is taking too long: it will be executed in background. Please check later for the result (errors won't be triggered).
-security=Security
+editsecurityquestion=Security question
 before=Before
 after=After
 
 captcha_error=Captcha entered does not match
 invalid.security.answer=Invalid security answer
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB).
+home=Home
+profile=Personal Information
+requests=User Requests
+edituser=Edit profile
+submit=Submit
+cancel=Cancel
+editchangepassword=Change password
+attributes.derived=Derived Attributes
+attributes.plain=Plain Attributes
+attributes.virtual=Virtual Attributes
+captcha=Captcha
+details=Details
+user-requests-link=User Requests
+self-pwd-reset-link=Self Password Reset
+self-registration-link=Self Registration
+user_request_error=Erro durante a solicita\u00e7\u00e3o do usu\u00e1rio. Por favor contacte o seu administrador
+
+page.home=Home
+page.changePassword=Change Password
+page.editSecurityQuestion=Edit Security Question
+page.edituser=Edit User
+page.confirmPasswordReset=Confirm Password Reset
+page.selfPwdReset=Password Reset
+page.selfRegistration=Registration
+page.resultPage=Result Page
+page.userRequests=User Requests
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_ru.properties
similarity index 87%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_ru.properties
index a1859f5..ecf3ec7 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeWebApplication_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/SyncopeEnduserApplication_ru.properties
@@ -69,10 +69,36 @@
 implementations=\u0420\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438
 
 timeout=Operation is taking too long: it will be executed in background. Please check later for the result (errors won't be triggered).
-security=Security
+editsecurityquestion=Security question
 before=Before
 after=After
 
 captcha_error=Captcha entered does not match
 invalid.security.answer=Invalid security answer
 tooLargeFile=File is too large, max upload file size is ${maxUploadSizeB} bytes (${maxUploadSizeMB} MB). 
+home=Home
+profile=Personal Information
+requests=User Requests
+edituser=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u043e\u0444\u0438\u043b\u044c
+submit=Submit
+cancel=Cancel
+editchangepassword=Change password
+attributes.derived=Derived Attributes
+attributes.plain=Plain Attributes
+attributes.virtual=Virtual Attributes
+captcha=Captcha
+details=Details
+user-requests-link=User Requests
+self-pwd-reset-link=Self Password Reset
+self-registration-link=Self Registration
+user_request_error=\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f. \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435\u0441\u044c \u043a \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u0443
+
+page.home=Home
+page.changePassword=Change Password
+page.editSecurityQuestion=Edit Security Question
+page.edituser=Edit User
+page.confirmPasswordReset=Confirm Password Reset
+page.selfPwdReset=Password Reset
+page.selfRegistration=Registration
+page.resultPage=Result Page
+page.userRequests=User Requests
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.html
index 4037265..e39dcc5 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/BinaryFieldPanel.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -36,8 +37,8 @@
           </span>
           <input wicket:id="fileUpload" type="file"/>
           <div>
-            <a href="#" wicket:id="downloadLink"><i title="download" alt="file download icon" class="far fa-arrow-alt-circle-down"></i></a>
-            <a href="#" wicket:id="resetLink"><i title="remove value" alt="remove value icon" class="fas fa-times"></i></a>
+            <a href="#" wicket:id="downloadLink"><i title="download" alt="file download icon" class="fa fa-arrow-alt-circle-down"></i></a>
+            <a href="#" wicket:id="resetLink"><i title="remove value" alt="remove value icon" class="fa fa-times"></i></a>
             <span wicket:id="preview"/>
           </div>
         </form>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel.html
index 6bcb727..1f645f2 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel_ru.properties
deleted file mode 100644
index 3971914..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/MultiFieldPanel_ru.properties
+++ /dev/null
@@ -1,29 +0,0 @@
-# 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.
-#
-# minLength=Минимальный размер пароля
-minLength=\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u043f\u0430\u0440\u043e\u043b\u044f
-# maxLength=Максимальный размер пароля
-maxLength=\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u043f\u0430\u0440\u043e\u043b\u044f
-# historyLength=Размер истории паролей
-historyLength=\u0420\u0430\u0437\u043c\u0435\u0440 \u0438\u0441\u0442\u043e\u0440\u0438\u0438 \u043f\u0430\u0440\u043e\u043b\u0435\u0439
-# digitRequired=Пароль должен содержать не менее одной цифры
-digitRequired=\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 \u043e\u0434\u043d\u043e\u0439 \u0446\u0438\u0444\u0440\u044b
-# prefixesNotPermitted=Пароль не должен содержать префикс
-prefixesNotPermitted=\u041f\u0430\u0440\u043e\u043b\u044c \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u043f\u0440\u0435\u0444\u0438\u043a\u0441
-# apply=Сохранить
-apply=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html
index f633396..9a177f8 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/navigation/Navbar.html
@@ -83,7 +83,7 @@
 
                 <div id="logout" class="btn-navbar-right">
                   <i aria-label="Logout" tabindex="0" accesskey="H"
-                     class="fas fa-sign-out-alt">
+                     class="fa fa-sign-out-alt">
                   </i>
                 </div>
               </a>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.html
deleted file mode 100644
index 5d44a8e..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BaseEnduserWebPage.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!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">
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
-    <meta charset="UTF-8"/>
-    <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'/>
-
-    <title>Apache Syncope</title>
-
-    <link rel="shortcut icon" href="ui-commons/img/favicon.png" type="image/png"/>
-
-    <link href="webjars/font-awesome/${font-awesome.version}/css/all.min.css" rel="stylesheet" type="text/css"/>
-    <link href="webjars/ionicons/${ionicons.version}/css/ionicons.min.css" rel="stylesheet" type="text/css" />
-    <link href="ui-commons/css/fonts.css" rel="stylesheet" type="text/css"/>
-
-    <link href="ui-commons/css/animations.css" rel="stylesheet" type="text/css" />
-    <link href="ui-commons/css/syncopeUI.css" rel="stylesheet" type="text/css"/>
-    <link href="ui-commons/css/login.css" rel="stylesheet" type="text/css" />
-
-    <!-- accessibility -->
-    <script type="text/javascript" src="ui-commons/js/accessibility.js"></script>
-
-    <script type="text/javascript" src="webjars/bootbox/${bootbox.version}/bootbox.js"></script>
-    <script type="text/javascript" src="webjars/jQuery-slimScroll/${jquery-slimscroll.version}/jquery.slimscroll.min.js"></script>
-  </head>
-  <body wicket:id="body" class="skin-green-light hold-transition sidebar-mini">
-    <span wicket:id="navbar">[NAVBAR]</span>
-
-    <div class="wrapper">
-      <span wicket:id="feedback"></span>
-
-      <div class="content-wrapper custom_content_wrapper">
-        <wicket:child/>
-      </div>
-    </div>
-
-    <div id="veil">Loading...</div>
-  </body>
-</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage.html
new file mode 100644
index 0000000..b03dfbd
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage.html
@@ -0,0 +1,90 @@
+<!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:message="lang:lang">
+<head>
+    <meta charset="UTF-8"/>
+    <meta content="width=device-width, initial-scale=1" name="viewport"/>
+
+    <title>Apache Syncope</title>
+
+    <link rel="shortcut icon" href="ui-commons/img/favicon.png" type="image/png"/>
+
+    <link href="webjars/font-awesome/${font-awesome.version}/css/all.min.css" rel="stylesheet" type="text/css"/>
+    <link href="webjars/ionicons/${ionicons.version}/css/ionicons.min.css" rel="stylesheet" type="text/css"/>
+    <link href="ui-commons/css/fonts.css" rel="stylesheet" type="text/css"/>
+
+    <link href="css/AdminLTE_plugins/dataTables.bootstrap4.min.css" rel="stylesheet" type="text/css"/>
+    <link href="ui-commons/css/animations.css" rel="stylesheet" type="text/css"/>
+    <link href="ui-commons/css/syncopeUI.css" rel="stylesheet" type="text/css"/>
+    <link href="css/syncopeEnduser.css" rel="stylesheet" type="text/css"/>
+
+    <!-- accessibility -->
+    <link href="ui-commons/css/accessibility.css" rel="stylesheet" type="text/css" />
+    <script type="text/javascript" src="ui-commons/js/accessibility.js"></script>
+
+    <script type="text/javascript" src="webjars/bootbox/${bootbox.version}/bootbox.js"></script>
+    <script type="text/javascript" src="webjars/jQuery-slimScroll/${jquery-slimscroll.version}/jquery.slimscroll.min.js"></script>
+    <script type="text/javascript">
+      // global variable for IndicatingOnConfirmAjaxLink
+      var proceed = false;
+    </script>
+</head>
+<body class="hold-transition sidebar-mini" wicket:id="body">
+
+<div class="wrapper">
+    <span wicket:id="feedback"></span>
+    <nav class="main-header navbar navbar-expand navbar-red navbar-light" aria-label="primary" style="z-index: 0">
+        <ul class="navbar-nav">
+            <li class="nav-item">
+                <a href="#" class="nav-link" data-widget="pushmenu" role="button" wicket:id="collapse">
+                    <i class="fas fa-bars"></i>
+                </a>
+            </li>
+        </ul>
+
+        <ul class="navbar-nav ml-auto">
+            <li class="nav-item">
+                <a class="nav-link" href="#" wicket:id="logout">
+                    <i class="fas fa-sign-out-alt" aria-hidden="true"></i>
+                </a>
+            </li>
+        </ul>
+    </nav>
+    <aside wicket:id="sidebar" class="main-sidebar sidebar-light-red elevation-4" aria-label="secondary">[SIDEBAR]</aside>
+
+    <div class="content-wrapper" wicket:id="contentWrapper" role="main">
+        <section class="content-header">
+            <h1><span wicket:id="pageTitle"/></h1>
+        </section>
+        <wicket:child/>
+    </div>
+
+      <footer class="main-footer">
+        <strong>Copyright &copy; 2010&#45;${year}
+          <a href="http://www.apache.org/" target="_blank" rel="noopener noreferrer">The Apache Software Foundation</a>.</strong>
+        All rights reserved.
+      </footer>
+
+    <div class="control-sidebar-bg" style="position: fixed; height: auto;"></div>
+</div>
+
+<div id="veil">Loading...</div>
+</body>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage.properties
similarity index 82%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage.properties
index 7f39171..8a6ebdb 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage.properties
@@ -14,5 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
-auxClasses.palette=\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b
+home=Home
+version=Version
+domain=Domain
+
+systemInfo=System Information
+hostname=Host Name
+processors=Available Processors
+os=Operating System
+jvm=JVM
+lang=en
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_fr_CA.properties
similarity index 79%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_fr_CA.properties
index 7f39171..d39be37 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_fr_CA.properties
@@ -14,5 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
-auxClasses.palette=\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b
+home=Accueil
+version=Version
+domain=Domaine
+systemInfo=Information sur le syst\u00e8me
+hostname=Nom d'h\u00f4te
+processors=Processeurs disponibles
+os=Syst\u00e8me d'exploitation
+jvm=JVM
+lang=fr
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_it.properties
similarity index 81%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_it.properties
index 7f39171..660159f 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_it.properties
@@ -14,5 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
-auxClasses.palette=\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b
+home=Home
+version=Versione
+domain=Dom\u00ednio
+
+systemInfo=Informazioni di Sistema
+hostname=Nome Host
+processors=Processori Disponibili
+os=Sistema Operativo
+jvm=JVM
+lang=it
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_ja.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_ja.properties
index 3779b77..3ece231 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_ja.properties
@@ -14,12 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+home=\u30db\u30fc\u30e0
+version=\u30d0\u30fc\u30b8\u30e7\u30f3
+domain=\u30c9\u30e1\u30a4\u30f3
+
+systemInfo=\u30b7\u30b9\u30c6\u30e0\u60c5\u5831
+hostname=\u30db\u30b9\u30c8\u540d
+processors=\u5229\u7528\u53ef\u80fd\u306a\u30d7\u30ed\u30bb\u30c3\u30b5\u30fc
+os=\u30aa\u30da\u30ec\u30fc\u30c6\u30a3\u30f3\u30b0\u30b7\u30b9\u30c6\u30e0
+jvm=JVM
+lang=ja
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_pt_BR.properties
similarity index 81%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_pt_BR.properties
index 7f39171..2bd5964 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_pt_BR.properties
@@ -14,5 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
-auxClasses.palette=\u0412\u0441\u043f\u043e\u043c\u043e\u0433\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043a\u043b\u0430\u0441\u0441\u044b
+home=In\u00edcio
+version=Vers\u00e3o
+domain=Domain
+
+systemInfo=System Information
+hostname=Host Name
+processors=Available Processors
+os=Operating System
+jvm=JVM
+lang=pt
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_ru.properties
similarity index 60%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_ru.properties
index 3779b77..93a6ba3 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/BasePage_ru.properties
@@ -14,12 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+home=\u0413\u043b\u0430\u0432\u043d\u0430\u044f
+version=\u0412\u0435\u0440\u0441\u0438\u044f
+domain=\u0414\u043e\u043c\u0435\u043d
+systemInfo=\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0441\u0438\u0441\u0442\u0435\u043c\u0435
+hostname=\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430
+processors=\u041f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0440\u044b
+os=\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430
+jvm=JVM
+lang=ru
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard.html
similarity index 86%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard.html
index dfcda58..6f33488 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,9 +19,8 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
-    <div wicket:id="derSchemas"/>
-    <span wicket:id="membershipsDerSchemas">
-      <div wicket:id="membershipDerSchemas"/>
-    </span>
+    <section class="content" wicket:id="content">
+      <span wicket:id="userProfileInfo"></span>
+    </section>
   </wicket:extend>
-</html>
\ No newline at end of file
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard.properties
similarity index 92%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard.properties
index 56c00af..b9100c3 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=Overview
+control=Control
+accessTokens=Access Tokens
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_fr_CA.properties
similarity index 90%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_fr_CA.properties
index 56c00af..7cb6b9f 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_fr_CA.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=R\u00e9sum\u00e9
+control=Contr\u00f4le
+accessTokens=Jetons d'acc\u00e8s
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_it.properties
similarity index 91%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_it.properties
index 56c00af..efdaee4 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_it.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=Sommario
+control=Gestione
+accessTokens=Token di accesso
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_ja.properties
similarity index 85%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_ja.properties
index 56c00af..65401fd 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_ja.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=\u6982\u8981
+control=\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb
+accessTokens=\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_pt_BR.properties
similarity index 91%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_pt_BR.properties
index 56c00af..5823cf9 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_pt_BR.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=Resumo
+control=Gest\u00e3o
+accessTokens=Tokens de acesso
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_ru.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_ru.properties
index cf3ca73..23cad85 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Dashboard_ru.properties
@@ -14,12 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
-cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
+overview=\u041e\u0431\u0437\u043e\u0440
+# control=\u00d0\u00a3\u00d0\u00bf\u00d1\u0080\u00d0\u00b0\u00d0\u00b2\u00d0\u00bb\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5
+control=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435
+accessTokens=\u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword.html
similarity index 81%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword.html
index 8da9bd9..c0f9fe8 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,9 +19,10 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
-    <div wicket:id="plainSchemas"/>
-    <span wicket:id="membershipsPlainSchemas">
-      <div wicket:id="membershipPlainSchemas"/>
-    </span>
+    <section class="content" wicket:id="content">
+      <div class="box">
+        <span wicket:id="changePasswordPanel">[CHANGE_PASSWORD_PANEL]</span>
+      </div>
+    </section>
   </wicket:extend>
-</html>
\ No newline at end of file
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword.properties
index 3779b77..2be6939 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword.properties
@@ -14,12 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
+password=Password
+submit=Change
 cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+confirmPassword=Password (confirm)
+passwordNeedsToBeUpdated=Password needs to be updated
+self.pwd.change.success.msg=Password successfully changed
+self.pwd.change.error=Unable to complete the change password
+self.pwd.change.error.msg=Please contact an administrator
+self.pwd.change.success=Successfully changed
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_fr_CA.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_fr_CA.properties
new file mode 100644
index 0000000..eb20e46
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_fr_CA.properties
@@ -0,0 +1,24 @@
+#
+#  Copyright (C) 2020 Tirasa (info@tirasa.net)
+# 
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+#
+password=Mot de passe
+submit=Modifier
+cancel=Annuler
+confirmPassword=Confirmer le mot de passe
+passwordNeedsToBeUpdated=Le mot de passe doit \u00eatre mis \u00e0 jour
+self.pwd.change.success.msg=Mot de passe chang\u00e9 avec succ\u00e8s
+self.pwd.change.error=Impossible de terminer le changement de mot de passe
+self.pwd.change.error.msg=Veuillez contacter un administrateur
+self.pwd.change.success=Modifi\u00e9 avec succ\u00e8s
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_it.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_it.properties
index 5519bc4..0044ab1 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_it.properties
@@ -14,12 +14,12 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Domanda di sicurezza
-securityAnswer=Risposta di sicurezza
-reload=Ricarica
-not.loading=Non carica?
-submit=Salva
+password=Password
+submit=Cambia
 cancel=Annulla
-self.pwd.reset.success=La password \u00e8 stata resettata con successo
-domain=Dominio
+confirmPassword=Password (conferma)
+passwordNeedsToBeUpdated=La password deve essere aggiornata
+self.pwd.change.success.msg=Password aggiornata con successo
+self.pwd.change.error=Errore durante il cambio password
+self.pwd.change.error.msg=Si prega di contattare 
+self.pwd.change.success=Aggiornamento avvenuto con successo
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_ja.properties
new file mode 100644
index 0000000..9361a16
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_ja.properties
@@ -0,0 +1,25 @@
+# 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.
+password=\u30d1\u30b9\u30ef\u30fc\u30c9
+submit=\u5909\u66f4
+cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
+confirmPassword=\u30d1\u30b9\u30ef\u30fc\u30c9 (\u78ba\u8a8d)
+passwordNeedsToBeUpdated=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u66f4\u65b0\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059
+self.pwd.change.success.msg=\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u6b63\u5e38\u306b\u5909\u66f4\u3055\u308c\u307e\u3057\u305f
+self.pwd.change.error=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5909\u66f4\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
+self.pwd.change.error.msg=\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306e\u78ba\u8a8d\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
+self.pwd.change.success=\u6b63\u5e38\u306b\u5909\u66f4\u3055\u308c\u307e\u3057\u305f
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_pt_BR.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_pt_BR.properties
index cf3ca73..a54d215 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_pt_BR.properties
@@ -14,12 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
+password=Senha
+submit=Alterar
+confirmPassword=Senha (confirmar)
+passwordNeedsToBeUpdated=Senha precisa ser atualizado
+self.pwd.change.success.msg=Senha mudada com sucesso
+
+self.pwd.change.error=Incapaz de completar a mudan\u00e7a de senha
+self.pwd.change.error.msg=Incapaz de completar a mudan\u00e7a de senha
+self.pwd.change.success=Alterado com sucesso
 cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_ru.properties
new file mode 100644
index 0000000..dc396f8
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditChangePassword_ru.properties
@@ -0,0 +1,28 @@
+# 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.
+password=\u041f\u0430\u0440\u043e\u043b\u044c
+# submit=\u00d0\u0098\u00d0\u00b7\u00d0\u00bc\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d1\u0082\u00d1\u008c
+submit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c
+cancel=\u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
+#confirmPassword=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c (\u00d0\u00bf\u00d0\u00be\u00d0\u00b4\u00d1\u0082\u00d0\u00b2\u00d0\u00b5\u00d1\u0080\u00d0\u00b6\u00d0\u00b4\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5)
+confirmPassword=\u041f\u0430\u0440\u043e\u043b\u044c (\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435)
+# passwordNeedsToBeUpdated=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c \u00d0\u00b4\u00d0\u00be\u00d0\u00bb\u00d0\u00b6\u00d0\u00b5\u00d0\u00bd \u00d0\u00b1\u00d1\u008b\u00d1\u0082\u00d1\u008c \u00d0\u00b8\u00d0\u00b7\u00d0\u00bc\u00d0\u00b5\u00d0\u00bd\u00d1\u0091\u00d0\u00bd
+passwordNeedsToBeUpdated=\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0451\u043d
+self.pwd.change.success.msg=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d
+self.pwd.change.error=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0441\u043c\u0435\u043d\u0443 \u043f\u0430\u0440\u043e\u043b\u044f
+self.pwd.change.error.msg=\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c
+self.pwd.change.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.html
new file mode 100644
index 0000000..d4c0a0b
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.html
@@ -0,0 +1,64 @@
+<!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="content" wicket:id="content">
+      <div class="box">
+        <div class="row clearfix">
+          <div class="col-md-12">
+            <div class="col-md-6 mx-auto">
+              <!-- form start -->
+              <form wicket:id="securityQuestionForm" class="form-horizontal">
+                <div class="box-body">
+                  <div class="box-header formcard">
+                    <header class="card-container bg-danger">
+                      <label class="card-header-style">Security Question</label>
+                    </header>
+                    <div class="card-container-body">
+                      <div>
+                        <div class="col-xs-12">
+                          <div class="form-group">
+                            <span wicket:id="securityQuestion">[SECURITY QUESTION]</span>
+                          </div>
+                          <div class="form-group">
+                            <span wicket:id="securityAnswer">[SECURITY ANSWER]</span>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+
+                  <span wicket:id="captchaPanelCard"/>
+                </div>
+                <div class="box-footer">
+                  <input id="submit_button" wicket:id="submit" type="submit" wicket:message="value:submit" 
+                         class="btn btn-success float-right"/>
+                  <input id="cancel_button" wicket:id="cancel" type="submit" wicket:message="value:cancel" 
+                         class="btn btn-default float-left"/>
+                </div>
+              </form>
+              <!-- /.box-body -->
+            </div>
+          </div>
+        </div>
+      </div>
+    </section>
+  </wicket:extend>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.properties
similarity index 71%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.properties
index 3779b77..2a001b7 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion.properties
@@ -14,12 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
+password=Password
+submit=Change
 cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+self.securityquestion.change.success=Successfully changed
+self.securityquestion.change.error=Unable to change the security question
+self.securityquestion.change.error.msg=Please contact an administrator
+self.securityquestion.change.success.msg=Security question changed successfully
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_it.properties
similarity index 68%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_it.properties
index 5519bc4..deec620 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_it.properties
@@ -14,12 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Domanda di sicurezza
-securityAnswer=Risposta di sicurezza
-reload=Ricarica
-not.loading=Non carica?
-submit=Salva
+password=Password
+submit=Cambia
 cancel=Annulla
-self.pwd.reset.success=La password \u00e8 stata resettata con successo
-domain=Dominio
+self.securityquestion.change.success=Aggiornamento avvenuto con successo
+self.securityquestion.change.error=Errore durante il cambio della domanda di sicurezza
+self.securityquestion.change.error.msg=Si prega di contattare 
+self.securityquestion.change.success.msg=Aggiornamento della domanda di sicurezza avvenuto con successo
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_ja.properties
new file mode 100644
index 0000000..a5a6bcb
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_ja.properties
@@ -0,0 +1,23 @@
+# 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.
+password=\u30d1\u30b9\u30ef\u30fc\u30c9
+submit=\u5909\u66f4
+cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
+self.securityquestion.change.success=\u6b63\u5e38\u306b\u5909\u66f4\u3055\u308c\u307e\u3057\u305f
+self.securityquestion.change.error=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5909\u66f4\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
+self.securityquestion.change.error.msg=\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306e\u78ba\u8a8d\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
+self.securityquestion.change.success.msg=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u306e\u8cea\u554f\u304c\u6b63\u5e38\u306b\u5909\u66f4\u3055\u308c\u307e\u3057\u305f
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_pt_BR.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_pt_BR.properties
index cf3ca73..7ded2eb 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_pt_BR.properties
@@ -14,12 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
-cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
+password=Senha
+submit=Alterar
+self.securityquestion.change.success=Alterado com sucesso
+
+self.securityquestion.change.error=Incapaz de completar a mudan\u00e7a de senha
+self.securityquestion.change.error.msg=Incapaz de completar a mudan\u00e7a de senha
+self.securityquestion.change.success.msg=Pergunta de seguran\u00e7a alterada com sucesso
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_ru.properties
new file mode 100644
index 0000000..616ccfa
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditSecurityQuestion_ru.properties
@@ -0,0 +1,24 @@
+# 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.
+password=\u041f\u0430\u0440\u043e\u043b\u044c
+# submit=\u00d0\u0098\u00d0\u00b7\u00d0\u00bc\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d1\u0082\u00d1\u008c
+submit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c
+cancel=\u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
+self.securityquestion.change.success=\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043e
+self.securityquestion.change.error=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0441\u043c\u0435\u043d\u0443 \u043f\u0430\u0440\u043e\u043b\u044f
+self.securityquestion.change.error.msg=\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c
+self.securityquestion.change.success.msg=\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u044c\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser.html
similarity index 72%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser.html
index 8da9bd9..1a47856 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,9 +19,15 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
-    <div wicket:id="plainSchemas"/>
-    <span wicket:id="membershipsPlainSchemas">
-      <div wicket:id="membershipPlainSchemas"/>
-    </span>
+    <section class="content" wicket:id="content">
+      <div class="row clearfix">
+        <!-- left column -->
+        <div class="col-md-12"> 
+          <div class="col-md-12 column box">
+            <span wicket:id="editUserPanel"></span>
+          </div>
+        </div>
+      </div><!-- </div> -->
+    </section>
   </wicket:extend>
-</html>
\ No newline at end of file
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser.properties
similarity index 84%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser.properties
index 56c00af..633e2c5 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser.properties
@@ -14,4 +14,10 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=Overview
+control=Control
+accessTokens=Access Tokens
+palette.available=Available
+palette.selected=Selected
+submit=Change
+cancel=Cancel
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_fr_CA.properties
similarity index 91%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_fr_CA.properties
index 56c00af..233a893 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_fr_CA.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=R�sum�
+control=Contr�le
+accessTokens=Jetons d'acc�s
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_it.properties
similarity index 91%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_it.properties
index 56c00af..efdaee4 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_it.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=Sommario
+control=Gestione
+accessTokens=Token di accesso
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_ja.properties
similarity index 85%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_ja.properties
index 56c00af..65401fd 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_ja.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=\u6982\u8981
+control=\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb
+accessTokens=\u30a2\u30af\u30bb\u30b9\u30c8\u30fc\u30af\u30f3
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_pt_BR.properties
similarity index 91%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_pt_BR.properties
index 56c00af..5823cf9 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_pt_BR.properties
@@ -14,4 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+overview=Resumo
+control=Gest\u00e3o
+accessTokens=Tokens de acesso
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_ru.properties
similarity index 69%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_ru.properties
index cf3ca73..23cad85 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/EditUser_ru.properties
@@ -14,12 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
-cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
+overview=\u041e\u0431\u0437\u043e\u0440
+# control=\u00d0\u00a3\u00d0\u00bf\u00d1\u0080\u00d0\u00b0\u00d0\u00b2\u00d0\u00bb\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5
+control=\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435
+accessTokens=\u0442\u043e\u043a\u0435\u043d\u043e\u0432 \u0434\u043e\u0441\u0442\u0443\u043f\u0430
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html
index abafe9e..55c8ab6 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -32,13 +33,14 @@
 
     <link href="ui-commons/css/login.css" rel="stylesheet" type="text/css" />
     <link href="ui-commons/css/syncopeUI.css" rel="stylesheet" type="text/css"/>
+    <link href="css/syncopeEnduser.css" rel="stylesheet" type="text/css"/>
 
     <!-- accessibility -->
     <link href="ui-commons/css/accessibility.css" rel="stylesheet" type="text/css" />
     <script type="text/javascript" src="ui-commons/js/accessibility.js"></script>
   </head>
 
-  <body class="login-body login_enduser">
+  <body class="login-body">
     <div id="accessibility">
       <div id="change_contrast" class="btn-accessibility">
         <i aria-label="Toggle high contrast colors mode" tabindex="0" accesskey="H"
@@ -51,13 +53,12 @@
         </i>
       </div>
     </div>
-
     <div class="container">
       <div class="login-card card card-container">
         <img class="login-logo" src="ui-commons/img/logo-green.png" />
 
         <span wicket:id="feedback" role="alert"/>
-        <div class="box alert-danger" style="padding: 5px;text-align: center;" wicket:id="exceptionMessage"/>
+        <div class="alert-danger" style="padding: 5px;text-align: center;" wicket:id="exceptionMessage"/>
 
         <form class="form-signin" wicket:id="login">
           <fieldset class="form-group">
@@ -75,7 +76,7 @@
               <select class="form-control select2bs4 select2-hidden-accessible" wicket:id="domain"/>
             </div>
 
-            <button wicket:id="submit" type="submit" class="btn btn-lg btn-primary btn-block btn-signin"></button>
+            <button wicket:id="submit" type="submit" class="btn btn-lg btn-primary btn-block btn-signin bg-danger"></button>
           </fieldset>
 
           <div wicket:id="ssoLogins">
@@ -84,11 +85,9 @@
         </form>
 
         <div class="text-center login_link">
+          <a wicket:id="self-pwd-reset"><wicket:message key="self-pwd-reset">[SELFPWDRESET]</wicket:message></a><br/>
           <a wicket:id="self-registration"><wicket:message key="self-registration">[SELFREGISTRATION]</wicket:message></a>
         </div>
-        <div class="text-center login_link">
-          <a wicket:id="self-pwd-reset"><wicket:message key="self-pwd-reset">[SELFPWDRESET]</wicket:message></a>
-        </div>
       </div>
     </div>
   </body>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login_ru.properties
index 46f2f2f..151e3f1 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Login_ru.properties
@@ -14,7 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-# username=\u00d0\u0098\u00d0\u00bc\u00d1\u008f \u00d0\u00bf\u00d0\u00be\u00d0\u00bb\u00d1\u008c\u00d0\u00b7\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d1\u0082\u00d0\u00b5\u00d0\u00bb\u00d1\u008f
 username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f
 # password=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c
 password=\u041f\u0430\u0440\u043e\u043b\u044c
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.html
index 7f68e1a..905836c 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.html
@@ -17,87 +17,18 @@
 specific language governing permissions and limitations
 under the License.
 -->
-<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org" class="max_height">
-  <head>
-    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
-    <meta charset="UTF-8"/>
-    <meta content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no' name='viewport'/>
-
-    <title>Apache Syncope - Change password</title>
-
-    <link rel="shortcut icon" href="ui-commons/img/favicon.png" type="image/png"/>
-
-    <link href="webjars/font-awesome/${font-awesome.version}/css/all.min.css" rel="stylesheet" type="text/css" />
-    <link href="webjars/ionicons/${ionicons.version}/css/ionicons.min.css" rel="stylesheet" type="text/css" />
-    <link href="ui-commons/css/fonts.css" rel="stylesheet" type="text/css"/>
-
-    <link href="ui-commons/css/syncopeUI.css" rel="stylesheet" type="text/css"/>
-
-    <!-- accessibility -->
-    <link href="ui-commons/css/accessibility.css" rel="stylesheet" type="text/css" />
-    <script type="text/javascript" src="ui-commons/js/accessibility.js"></script>
-  </head>
-
-  <body class="max_height">
-    <div id="accessibility">
-      <div id="change_contrast" class="btn-accessibility">
-        <i aria-label="Toggle high contrast colors mode" tabindex="0" accesskey="H"
-           class="fa fa-adjust">
-        </i>
-      </div>
-      <div id="change_fontSize" class="btn-accessibility">
-        <i aria-label="Toggle font size increment" tabindex="1" accesskey="F"
-           class="fa fa-font">
-        </i>
-      </div>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:extend>
+    <div class="brand-link bg-red brand-custom">
+      <span class="logo-mini">
+        <img aria-label="Apache Syncope Enduser" alt="Apache Syncope Enduser" src="ui-commons/img/logo-mini.png"/>
+      </span>
+      <span class="brand-text font-weight-light">Apache Syncope</span>
     </div>
-
-    <div id="mcp_wrapper" class="wrapper">
-      <div class="content-wrapper">
-        <div class="card card-container vertical_align">
-
-          <div class="custom_content_wrapper">
-            <div class="col-xs-12">
-              <img id="mcp_logo" class="login-logo" src="ui-commons/img/logo-green.png" />
-
-              <span wicket:id="feedback"></span>
-
-              <div class="alert alert-success"><wicket:message key="passwordNeedsToBeUpdated"/></div>
-            </div>
-
-            <div class="col-xs-12">
-
-              <div id="mcp_form" class="container-fluid password_reset_wrapper">
-                <div class="row">
-                  <div class="col-md-6 col-md-offset-3">
-                    <form wicket:id="changePassword" class="form-horizontal">
-                      <fieldset class="enduser_fieldset">
-                        <div class="form-group input-md">
-                          <input type="text" wicket:id="username" id="username" class="form-control" 
-                                 wicket:message="placeholder:username" required="required" autofocus="autofocus" />
-                        </div>
-                        <span id="password" wicket:id="password" class="form-group input-md"/>
-                        <span id="confirmPassword" wicket:id="confirmPassword" class="form-group input-md"/>
-
-                        <div class="password_reset_buttons form-group">
-                          <div class="col-md-6">
-                            <input id="cancel_button" wicket:id="cancel" type="submit" wicket:message="value:cancel" 
-                                   class="btn btn-default float-left"/>
-                          </div>
-                          <div class="col-md-6">
-                            <input id="submit_button" wicket:id="submit" type="submit" wicket:message="value:submit" 
-                                   class="btn btn-primary float-right"/>
-                          </div>
-                        </div>
-                      </fieldset>
-                    </form>
-                  </div>
-                </div>
-              </div>
-            </div>
-          </div>
-        </div>
+    <section class="content" wicket:id="content">
+      <div class="box">
+        <span wicket:id="changePasswordPanel">[CHANGE_PASSWORD_PANEL]</span>
       </div>
-    </div>
-  </body>
+    </section>
+  </wicket:extend>
 </html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.properties
index f69fef9..899a97c 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword.properties
@@ -21,3 +21,6 @@
 confirmPassword=Password (confirm)
 passwordNeedsToBeUpdated=Password needs to be updated
 self.pwd.change.success=Password successfully changed
+Cancel=Cancel
+self.pwd.change.error=Unable to complete the change password
+self.pwd.change.error.msg=Please contact an administrator
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_it.properties
index 0d65dc4..caa926b 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_it.properties
@@ -22,3 +22,6 @@
 passwordNeedsToBeUpdated=La password deve essere aggiornata
 self.pwd.change.success=Password aggiornata con successo
 
+Cancel=Annulla
+self.pwd.change.error=Errore durante il cambio password
+self.pwd.change.error.msg=Si prega di contattare 
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ja.properties
index 34bb07c..ea0d5bf 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ja.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ja.properties
@@ -22,3 +22,6 @@
 passwordNeedsToBeUpdated=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u66f4\u65b0\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059
 self.pwd.change.success=\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u6b63\u5e38\u306b\u5909\u66f4\u3055\u308c\u307e\u3057\u305f
 
+Cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
+self.pwd.change.error=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u5909\u66f4\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
+self.pwd.change.error.msg=\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306e\u78ba\u8a8d\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_pt_BR.properties
index 3390349..f5e4aba 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_pt_BR.properties
@@ -22,3 +22,5 @@
 passwordNeedsToBeUpdated=Senha precisa ser atualizado
 self.pwd.change.success=Senha mudada com sucesso
 
+self.pwd.change.error=Incapaz de completar a mudan\u00e7a de senha
+self.pwd.change.error.msg=Incapaz de completar a mudan\u00e7a de senha
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ru.properties
index e2c504c..1d7af16 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/MustChangePassword_ru.properties
@@ -14,8 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
-# username=\u00d0\u0098\u00d0\u00bc\u00d1\u008f \u00d0\u00bf\u00d0\u00be\u00d0\u00bb\u00d1\u008c\u00d0\u00b7\u00d0\u00be\u00d0\u00b2\u00d0\u00b0\u00d1\u0082\u00d0\u00b5\u00d0\u00bb\u00d1\u008f
 username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f
 # password=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c
 password=\u041f\u0430\u0440\u043e\u043b\u044c
@@ -28,3 +26,6 @@
 passwordNeedsToBeUpdated=\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0451\u043d
 self.pwd.change.success=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d
 
+Cancel=\u041e\u0442\u043c\u0435\u043d\u0430
+self.pwd.change.error=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u0441\u043c\u0435\u043d\u0443 \u043f\u0430\u0440\u043e\u043b\u044f
+self.pwd.change.error.msg=\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Self.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Self.html
index 6124f0c..c16e43f 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Self.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Self.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.html
index 1a6f8a8..9ec6f04 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,43 +19,38 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
-
+    <div class="brand-link bg-red brand-custom">
+      <span class="logo-mini">
+        <img aria-label="Apache Syncope Enduser" alt="Apache Syncope Enduser" src="ui-commons/img/logo-mini.png"/>
+      </span>
+      <span class="brand-text font-weight-light">Apache Syncope</span>
+    </div>
     <section class="content" wicket:id="content">
-      <div id="confirm_password_reset" class="container-fluid password_reset_wrapper">
+      <div class="box">
+        <div id="confirm_password_reset" class="container-fluid password_reset_wrapper">
 
-        <div class="row">
-          <div class="col-md-6 col-md-offset-3">
-
+          <div class="col-md-6 mx-auto">
             <div class="page-header">
               <h1>
                 <wicket:message key="confirm-reset">[CONFIRM_PASSWORD_RESET]</wicket:message>
               </h1>
             </div>
 
-            <form wicket:id="selfConfirmPwdResetForm" class="form-horizontal">
-              <fieldset class="enduser_fieldset">
-                <select wicket:id="domain" class="form-group input-md"/>
-                <span id="password" wicket:id="password" class="form-group input-md"/>
-                <span id="confirmPassword" wicket:id="confirmPassword" class="form-group input-md"/>
+            <form wicket:id="selfConfirmPwdResetForm">
+              <div class="box-body">
+                <span wicket:id="selfConfirmPasswordResetPanelCard">[CONFIRM_PASSWORD_RESET]</span>
+              </div>
 
-                <div class="password_reset_buttons form-group">
-                  <div class="col-md-6">
-                    <input id="cancel_button" wicket:id="cancel" type="submit" wicket:message="value:cancel" 
-                           class="btn btn-default float-left"/>
-                  </div>
-                  <div class="col-md-6">
-                    <input id="submit_button" wicket:id="submit" type="submit" wicket:message="value:submit" 
-                           class="btn btn-primary float-right"/>
-                  </div>
-                </div>
-              </fieldset>
+              <div class="box-footer">
+                <input id="cancel_button" wicket:id="cancel" type="submit" wicket:message="value:cancel"
+                       class="btn btn-default float-left"/>
+                <input id="submit_button" wicket:id="submit" type="submit" wicket:message="value:submit"
+                       class="btn btn-primary float-right"/>
+              </div>
             </form>
-
           </div> <!-- col -->
-        </div> <!-- row -->
-
+        </div>
       </div>
     </section>
-
   </wicket:extend>
 </html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.properties
index 8d7d59c..05f050b 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset.properties
@@ -20,5 +20,9 @@
 password-strength=Password strength:
 cancel=Cancel
 submit=Submit
-self.confirm.pwd.reset.success=Password successfully reset
+self.confirm.pwd.reset.success=Password Changed!
+self.confirm.pwd.reset.success.msg=Password successfully changed
 self.confirm.pwd.reset.error.empty=No token was specified in the url, cannot access to the requested page
+
+self.confirm.pwd.reset.error=Unable to complete the Password Reset
+self.confirm.pwd.reset.error.msg=Please contact an administrator
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_it.properties
index e5fffa1..c924560 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_it.properties
@@ -20,5 +20,9 @@
 password-strength=Sicurezza della password:
 cancel=Annulla
 submit=Invia
-self.confirm.pwd.reset.success=Password resettata con successo
+self.confirm.pwd.reset.success=Password \u00e8 stata modificata
+self.confirm.pwd.reset.success.msg=Cambio password eseguito con successo
 self.confirm.pwd.reset.error.empty=Nessun token \u00e8 specifico nell'url, non \u00e8 possibile accedere alla pagina richiesta
+
+self.confirm.pwd.reset.error=Errore durante il processo di reset password
+self.confirm.pwd.reset.error.msg=Si prega di contattare 
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ja.properties
index 4a2e3a3..7703a33 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ja.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ja.properties
@@ -22,3 +22,6 @@
 submit=\u63d0\u51fa\u3059\u308b
 self.confirm.pwd.reset.success=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f
 self.confirm.pwd.reset.error.empty=URL\u306b\u30c8\u30fc\u30af\u30f3\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u8981\u6c42\u3055\u308c\u305f\u30da\u30fc\u30b8\u306b\u30a2\u30af\u30bb\u30b9\u3067\u304d\u307e\u305b\u3093
+self.confirm.pwd.reset.success.msg=Password Changed!
+self.confirm.pwd.reset.error=\u30d1\u30b9\u30ef\u30fc\u30c9\u30ea\u30bb\u30c3\u30c8\u306e\u78ba\u8a8d\u3092\u5b8c\u4e86\u3067\u304d\u307e\u305b\u3093
+self.confirm.pwd.reset.error.msg=\u7ba1\u7406\u8005\u306b\u9023\u7d61\u3057\u3066\u304f\u3060\u3055\u3044
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_pt_BR.properties
index da4c530..88202f9 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_pt_BR.properties
@@ -21,4 +21,7 @@
 cancel=Cancelar
 submit=Enviar
 self.confirm.pwd.reset.success=Senha redefinida com sucesso
+self.confirm.pwd.reset.success.msg=Senha redefinida com sucesso
 self.confirm.pwd.reset.error.empty=Nenhum token foi especificado na URL, n\u00e3o pode acessar a p\u00e1gina solicitada
+self.confirm.pwd.reset.error=Incapaz de completar a redefini\u00e7\u00e3o de senha
+self.confirm.pwd.reset.error.msg=Incapaz de completar a mudan\u00e7a de senha
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ru.properties
index 03d5fc4..a1445b8 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfConfirmPasswordReset_ru.properties
@@ -20,5 +20,9 @@
 password-strength=\u041d\u0430\u0434\u0435\u0436\u043d\u043e\u0441\u0442\u044c \u041f\u0430\u0440\u043e\u043b\u044f:
 cancel=\u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
 submit=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c
-self.confirm.pwd.reset.success==\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0431\u0440\u043e\u0448\u0435\u043d
+self.confirm.pwd.reset.success=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0431\u0440\u043e\u0448\u0435\u043d
+self.confirm.pwd.reset.success.msg=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0431\u0440\u043e\u0448\u0435\u043d
 self.confirm.pwd.reset.error.empty=\u0412 URL \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d \u0442\u043e\u043a\u0435\u043d, \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435
+
+self.confirm.pwd.reset.error=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0441\u0431\u0440\u043e\u0441\u0430 \u043f\u0430\u0440\u043e\u043b\u044f
+self.confirm.pwd.reset.error.msg=\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0441\u0432\u044f\u0436\u0438\u0442\u0435\u0441\u044c \u0441 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440\u043e\u043c
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel.html
similarity index 66%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel.html
index 04d76a6..4b11cb8 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel.html
@@ -21,10 +21,6 @@
   <wicket:panel>
 
     <fieldset class="enduser_fieldset">
-      <div class="form-group input-md">
-        <label for="domain"><wicket:message key="domain"/></label>
-        <select wicket:id="domain" class="form-control"/>
-      </div>
 
       <div class="form-group input-md">
         <label for="username"><wicket:message key="username"/></label>
@@ -33,15 +29,16 @@
                autofocus="autofocus" />
       </div>
       <div class="form-group input-md">
-        <label for="securityQuestion"><wicket:message key="securityQuestion"/></label>
+        <label for="securityQuestion" wicket:id="securityQuestionLabel">[LABEL]</label>
         <input id="securityQuestion" type="text" wicket:id="securityQuestion" class="form-control" 
                wicket:message="placeholder:securityQuestion" 
                autofocus="autofocus" />
-        <div class="suggestions">(<wicket:message key="not.loading">
-          </wicket:message><a wicket:id="reloadLink"> <wicket:message key="reload"></wicket:message></a>)</div>
+        <div class="suggestions"><label wicket:id="not.loading">([LABEL])</label>
+          <a wicket:id="reloadLink"> <wicket:message key="reload"></wicket:message></a>
+        </div>
       </div>
       <div class="form-group input-md">
-        <label for="securityAnswer"><wicket:message key="securityAnswer"/></label>
+        <label for="securityAnswer" wicket:id="securityAnswerLabel">[LABEL]</label>
         <input id="securityAnswer" type="text" wicket:id="securityAnswer" class="form-control" 
                wicket:message="placeholder:securityAnswer" 
                autofocus="autofocus" />
@@ -50,16 +47,6 @@
         <span wicket:id="captchaPanel">[CAPTCHA]</span>
       </div>
 
-      <div class="password_reset_buttons form-group">
-        <div class="col-md-6">
-          <input wicket:id="cancel" type="submit" wicket:message="value:cancel" 
-                 class="btn btn-default float-left"/>
-        </div>
-        <div class="col-md-6">
-          <input wicket:id="submit" type="submit" wicket:message="value:submit" 
-                 class="btn btn-success float-right"/>
-        </div>
-      </div>
     </fieldset>
 
   </wicket:panel>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel.properties
similarity index 93%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel.properties
index 3779b77..73ca456 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel.properties
@@ -21,5 +21,3 @@
 not.loading=Not loading?
 submit=Submit
 cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_fr_CA.properties
similarity index 92%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_fr_CA.properties
index 3779b77..73ca456 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_fr_CA.properties
@@ -21,5 +21,3 @@
 not.loading=Not loading?
 submit=Submit
 cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_it.properties
similarity index 91%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_it.properties
index 5519bc4..5cc8c26 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_it.properties
@@ -21,5 +21,3 @@
 not.loading=Non carica?
 submit=Salva
 cancel=Annulla
-self.pwd.reset.success=La password \u00e8 stata resettata con successo
-domain=Dominio
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_ja.properties
similarity index 88%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_ja.properties
index 1e7f25d..54ca2b3 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ja.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_ja.properties
@@ -21,5 +21,3 @@
 not.loading=\u30ed\u30fc\u30c9\u3057\u3066\u3044\u307e\u305b\u3093\u304b\uff1f
 submit=\u63d0\u51fa\u3059\u308b
 cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
-self.pwd.reset.success=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f
-domain=\u30c9\u30e1\u30a4\u30f3
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_pt_BR.properties
similarity index 93%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_pt_BR.properties
index cf3ca73..0a1b647 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_pt_BR.properties
@@ -21,5 +21,3 @@
 not.loading=N\u00e3o est\u00e1 carregando?
 submit=Enviar
 cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_ru.properties
similarity index 89%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_ru.properties
index 4156330..9287f49 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset$SelfPwdResetPanel_ru.properties
@@ -21,5 +21,3 @@
 not.loading=\u041d\u0435 \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442\u0441\u044f?
 submit=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c
 cancel=\u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
-self.pwd.reset.success=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0431\u0440\u043e\u0448\u0435\u043d
-domain=Domain
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.html
index 4442e79..2e5c7a2 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,12 +19,17 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
-
+    <div class="brand-link bg-red brand-custom">
+      <span class="logo-mini">
+        <img aria-label="Apache Syncope Enduser" alt="Apache Syncope Enduser" src="ui-commons/img/logo-mini.png"/>
+      </span>
+      <span class="brand-text font-weight-light">Apache Syncope</span>
+    </div>
     <section class="content" wicket:id="content">
-      <div id="password_reset" class="container-fluid password_reset_wrapper">
+      <div class="box">
+        <div id="password_reset" class="container-fluid password_reset_wrapper">
 
-        <div class="row">
-          <div class="col-md-6 col-md-offset-3">
+          <div class="col-md-6 mx-auto">
 
             <div class="page-header">
               <h1>
@@ -31,14 +37,21 @@
               </h1>
             </div>
 
-            <form wicket:id="selfPwdResetForm" class="form-horizontal">
-              <div wicket:id="selfPwdResetPanel"/>
-            </form>
+            <form wicket:id="selfPwdResetForm">
+              <div class="box-body">
+                <span wicket:id="selfPasswordResetPanelCard">[PASSWORD_RESET]</span>
+              </div>
 
+              <div class="password_reset_buttons form-group">
+                <input wicket:id="cancel" type="submit" wicket:message="value:cancel"
+                       class="btn btn-default float-left"/>
+                <input wicket:id="submit" type="submit" wicket:message="value:submit"
+                       class="btn btn-success float-right"/>
+              </div>
+            </form>
           </div> <!-- col -->
-        </div> <!-- row -->
+        </div>
       </div>
     </section>
-
   </wicket:extend>
-</html>
\ No newline at end of file
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.properties
index e76af19..3c4818e 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset.properties
@@ -15,3 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 password-reset=Password reset
+self.pwd.reset.success=Your password has been reset successfully!
+self.pwd.reset.success.msg=An email has been sent to your address.
+self.pwd.reset.error=Error during password reset!
+self.pwd.reset.error.msg=Try again or contact an administrator.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_fr_CA.properties
similarity index 73%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_fr_CA.properties
index cf3ca73..3c4818e 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_fr_CA.properties
@@ -14,12 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
-cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
+password-reset=Password reset
+self.pwd.reset.success=Your password has been reset successfully!
+self.pwd.reset.success.msg=An email has been sent to your address.
+self.pwd.reset.error=Error during password reset!
+self.pwd.reset.error.msg=Try again or contact an administrator.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_it.properties
index 689e50a..c8a9683 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_it.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_it.properties
@@ -15,3 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 password-reset=Reset della password
+self.pwd.reset.success=La password \u00e8 stata resettata con successo
+self.pwd.reset.success.msg=Una email \u00e8 stata inviata all'indirizzo configurato
+self.pwd.reset.error=Error during password reset!
+self.pwd.reset.error.msg=Try again or contact an administrator.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ja.properties
index ddf5e73..791121e 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ja.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ja.properties
@@ -15,3 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 password-reset=\u30d1\u30b9\u30ef\u30fc\u30c9\u306e\u30ea\u30bb\u30c3\u30c8
+self.pwd.reset.success=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f
+self.pwd.reset.success.msg=An email has been sent to your address.
+self.pwd.reset.error=Error during password reset!
+self.pwd.reset.error.msg=Try again or contact an administrator.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_pt_BR.properties
index 2163279..7195575 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_pt_BR.properties
@@ -15,3 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 password-reset=Resetar a senha
+self.pwd.reset.success=Senha redefinida com sucesso
+self.pwd.reset.success.msg=An email has been sent to your address.
+self.pwd.reset.error=Error during password reset!
+self.pwd.reset.error.msg=Try again or contact an administrator.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ru.properties
index 2d8ab11..2bc9d14 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfPasswordReset_ru.properties
@@ -15,3 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 password-reset=\u0412\u043e\u0441\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f
+self.pwd.reset.success=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0431\u0440\u043e\u0448\u0435\u043d
+self.pwd.reset.success.msg=An email has been sent to your address.
+self.pwd.reset.error=Error during password reset!
+self.pwd.reset.error.msg=Try again or contact an administrator.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfRegistration.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfRegistration.html
new file mode 100644
index 0000000..2002cca
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfRegistration.html
@@ -0,0 +1,40 @@
+<!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>
+    <div class="brand-link bg-red brand-custom">
+      <span class="logo-mini">
+        <img aria-label="Apache Syncope Enduser" alt="Apache Syncope Enduser" src="ui-commons/img/logo-mini.png"/>
+      </span>
+      <span class="brand-text font-weight-light">Apache Syncope</span>
+    </div>
+    <section class="content" wicket:id="content">
+      <div class="row clearfix">
+        <!-- left column -->
+        <div class="col-md-12"> 
+          <div class="col-md-12 column box">
+            <span wicket:id="selfRegistrationPanel"></span>
+          </div>
+        </div>
+      </div>
+    </section>
+  </wicket:extend>
+</html>
+X
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult.html
new file mode 100644
index 0000000..19e5d56
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult.html
@@ -0,0 +1,74 @@
+<!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>
+    <div class="brand-link bg-red brand-custom">
+      <span class="logo-mini">
+        <img aria-label="Apache Syncope Enduser" alt="Apache Syncope Enduser" src="ui-commons/img/logo-mini.png"/>
+      </span>
+      <span class="brand-text font-weight-light">Apache Syncope</span>
+    </div>
+    <section class="content" wicket:id="content">
+      <div class="box">
+        <div id="password_reset" class="container-fluid password_reset_wrapper">
+
+          <div class="row ">
+            <div class="col-md-6 mx-auto">
+
+              <div class="page-header">
+                <h1>
+                  <span wicket:id="resultTitle">[LABEL]</span>
+                </h1>
+
+              </div>
+              <fieldset class="enduser_fieldset">
+
+                <div class="form-group input-md">
+                  <span wicket:id="resultMessage">[LABEL]</span>
+                  <a wicket:id="login">
+                    <wicket:message key="login">[login]</wicket:message>
+                  </a>
+                </div>
+
+              </fieldset>
+
+              <span wicket:id="statusIcon"></span>
+              <wicket:fragment wicket:id="successIcon">
+                <svg class="checkmark_ok" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
+                  <circle class="checkmark_circle_ok" cx="26" cy="26" r="25" fill="none"/>
+                  <path class="checkmark_check_ok" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
+                </svg>
+              </wicket:fragment>
+              <wicket:fragment wicket:id="errorIcon">
+                <svg class="checkmark_error" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
+                  <circle class="checkmark_circle_error" cx="26" cy="26" r="25" fill="none"/>
+                  <path class="checkmark_check_error" fill="none" d="M16 16 36 36 M36 16 16 36"/>
+                </svg>
+              </wicket:fragment>
+
+
+            </div>
+          </div> <!-- col -->
+        </div> <!-- row -->
+      </div>
+    </section>
+
+  </wicket:extend>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult.properties
similarity index 94%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult.properties
index 56c00af..bcdb339 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult.properties
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+login=Back to Home
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_fr_CA.properties
similarity index 94%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_fr_CA.properties
index 56c00af..bcdb339 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_fr_CA.properties
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+login=Back to Home
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_it.properties
similarity index 94%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_it.properties
index 56c00af..709cc6f 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_it.properties
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+login=Torna alla Home
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_ja.properties
similarity index 94%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_ja.properties
index 56c00af..bcdb339 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_ja.properties
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+login=Back to Home
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_pt_BR.properties
similarity index 94%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_pt_BR.properties
index 56c00af..bcdb339 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_pt_BR.properties
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+login=Back to Home
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_ru.properties
similarity index 94%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_ru.properties
index 56c00af..bcdb339 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfResult_ru.properties
@@ -14,4 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+login=Back to Home
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfUpdate.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfUpdate.html
index 629e25f..40638bc 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfUpdate.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/SelfUpdate.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/WizardMgtPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/WizardMgtPanel.html
deleted file mode 100644
index c62d0d0..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/pages/WizardMgtPanel.html
+++ /dev/null
@@ -1,60 +0,0 @@
-<!--
-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:panel>
-    <span wicket:id="container">
-      <wicket:container wicket:id="content"/>
-    </span>
-
-    <wicket:fragment wicket:id="wizard">
-      <div class="col-xs-12">
-        <div class="box">
-          <div class="box-header">
-            <h3 class="box-title">
-              <span wicket:id="title">[TITLE]</span>
-            </h3>
-          </div>
-          <div class="box-body">
-            <span wicket:id="wizard"/>
-          </div>
-        </div>
-      </div>
-    </wicket:fragment>
-
-    <wicket:fragment wicket:id="default">
-
-      <wicket:child/>
-
-      <wicket:enclosure child="add">
-        <div class="modal-footer">
-          <a href="#" class="btn btn-default btn-circle btn-lg float-left" wicket:id="exit">
-            <i class="fas fa-sign-out-alt"></i>
-          </a>
-          <a href="#" class="btn btn-success btn-circle btn-lg" wicket:id="add">
-            <i class="fa fa-plus"></i>
-          </a>
-        </div>
-      </wicket:enclosure>
-    </wicket:fragment>
-
-    <span wicket:id="outerObjectsRepeater">
-      <div wicket:id="outer"/>
-    </span>
-  </wicket:panel>
-</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.html
new file mode 100644
index 0000000..a31ec8b
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.html
@@ -0,0 +1,61 @@
+<!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:panel>
+    <div class="row clearfix">
+      <!-- left column -->
+      <div class="col-md-12">
+        <div class="col-md-6 mx-auto">
+          <!-- form start -->
+          <form wicket:id="changePassword" class="form-horizontal">
+            <div class="box-body">
+              <div class="box-header formcard">
+                <header class="card-container bg-danger">
+                  <label class="card-header-style"><wicket:message key="editchangepassword"/></label>
+                </header>
+                <div class="card-container-body">
+                  <div>
+                    <div class="col-xs-12">
+                      <div class="form-group">
+                        <span id="password" wicket:id="password"/>
+                      </div>
+                      <div class="form-group">
+                        <span id="confirmPassword" wicket:id="confirmPassword"/>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <span wicket:id="captchaPanelCard"/>
+            </div>
+            <div class="box-footer">
+              <input id="submit_button" wicket:id="submit" type="submit" wicket:message="value:submit" 
+                     class="btn btn-success float-right"/>
+              <input id="cancel_button" wicket:id="cancel" type="submit" wicket:message="value:cancel" 
+                     class="btn btn-default float-left"/>
+            </div>
+          </form>
+          <!-- /.box-body -->
+        </div>
+      </div>
+    </div><!-- </div> -->
+  </wicket:panel>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.properties
similarity index 79%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.properties
index 3779b77..f2447ea 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel.properties
@@ -15,11 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
+password=Password
+submit=Change
 cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+confirmPassword=Password (confirm)
+passwordNeedsToBeUpdated=Password needs to be updated
+self.pwd.change.success=Password successfully changed
+editchangepassword=Change password
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_it.properties
similarity index 79%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_it.properties
index 3779b77..0d65dc4 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_it.properties
@@ -15,11 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+password=Password
+submit=Cambia
+cancel=Annulla
+confirmPassword=Password (conferma)
+passwordNeedsToBeUpdated=La password deve essere aggiornata
+self.pwd.change.success=Password aggiornata con successo
+
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_ja.properties
similarity index 68%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ja.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_ja.properties
index 1e7f25d..34bb07c 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_ja.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_ja.properties
@@ -15,11 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 username=\u30e6\u30fc\u30b6\u30fc\u540d
-securityQuestion=\u79d8\u5bc6\u306e\u8cea\u554f
-securityAnswer=\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u56de\u7b54
-reload=\u30ea\u30ed\u30fc\u30c9
-not.loading=\u30ed\u30fc\u30c9\u3057\u3066\u3044\u307e\u305b\u3093\u304b\uff1f
-submit=\u63d0\u51fa\u3059\u308b
+password=\u30d1\u30b9\u30ef\u30fc\u30c9
+submit=\u5909\u66f4
 cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
-self.pwd.reset.success=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u30ea\u30bb\u30c3\u30c8\u3057\u307e\u3057\u305f
-domain=\u30c9\u30e1\u30a4\u30f3
+confirmPassword=\u30d1\u30b9\u30ef\u30fc\u30c9 (\u78ba\u8a8d)
+passwordNeedsToBeUpdated=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u66f4\u65b0\u3059\u308b\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059
+self.pwd.change.success=\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u6b63\u5e38\u306b\u5909\u66f4\u3055\u308c\u307e\u3057\u305f
+
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_pt_BR.properties
similarity index 78%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_pt_BR.properties
index 3779b77..3390349 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_pt_BR.properties
@@ -14,12 +14,11 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+username=Usu\u00e1rio
+password=Senha
+submit=Alterar
+Cancel=Cancelar
+confirmPassword=Senha (confirmar)
+passwordNeedsToBeUpdated=Senha precisa ser atualizado
+self.pwd.change.success=Senha mudada com sucesso
+
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_ru.properties
new file mode 100644
index 0000000..6f199c6
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/ChangePasswordPanel_ru.properties
@@ -0,0 +1,28 @@
+# 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.
+username=\u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f
+# password=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c
+password=\u041f\u0430\u0440\u043e\u043b\u044c
+# submit=\u00d0\u0098\u00d0\u00b7\u00d0\u00bc\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d1\u0082\u00d1\u008c
+submit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c
+cancel=\u043e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
+#confirmPassword=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c (\u00d0\u00bf\u00d0\u00be\u00d0\u00b4\u00d1\u0082\u00d0\u00b2\u00d0\u00b5\u00d1\u0080\u00d0\u00b6\u00d0\u00b4\u00d0\u00b5\u00d0\u00bd\u00d0\u00b8\u00d0\u00b5)
+confirmPassword=\u041f\u0430\u0440\u043e\u043b\u044c (\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435)
+# passwordNeedsToBeUpdated=\u00d0\u009f\u00d0\u00b0\u00d1\u0080\u00d0\u00be\u00d0\u00bb\u00d1\u008c \u00d0\u00b4\u00d0\u00be\u00d0\u00bb\u00d0\u00b6\u00d0\u00b5\u00d0\u00bd \u00d0\u00b1\u00d1\u008b\u00d1\u0082\u00d1\u008c \u00d0\u00b8\u00d0\u00b7\u00d0\u00bc\u00d0\u00b5\u00d0\u00bd\u00d1\u0091\u00d0\u00bd
+passwordNeedsToBeUpdated=\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0451\u043d
+self.pwd.change.success=\u041f\u0430\u0440\u043e\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d
+
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
new file mode 100644
index 0000000..36bd619
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/Sidebar.html
@@ -0,0 +1,58 @@
+<!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:panel>
+    <div class="brand-link bg-red" style="height: 57px;">
+      <span style="position: absolute;">
+        <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 font-weight-light">Apache Syncope</span>
+      </span>
+    </div>
+
+    <!-- Sidebar -->
+    <div class="sidebar">
+      <div class="user-panel mt-2 pb-2 mb-2 d-flex">
+      </div>
+
+      <!-- Sidebar Menu -->
+      <nav class="mt-2">
+        <ul class="nav nav-pills nav-sidebar flex-column nav-child-indent nav-compact" data-widget="treeview" role="menu" data-accordion="false">
+          <li class="nav-item" wicket:id="dashboardLI"><a href="#" wicket:id="home" class="nav-link"><i class="fa fa-id-card nav-icon"></i><p><wicket:message key="home"/></p></a></li>
+          <li wicket:id="profileLI" class="nav-item has-treeview">
+            <a id="profileLink" href="#" class="nav-link"><i class="nav-icon fa fa-user"></i><p><wicket:message key="profile"/></p> <i class="fas fa-angle-left right"></i></a>
+            <ul wicket:id="profileUL" class="nav nav-treeview">
+              <li class="nav-item" wicket:id="edituserLI"><a href="#" class="nav-link" wicket:id="edituser"><i class="nav-icon fa fa-edit"></i><p><wicket:message key="edituser"/></p></a></li>
+              <li class="nav-item" wicket:id="editchangepasswordLI"><a href="#" class="nav-link" wicket:id="editchangepassword"><i class="nav-icon fas fa-key"></i><p><wicket:message key="editchangepassword"/></p></a></li>
+              <li class="nav-item" wicket:id="editsecurityquestionLI"><a href="#" class="nav-link" wicket:id="editsecurityquestion"><i class="nav-icon fa fa-lock"></i><p><wicket:message key="editsecurityquestion"/></p></a></li>
+            </ul>
+          </li>
+
+          <wicket:container wicket:id="extPages">
+            <li class="nav-item" wicket:id="extPageLI">
+              <a href="#" class="nav-link" wicket:id="extPage"><i wicket:id="extPageIcon"></i><p wicket:id="extPageLabel"/></a>
+            </li>
+          </wicket:container>
+        </ul>
+      </nav>
+      <!-- /.sidebar-menu -->
+    </div>
+    <!-- /.sidebar -->
+  </wicket:panel>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel.html
new file mode 100644
index 0000000..1f80dab
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel.html
@@ -0,0 +1,45 @@
+<!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" >
+  <wicket:panel>
+    <!-- general form elements -->
+    <div class="col-md-6 mx-auto">
+      <!-- form start -->
+      <form wicket:id="form">
+        <div class="box-body">
+          <span wicket:id="userDetailsPanelCard">[userDetailsPanelCard]</span>
+          <span wicket:id="groupsPanelCard">[groupsPanelCard]</span>
+          <span wicket:id="plainAttrsPanelCard">[plainAttrsPanelCard]</span>
+          <span wicket:id="derAttrsPanelCard">[derAttrsPanelCard]</span>
+          <span wicket:id="virAttrsPanelCard">[virAttrsPanelCard]</span>
+          <span wicket:id="resourcesPanelCard">[resourcesPanelCard]</span>
+          <span wicket:id="captchaPanelCard">[captchaPanelCard]</span>
+        </div>
+        <!-- /.box-body -->
+        <div class="box-footer">
+          <input id="submit_button" wicket:id="submit" type="submit" wicket:message="value:submit" 
+                 class="btn btn-success float-right"/>
+          <input id="cancel_button" wicket:id="cancel" type="submit" wicket:message="value:cancel" 
+                 class="btn btn-default float-left"/>
+        </div>
+      </form>
+    </div>
+  </wicket:panel>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel.properties
similarity index 77%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel.properties
index 3779b77..0a7e6bb 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel.properties
@@ -14,12 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+self.profile.change.success.msg=Your profile has been successfully changed
+
+self.profile.change.success=Operation completed successfully
+self.profile.change.error.msg=Error updating profile
+self.profile.change.error=Error
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_it.properties
similarity index 75%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_it.properties
index 3779b77..94cf2b2 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_it.properties
@@ -14,12 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+self.profile.change.success.msg=Il tuo profilo \u00e8 stato modificato correttamente
+self.profile.change.success=Operazione completata con successo
+self.profile.change.error.msg=Errore durante l'aggiornamento del profilo
+self.profile.change.error=Errore
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_ja.properties
similarity index 64%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_ja.properties
index cf3ca73..6bfaa27 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_ja.properties
@@ -14,12 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
-cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
+self.profile.change.success.msg=\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u304c\u6b63\u5e38\u306b\u7de8\u96c6\u3055\u308c\u307e\u3057\u305f
+self.profile.change.success=\u64cd\u4f5c\u306f\u6b63\u5e38\u306b\u5b8c\u4e86\u3057\u307e\u3057\u305f
+self.profile.change.error.msg=\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306e\u66f4\u65b0\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f
+self.profile.change.error=\u30a8\u30e9\u30fc
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_pt_BR.properties
similarity index 77%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_pt_BR.properties
index 3779b77..fda623b 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_pt_BR.properties
@@ -14,12 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Username
-securityQuestion=Security Question
-securityAnswer=Security Answer
-reload=Reload
-not.loading=Not loading?
-submit=Submit
-cancel=Cancel
-self.pwd.reset.success=Password successfully reset
-domain=Domain
+self.profile.change.success.msg=Seu perfil foi editado com sucesso
+self.profile.change.success=Opera\u00e7\u00e3o conclu\u00edda com sucesso
+self.profile.change.error.msg=Erro ao atualizar perfil
+self.profile.change.error=Erro
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_ru.properties
new file mode 100644
index 0000000..adc4bf8
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserFormPanel_ru.properties
@@ -0,0 +1,20 @@
+# 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.
+self.profile.change.success.msg=\u0412\u0430\u0448 \u043f\u0440\u043e\u0444\u0438\u043b\u044c \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u043e\u0442\u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d
+self.profile.change.success=\u041e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430
+self.profile.change.error.msg=\u041e\u0448\u0438\u0431\u043a\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u044f
+self.profile.change.error=\u043e\u0448\u0438\u0431\u043a\u0430
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs$Schemas.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs$Schemas.html
similarity index 98%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs$Schemas.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs$Schemas.html
index f62795a..ab664f9 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs$Schemas.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs$Schemas.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.html
similarity index 98%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.html
index 435b727..41b83c5 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties
similarity index 99%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties
index b28194c..691a2c5 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractAttrs_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/AbstractAttrs_ru.properties
@@ -14,6 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
 attributes.accordion=\u0421\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435
 attributes.membership.accordion=\u0418\u0437 \u0443\u0447\u0430\u0441\u0442\u043d\u0438\u043a\u043e\u0432 \u0433\u0440\u0443\u043f\u043f\u044b '${groupName}'
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs.html
similarity index 98%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs.html
index dfcda58..0e0fec9 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_ru.properties
similarity index 99%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_ru.properties
index 3266270..64927a7 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/DerAttrs_ru.properties
@@ -14,7 +14,6 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
 derived.emptyvalue.message=\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u043e...
 attribute.empty.list=\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u044b\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u043e\u0432
 attributes.derived=\u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u043d\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups.html
similarity index 88%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups.html
index 1a505db..4f3d614 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -19,11 +20,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
     <div class="col-xs-12" wicket:id="groupsContainer">
-      <div class="box">
+      <div>
         <div class="box-header">
-          <h3 class="box-title">
-            <wicket:message key="groups.palette">[GROUPS]</wicket:message>
-          </h3>
           <span wicket:id="changed"></span>
         </div>
         <div class="box-body">
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_ru.properties
similarity index 99%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_ru.properties
index 72a0042..0a348ec 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Groups_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Groups_ru.properties
@@ -14,6 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
 groups.palette=\u0413\u0440\u0443\u043f\u043f\u044b
 palette.available=Available (limited to the first 30 results)
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html
similarity index 93%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html
index 8da9bd9..9650ce5 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -20,7 +21,7 @@
   <wicket:extend>
     <div wicket:id="plainSchemas"/>
     <span wicket:id="membershipsPlainSchemas">
-      <div wicket:id="membershipPlainSchemas"/>
+          <div wicket:id="membershipPlainSchemas"/>
     </span>
   </wicket:extend>
 </html>
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_ru.properties
similarity index 99%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_ru.properties
index c073f72..77d39fe 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/PlainAttrs_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/PlainAttrs_ru.properties
@@ -14,6 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-#
 attribute.empty.list=\u041f\u0440\u043e\u0441\u0442\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442
 attributes.plain=\u041f\u0440\u043e\u0441\u0442\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources.html
similarity index 88%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources.html
index 10f17f7..042f47f 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -19,11 +20,8 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
     <div class="col-xs-12">
-      <div class="box">
+      <div>
         <div class="box-header">
-          <h3 class="box-title">
-            <wicket:message key="resources.palette">[ROLES]</wicket:message>
-          </h3>
           <span wicket:id="changed"></span>
         </div>
         <div class="box-body">
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_ru.properties
similarity index 94%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_ru.properties
index 8e75558..ce0edc1 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Resources_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/Resources_ru.properties
@@ -14,6 +14,4 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
-# resources.palette=Внешние ресурсы
 resources.palette=\u0412\u043d\u0435\u0448\u043d\u0438\u0435 \u0440\u0435\u0441\u0443\u0440\u0441\u044b
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.html
similarity index 86%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.html
index dfcda58..ca2c857 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/DerAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/SelfUserDetails.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,9 +19,6 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
-    <div wicket:id="derSchemas"/>
-    <span wicket:id="membershipsDerSchemas">
-      <div wicket:id="membershipDerSchemas"/>
-    </span>
+    <div wicket:id="password"></div>
   </wicket:extend>
-</html>
\ No newline at end of file
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails$EditUserPasswordPanel.html
similarity index 90%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails$EditUserPasswordPanel.html
index e77ed5d..380233c 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails$EditUserPasswordPanel.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -20,7 +21,7 @@
   <wicket:panel>
     <div id="editUserChangePassword">
       <div class="alert alert-warning">
-        <i class="fas fa-exclamation-triangle"></i> <label wicket:id="warning">[warning]</label>
+        <i class="fa fa-exclamation-triangle"></i> <label wicket:id="warning">[warning]</label>
       </div>
 
       <div wicket:id="passwordPanel">[change password panel]</div>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
similarity index 80%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
index cc02ba2..3773f68 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -25,13 +26,7 @@
       <div class="form-group">
         <span wicket:id="username"/>
       </div>
-      <div wicket:id="accordionPanel"></div>
-      <div class="form-group">
-        <span wicket:id="securityQuestion">[SECURITY QUESTION]</span>
-      </div>
-      <div class="form-group">
-        <span wicket:id="securityAnswer">[SECURITY ANSWER]</span>
-      </div>
+      <wicket:child/>
     </div>
   </wicket:panel>
 </html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_ru.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails_ru.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs.html
similarity index 98%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs.html
index abff807..5968a33 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_ru.properties
similarity index 99%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_ru.properties
index 7509896..a162426 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/VirAttrs_ru.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/VirAttrs_ru.properties
@@ -14,6 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-
 attribute.empty.list=\u0412\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0442
 attributes.virtual=\u0412\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\u044b
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.html
similarity index 84%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.html
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.html
index 985fc50..92dd8fb 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.html
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.html
@@ -24,10 +24,10 @@
         <img wicket:id="image" />
       </div>
       <div id="captcha_image" class="captcha_elem">
-        <button wicket:id="reloadButton" id="refresh" type="button" class="btn btn-default btn-xs fa fa-sync" 
-                title="Refresh Captcha"></button>
-        <button id="infoLink" class="btn btn-default btn-xs fa fa-question-circle" title="What is this?"
-                onclick="window.open('https://it.wikipedia.org/wiki/CAPTCHA')"></button>
+        <button wicket:id="reloadButton" id="refresh" type="button" class="btn btn-default btn-xs" 
+                title="Refresh Captcha"><i class="fas fa-sync"></i></button>
+        <button id="infoLink" class="btn btn-default btn-xs" title="What is this?"
+                onclick="window.open('https://it.wikipedia.org/wiki/CAPTCHA')"><i class="fa fa-question-circle"></i></button>
       </div>
       <div>
         <label for="captcha"><wicket:message key="captcha.message"/></label>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/captcha/CaptchaPanel_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget.html
new file mode 100644
index 0000000..71c4015
--- /dev/null
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget.html
@@ -0,0 +1,50 @@
+<!--
+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:panel>
+    <div class="box">
+      <div class="box-body" wicket:id="userProfile">
+        <div class="box-header formcard col-md-4">
+          <header class="card-container bg-danger">
+            <label class="card-header-style">
+              <wicket:message key="welcome"/>
+              <b><label class="card-header-style" wicket:id="welcome"/></b></label>
+          </header>
+          <div class="card-container-body">
+            <div>
+              <div class="box-footer no-padding no-border-top">
+                <ul class="nav navbar-nav flex-column">
+                  <li class="nav-item"><a class="nav-link disabled" href="#"><wicket:message key="name"/>:
+                      <span class="float-right badge"><label wicket:id="username"/></span></a>
+                  </li>
+                  <li class="nav-item"><a class="nav-link disabled" href="#"><wicket:message key="lastLoginDate"/>:
+                      <span class="float-right badge"><label wicket:id="lastLoginDate"/></span></a>
+                  </li>
+                  <li class="nav-item"><a class="nav-link disabled" href="#"><wicket:message key="lastChangePwdDate"/>:
+                      <span class="float-right badge"><label wicket:id="changePwdDate"/></span></a>
+                  </li>
+                </ul>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </wicket:panel>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget.properties
similarity index 88%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget.properties
index 56c00af..04eedfc 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget.properties
@@ -14,4 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+welcome=Welcome, 
+name=Name
+lastLoginDate=Last Login Date
+lastChangePwdDate=Last Change Password Date
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_fr_CA.properties
similarity index 87%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_fr_CA.properties
index 56c00af..b6e20a6 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_fr_CA.properties
@@ -14,4 +14,7 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+welcome=Bienvenu, 
+name=Name
+lastLoginDate=Last Login Date
+lastChangePwdDate=Last Change Password Date
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_it.properties
similarity index 87%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_it.properties
index 56c00af..a9c98ce 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_it.properties
@@ -14,4 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+welcome=Benvenuto,  
+
+name=Nome
+lastLoginDate=Data Ultimo Accesso
+lastChangePwdDate=Data Ultimo Cambio Password
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_ja.properties
similarity index 85%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_ja.properties
index 56c00af..b6154b7 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_ja.properties
@@ -14,4 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+welcome=\u30e6\u30fc\u30b6\u30fc\u540d
+
+name=Name
+lastLoginDate=Last Login Date
+lastChangePwdDate=Last Change Password Date
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_pt_BR.properties
similarity index 87%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_pt_BR.properties
index 56c00af..64baa37 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_pt_BR.properties
@@ -14,4 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+welcome=Bem-vinda
+
+name=Name
+lastLoginDate=Last Login Date
+lastChangePwdDate=Last Change Password Date
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_ru.properties
similarity index 86%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_ru.properties
index 56c00af..fa6afbc 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/widgets/UserProfileWidget_ru.properties
@@ -14,4 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+welcome=\u3088\u3046\u3053\u305d, 
+
+name=Name
+lastLoginDate=Last Login Date
+lastChangePwdDate=Last Change Password Date
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Captcha.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Captcha.html
deleted file mode 100644
index 9593914..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/Captcha.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-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:panel>
-    <div class="box-body">
-      <span wicket:id="captchaPanel">[CAPTCHA]</span>
-    </div>
-  </wicket:panel>
-</html>
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_pt_BR.properties
deleted file mode 100644
index d21fb6b..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_pt_BR.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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.
-captcha.message=Por favor, insira o c\u00f3digo exibido dentro da imagem.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ru.properties
deleted file mode 100644
index 9a077bd..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ru.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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.
-captcha.message=\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u043d\u0430 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0435.
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.html
deleted file mode 100644
index f70fdfe..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
-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:panel>
-    <div class="col-xs-12">
-      <div class="box">
-        <div class="box-header">
-          <h3 class="box-title">
-            <wicket:message key="auxClasses.palette">AUX CLASSES</wicket:message>
-          </h3>
-          <span wicket:id="changed"></span>
-        </div>
-        <div class="box-body">
-          <span wicket:id="auxClasses">[AUX CLASSES]</span>
-        </div>
-      </div>
-    </div>
-  </wicket:panel>
-</html>
\ No newline at end of file
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_it.properties
deleted file mode 100644
index 69fb846..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_it.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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.
-auxClasses.palette=Classi ausiliarie
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ja.properties
deleted file mode 100644
index 5840b2f..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_ja.properties
+++ /dev/null
@@ -1,17 +0,0 @@
-# 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.
-auxClasses.palette=\u88dc\u52a9\u30af\u30e9\u30b9
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.html
deleted file mode 100644
index 96f296e..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!--
-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">
-  <head>
-    <title></title>
-  </head>
-  <body>
-    <wicket:panel>
-      <div class="wizard-form">
-        <form wicket:id="form">
-          <div class="wizard-overview">
-            <div wicket:id="overview"></div> 
-          </div>
-          <div class="wizard-header">
-            <div wicket:id="header"></div>
-          </div>
-          <div class="wizard-view">
-            <div wicket:id="view"></div>
-          </div>
-          <div class="wizard-buttons">
-            <div wicket:id="buttons"></div>
-          </div>
-        </form>
-      </div>
-      <span wicket:id="outerObjectsRepeater">
-        <div wicket:id="outer"/>
-      </span>
-    </wicket:panel>
-  </body>
-</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.properties
deleted file mode 100644
index cafa9d6..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-wizard.cancel.error = Wizard error on cancel
-wizard.apply.error = Wizard error on apply changes
-next=Next >
-previous=< Prev
-finish=Finish
-last=Last
-cancel=Cancel
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.html
deleted file mode 100644
index 4d21ce8..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizardMgtButtonBar.html
+++ /dev/null
@@ -1,38 +0,0 @@
-<!--
-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">
-  <head>
-    <title></title>
-  </head>
-  <body>
-    <wicket:panel xmlns:wicket="http://wicket.apache.org">
-
-      <span class="float-left">
-        <input wicket:id="cancel" type="submit" wicket:message="value:cancel" class="btn btn-default"/>
-      </span>
-      <span class="float-right">
-        <input wicket:id="previous" type="submit" wicket:message="value:previous" class="btn btn-default"/>
-        <input wicket:id="next" type="submit" wicket:message="value:next" class="btn btn-default"/>
-        <input wicket:id="last" type="submit" wicket:message="value:last" class="btn btn-default"/>
-        <input wicket:id="finish" type="submit" wicket:message="value:finish" class="btn btn-success"/>
-      </span>
-
-    </wicket:panel>
-  </body>
-</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_it.properties
deleted file mode 100644
index 3230f79..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_it.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-wizard.cancel.error = Errore wizard in annullamento operazione
-wizard.apply.error = Errore wizard in fase di commit
-next=Succ >
-previous=< Prec
-finish=Termina
-last=Ultimo
-cancel=Annulla
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_ja.properties
deleted file mode 100644
index 8c1969a..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_ja.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-wizard.cancel.error = \u30ad\u30e3\u30f3\u30bb\u30eb\u6642\u306b\u30a6\u30a3\u30b6\u30fc\u30c9\u30a8\u30e9\u30fc
-wizard.apply.error = \u5909\u66f4\u306e\u9069\u7528\u6642\u306b\u30a6\u30a3\u30b6\u30fc\u30c9\u30a8\u30e9\u30fc
-next=\u6b21\u3078 >
-previous=< \u524d\u3078
-finish=\u5b8c\u4e86
-last=\u6700\u5f8c
-cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_pt_BR.properties
deleted file mode 100644
index 6cca386..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_pt_BR.properties
+++ /dev/null
@@ -1,23 +0,0 @@
-# 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.
-wizard.cancel.error = Erro em assistente de cancelar
-wizard.apply.error = Assistente de erro em aplicar as altera\u00e7\u00f5es
-next=Segu >
-previous=< Prec
-finish=Terminar
-last=\u00daltimo
-cancel=Cancelar
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_ru.properties
deleted file mode 100644
index af93f5a..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/ui/commons/wizards/AjaxWizard_ru.properties
+++ /dev/null
@@ -1,30 +0,0 @@
-# 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.
-# wizard.cancel.error = Во время отмены произошла ошибка
-wizard.cancel.error = \u0412\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043c\u0435\u043d\u044b \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430
-# wizard.apply.error = Во время применения изменений произошла ошибка
-wizard.apply.error = \u0412\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430
-# next=Далее >
-next=\u0414\u0430\u043b\u0435\u0435 >
-# previous=< Назад
-previous=< \u041d\u0430\u0437\u0430\u0434
-# finish=Завершить
-finish=\u0417\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c
-# last=В конец
-last=\u0412 \u043a\u043e\u043d\u0435\u0446
-# cancel=Отмена
-cancel=\u041e\u0442\u043c\u0435\u043d\u0430
diff --git a/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java b/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java
new file mode 100644
index 0000000..c61be4e
--- /dev/null
+++ b/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/AbstractTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.giffing.wicket.spring.boot.context.extensions.WicketApplicationInitConfiguration;
+import com.giffing.wicket.spring.boot.context.extensions.boot.actuator.WicketEndpointRepository;
+import com.giffing.wicket.spring.boot.starter.app.classscanner.candidates.WicketClassCandidatesHolder;
+import com.giffing.wicket.spring.boot.starter.configuration.extensions.core.settings.general.GeneralSettingsProperties;
+import com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.boot.actuator.WicketEndpointRepositoryDefault;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Properties;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.tuple.Triple;
+import org.apache.cxf.jaxrs.client.Client;
+import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup;
+import org.apache.syncope.client.lib.AuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.client.ui.commons.MIMETypesLoader;
+import org.apache.syncope.common.keymaster.client.api.DomainOps;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.Domain;
+import org.apache.syncope.common.lib.info.NumbersInfo;
+import org.apache.syncope.common.lib.info.PlatformInfo;
+import org.apache.syncope.common.lib.info.SystemInfo;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.AnyTypeTO;
+import org.apache.syncope.common.lib.to.PlainSchemaTO;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.rest.api.beans.SchemaQuery;
+import org.apache.syncope.common.rest.api.service.AnyTypeService;
+import org.apache.syncope.common.rest.api.service.SchemaService;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.util.tester.WicketTester;
+import org.junit.jupiter.api.BeforeAll;
+import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+public abstract class AbstractTest {
+
+    @ImportAutoConfiguration
+    @Configuration
+    public static class SyncopeEnduserWebApplicationTestConfig {
+
+        @Bean
+        public ServiceOps selfServiceOps() {
+            return mock(ServiceOps.class);
+        }
+
+        @Bean
+        public DomainOps domainOps() {
+            DomainOps domainOps = mock(DomainOps.class);
+            when(domainOps.list()).thenReturn(List.of(new Domain.Builder(SyncopeConstants.MASTER_DOMAIN).build()));
+            return domainOps;
+        }
+
+        @Bean
+        public GeneralSettingsProperties generalSettingsProperties() {
+            return new GeneralSettingsProperties();
+        }
+
+        @Bean
+        public List<WicketApplicationInitConfiguration> configurations() {
+            return List.of();
+        }
+
+        @Bean
+        public WicketClassCandidatesHolder wicketClassCandidatesHolder() {
+            return new WicketClassCandidatesHolder();
+        }
+
+        @Bean
+        public WicketEndpointRepository wicketEndpointRepository() {
+            return new WicketEndpointRepositoryDefault();
+        }
+
+        @Bean
+        public ClassPathScanImplementationLookup classPathScanImplementationLookup() {
+            ClassPathScanImplementationLookup lookup = new ClassPathScanImplementationLookup();
+            lookup.load();
+            return lookup;
+        }
+
+        @Bean
+        public MIMETypesLoader mimeTypesLoader() {
+            MIMETypesLoader mimeTypesLoader = new MIMETypesLoader();
+            mimeTypesLoader.load();
+            return mimeTypesLoader;
+        }
+    }
+
+    public static class TestSyncopeWebApplication extends SyncopeWebApplication {
+
+        public interface SyncopeServiceClient extends SyncopeService, Client {
+        }
+
+        public interface AnyTypeServiceClient extends AnyTypeService, Client {
+        }
+
+        public interface SchemaServiceClient extends SchemaService, Client {
+        }
+
+        private SyncopeService getSyncopeService() {
+            SyncopeServiceClient service = mock(SyncopeServiceClient.class);
+            when(service.type(anyString())).thenReturn(service);
+            when(service.accept(anyString())).thenReturn(service);
+
+            when(service.platform()).thenReturn(new PlatformInfo());
+            when(service.system()).thenReturn(new SystemInfo());
+
+            NumbersInfo numbersInfo = new NumbersInfo();
+            Stream.of(NumbersInfo.ConfItem.values()).
+                    forEach(item -> numbersInfo.getConfCompleteness().put(item.name(), true));
+            when(service.numbers()).thenReturn(numbersInfo);
+
+            return service;
+        }
+
+        private SchemaService getSchemaService() {
+            SchemaServiceClient service = mock(SchemaServiceClient.class);
+
+            when(service.type(anyString())).thenReturn(service);
+            when(service.accept(anyString())).thenReturn(service);
+
+            PlainSchemaTO firstname = new PlainSchemaTO();
+            firstname.setKey("firstname");
+            firstname.setType(AttrSchemaType.String);
+            firstname.setAnyTypeClass("minimal user");
+            firstname.setMandatoryCondition("false");
+            when(service.search(any(SchemaQuery.class))).thenReturn(List.of(firstname));
+            return service;
+        }
+
+        private AnyTypeService getAnyTypeService() {
+            AnyTypeServiceClient service = mock(AnyTypeServiceClient.class);
+
+            when(service.type(anyString())).thenReturn(service);
+            when(service.accept(anyString())).thenReturn(service);
+
+            AnyTypeTO anyTypeTO = new AnyTypeTO();
+            anyTypeTO.setKey("123456");
+            anyTypeTO.setKind(AnyTypeKind.USER);
+
+            when(service.read(anyString())).thenReturn(anyTypeTO);
+            return service;
+        }
+
+        private UserTO getUserTO() {
+            UserTO userTO = new UserTO();
+            userTO.setUsername("username");
+            return userTO;
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public SyncopeClientFactoryBean newClientFactory() {
+            SyncopeClient client = mock(SyncopeClient.class);
+            when(client.getJWT()).thenReturn("<anyJWT>");
+
+            when(client.self()).thenReturn(Triple.of(new HashMap<>(), List.of(), getUserTO()));
+
+            SyncopeService syncopeService = getSyncopeService();
+            when(client.getService(SyncopeService.class)).thenReturn(syncopeService);
+
+            SchemaService schemaService = getSchemaService();
+            when(client.getService(SchemaService.class)).thenReturn(schemaService);
+
+            AnyTypeService anyTypeService = getAnyTypeService();
+            when(client.getService(AnyTypeService.class)).thenReturn(anyTypeService);
+
+            SyncopeClientFactoryBean clientFactory = mock(SyncopeClientFactoryBean.class);
+            when(clientFactory.setDomain(any())).thenReturn(clientFactory);
+            when(clientFactory.create(any(AuthenticationHandler.class))).thenReturn(client);
+            when(clientFactory.create(anyString(), anyString())).thenReturn(client);
+
+            return clientFactory;
+        }
+    }
+
+    protected static Properties PROPS;
+
+    protected static WicketTester TESTER;
+
+    @BeforeAll
+    public static void loadProps() throws IOException {
+        PROPS = new Properties();
+        try (InputStream is = AbstractTest.class.getResourceAsStream("/enduser.properties")) {
+            PROPS.load(is);
+        }
+    }
+
+    @BeforeAll
+    public static void setupTester() throws IOException {
+        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
+        ctx.register(SyncopeEnduserWebApplicationTestConfig.class);
+        ctx.register(TestSyncopeWebApplication.class);
+        ctx.refresh();
+
+        TESTER = new WicketTester(ctx.getBean(SyncopeWebApplication.class));
+    }
+}
diff --git a/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/SyncopeEnduserApplicationTest.java b/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/SyncopeEnduserApplicationTest.java
new file mode 100644
index 0000000..1323395
--- /dev/null
+++ b/client/idrepo/enduser/src/test/java/org/apache/syncope/client/enduser/SyncopeEnduserApplicationTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.IOException;
+import java.security.AccessControlException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import javax.ws.rs.BadRequestException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.enduser.pages.Dashboard;
+import org.apache.syncope.client.enduser.pages.Login;
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.wicket.feedback.FeedbackMessage;
+import org.apache.wicket.util.tester.FormTester;
+import org.junit.jupiter.api.Test;
+
+public class SyncopeEnduserApplicationTest extends AbstractTest {
+
+    private Map<String, String> getConfiguredSecurityHeaders() throws IOException {
+        Map<String, String> securityHeaders = new HashMap<>();
+
+        @SuppressWarnings("unchecked")
+        Enumeration<String> propNames = (Enumeration<String>) PROPS.propertyNames();
+        while (propNames.hasMoreElements()) {
+            String name = propNames.nextElement();
+            if (name.startsWith("security.headers.")) {
+                securityHeaders.put(StringUtils.substringAfter(name, "security.headers."), PROPS.getProperty(name));
+            }
+        }
+
+        return securityHeaders;
+    }
+
+    @Test
+    public void securityHeaders() throws IOException {
+        Map<String, String> securityHeaders = getConfiguredSecurityHeaders();
+        assertEquals(4, securityHeaders.size());
+
+        // 1. anonymous
+        TESTER.startPage(Login.class);
+        TESTER.assertRenderedPage(Login.class);
+        securityHeaders.forEach((key, value) -> assertEquals(value, TESTER.getLastResponse().getHeader(key)));
+
+        // 2. authenticated
+        FormTester formTester = TESTER.newFormTester("login");
+        formTester.setValue("username", "username");
+        formTester.setValue("password", "password");
+        formTester.submit("submit");
+
+        TESTER.assertRenderedPage(Dashboard.class);
+        securityHeaders.forEach((key, value) -> assertEquals(value, TESTER.getLastResponse().getHeader(key)));
+    }
+
+    @Test
+    public void errors() {
+        SyncopeEnduserSession session = SyncopeEnduserSession.get();
+
+        assertNull(session.getFeedbackMessages().first());
+
+        session.onException(new AccessControlException("JWT Expired"));
+        FeedbackMessage message = session.getFeedbackMessages().first();
+        assertNotNull(message);
+        assertTrue(message.isError());
+        assertEquals(SyncopeEnduserSession.Error.SESSION_EXPIRED.fallback(), message.getMessage());
+        session.getFeedbackMessages().clear();
+
+        session.onException(new AccessControlException("Auth Exception"));
+        message = session.getFeedbackMessages().first();
+        assertNotNull(message);
+        assertTrue(message.isError());
+        assertEquals(SyncopeEnduserSession.Error.AUTHORIZATION.fallback(), message.getMessage());
+        session.getFeedbackMessages().clear();
+
+        session.onException(new BadRequestException());
+        message = session.getFeedbackMessages().first();
+        assertNotNull(message);
+        assertTrue(message.isError());
+        assertEquals(SyncopeEnduserSession.Error.REST.fallback(), message.getMessage());
+        session.getFeedbackMessages().clear();
+
+        SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidUser);
+        sce.getElements().add("Error 1");
+        session.onException(sce);
+        message = session.getFeedbackMessages().first();
+        assertNotNull(message);
+        assertTrue(message.isError());
+        assertEquals(ClientExceptionType.InvalidUser.name() + ": Error 1", message.getMessage());
+        session.getFeedbackMessages().clear();
+
+        sce = SyncopeClientException.build(ClientExceptionType.InvalidUser);
+        sce.getElements().add("Error 1");
+        sce.getElements().add("Error 2");
+        session.onException(sce);
+        message = session.getFeedbackMessages().first();
+        assertNotNull(message);
+        assertTrue(message.isError());
+        assertEquals(ClientExceptionType.InvalidUser.name() + ": Error 1, Error 2", message.getMessage());
+        session.getFeedbackMessages().clear();
+
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+        scce.addException(SyncopeClientException.build(ClientExceptionType.InvalidUser));
+        scce.addException(SyncopeClientException.build(ClientExceptionType.InvalidExternalResource));
+        session.onException(new ExecutionException(scce));
+        message = session.getFeedbackMessages().first();
+        assertNotNull(message);
+        assertTrue(message.isError());
+        assertTrue(StringUtils.contains((CharSequence) message.getMessage(),
+                ClientExceptionType.InvalidExternalResource.name()));
+        assertTrue(StringUtils.contains((CharSequence) message.getMessage(), ClientExceptionType.InvalidUser.name()));
+        session.getFeedbackMessages().clear();
+    }
+}
diff --git a/client/idrepo/enduser/src/test/resources/log4j2.xml b/client/idrepo/enduser/src/test/resources/log4j2.xml
new file mode 100644
index 0000000..1da7c54
--- /dev/null
+++ b/client/idrepo/enduser/src/test/resources/log4j2.xml
@@ -0,0 +1,54 @@
+<?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.
+-->
+<configuration status="WARN">
+
+  <appenders>
+
+    <Console name="main" target="SYSTEM_OUT">
+      <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger - %msg%n"/>
+    </Console>
+    
+  </appenders>
+
+  <loggers>
+
+    <asyncLogger name="org.apache.syncope.client.lib" additivity="false" level="OFF">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.apache.syncope.client.enduser" additivity="false" level="ERROR">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+
+    <asyncLogger name="org.apache.wicket" additivity="false" level="ERROR">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+    
+    <asyncLogger name="org.apache.cxf" additivity="false" level="ERROR">
+      <appender-ref ref="main"/>
+    </asyncLogger>
+    
+    <root level="ERROR">
+      <appender-ref ref="main"/>
+    </root>
+  
+  </loggers>
+  
+</configuration>
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 f7c3594..bffc121 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
@@ -24,29 +24,22 @@
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
 import org.apache.syncope.client.enduser.markup.html.form.BpmnProcessesAjaxPanel;
+import org.apache.syncope.client.enduser.panels.UserRequestDetails;
 import org.apache.syncope.client.enduser.rest.BpmnProcessRestClient;
 import org.apache.syncope.client.enduser.rest.UserRequestRestClient;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
 import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Accordion;
-import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.BpmnProcess;
 import org.apache.syncope.common.lib.to.UserRequest;
-import org.apache.syncope.common.lib.to.UserRequestForm;
-import org.apache.syncope.ext.client.common.ui.panels.UserRequestFormPanel;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
-import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.ajax.markup.html.navigation.paging.AjaxPagingNavigator;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
 import org.apache.wicket.extensions.markup.html.tabs.ITab;
 import org.apache.wicket.markup.html.WebMarkupContainer;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
 import org.apache.wicket.markup.repeater.data.IDataProvider;
@@ -55,12 +48,14 @@
 import org.apache.wicket.model.StringResourceModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
-@ExtPage(label = "User Requests", icon = "", listEntitlement = "")
-public class Flowable extends BaseExtPage {
+@ExtPage(label = "User Requests", icon = "fa fa-briefcase", listEntitlement = "")
+public class Flowable extends BasePage {
 
     private static final long serialVersionUID = -8781434495150074529L;
 
-    private final int rowsPerPage = 10;
+    private static final String USER_REQUESTS = "page.userRequests";
+
+    private final int rowsPerPage = 5;
 
     private final Model<String> bpmnProcessModel = new Model<>();
 
@@ -69,7 +64,7 @@
     private final DataView<UserRequest> urDataView;
 
     public Flowable(final PageParameters parameters) {
-        super(parameters);
+        super(parameters, USER_REQUESTS);
 
         container = new WebMarkupContainer("content");
         container.setOutputMarkupId(true);
@@ -83,15 +78,15 @@
             protected void populateItem(final Item<UserRequest> item) {
                 final UserRequest userRequest = item.getModelObject();
                 item.add(new Accordion("userRequestDetails", Collections.<ITab>singletonList(new AbstractTab(
-                        new StringResourceModel("user.requests.accordion", container,
-                                Model.of(userRequest))) {
+                        new StringResourceModel("user.requests.accordion", container, Model.of(userRequest))) {
 
                     private static final long serialVersionUID = 1037272333056449378L;
 
                     @Override
                     public WebMarkupContainer getPanel(final String panelId) {
                         // find the form associated to the current request, if any
-                        return new UserRequestDetails(panelId, userRequest);
+                        return new UserRequestDetails(
+                                panelId, userRequest, container, notificationPanel, getPageReference());
                     }
                 }), Model.of(-1)).setOutputMarkupId(true));
             }
@@ -102,8 +97,7 @@
         container.add(urDataView);
         container.add(new AjaxPagingNavigator("navigator", urDataView));
 
-        final AjaxLink<Void> startButton =
-                new AjaxLink<>("start") {
+        AjaxLink<Void> startButton = new AjaxLink<Void>("start") {
 
             private static final long serialVersionUID = 3669569969172391336L;
 
@@ -111,7 +105,7 @@
             public void onClick(final AjaxRequestTarget target) {
                 if (StringUtils.isNotBlank(bpmnProcessModel.getObject())) {
                     try {
-                        UserRequestRestClient.start(bpmnProcessModel.getObject(), null);
+                        UserRequestRestClient.startRequest(bpmnProcessModel.getObject(), null);
                     } catch (Exception e) {
                         LOG.error("Unable to start bpmnProcess [{}]", bpmnProcessModel.getObject(), e);
                         SyncopeEnduserSession.get()
@@ -127,98 +121,27 @@
         container.add(startButton);
 
         // autocomplete select with bpmnProcesses
-        final BpmnProcessesAjaxPanel bpmnProcesses =
-                new BpmnProcessesAjaxPanel("bpmnProcesses", "bpmnProcesses", bpmnProcessModel,
-                        new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+        BpmnProcessesAjaxPanel bpmnProcesses = new BpmnProcessesAjaxPanel("bpmnProcesses", "bpmnProcesses",
+                bpmnProcessModel, new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
 
-                    private static final long serialVersionUID = -1107858522700306810L;
+            private static final long serialVersionUID = -1107858522700306810L;
 
-                    @Override
-                    protected void onUpdate(final AjaxRequestTarget target) {
-                        if (StringUtils.isNotBlank(bpmnProcessModel.getObject())) {
-                            startButton.setEnabled(true);
-                        } else {
-                            startButton.setEnabled(false);
-                        }
-                        target.add(container);
-                    }
-                });
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                if (StringUtils.isNotBlank(bpmnProcessModel.getObject())) {
+                    startButton.setEnabled(true);
+                } else {
+                    startButton.setEnabled(false);
+                }
+                target.add(container);
+            }
+        });
         bpmnProcesses.setChoices(BpmnProcessRestClient.getDefinitions().stream()
                 .filter(definition -> !definition.isUserWorkflow())
                 .map(BpmnProcess::getKey).collect(Collectors.toList()));
         container.add(bpmnProcesses);
 
-        body.add(container);
-    }
-
-    @Override
-    protected void onBeforeRender() {
-        super.onBeforeRender();
-        navbar.setActiveNavItem(getClass().getSimpleName().toLowerCase());
-    }
-
-    public class UserRequestDetails extends Panel {
-
-        private static final long serialVersionUID = -2447602429647965090L;
-
-        public UserRequestDetails(final String id, final UserRequest userRequest) {
-            super(id);
-
-            final UserRequestForm formTO = userRequest.getHasForm()
-                    ? UserRequestRestClient.getForm(SyncopeEnduserSession.get().getSelfTO().getUsername(), userRequest.
-                            getTaskId()).orElse(null)
-                    : null;
-
-            add(formTO == null || formTO.getProperties() == null || formTO.getProperties().isEmpty()
-                    ? new Fragment("fragContainer", "formDetails", UserRequestDetails.this)
-                            .add(new Label("executionId", userRequest.getExecutionId()))
-                            .add(new Label("startTime", userRequest.getStartTime()))
-                    : new Fragment("fragContainer", "formProperties", UserRequestDetails.this)
-                            .add(new Form<>("userRequestWrapForm").add(new UserRequestFormPanel(
-                                    "userRequestFormPanel",
-                                    formTO,
-                                    false) {
-
-                                private static final long serialVersionUID = 3617895525072546591L;
-
-                                @Override
-                                protected void viewDetails(final AjaxRequestTarget target) {
-                                    // do nothing
-                                }
-                            }).add(new AjaxButton("submit") {
-
-                                private static final long serialVersionUID = 4284361595033427185L;
-
-                                @Override
-                                protected void onSubmit(final AjaxRequestTarget target) {
-                                    try {
-                                        UserRequestRestClient.claimForm(formTO.getTaskId());
-                                        UserRequestRestClient.submitForm(formTO);
-                                        target.add(container);
-                                    } catch (SyncopeClientException sce) {
-                                        LOG.error("Unable to submit user request form for BPMN process [{}]",
-                                                formTO.getBpmnProcess(), sce);
-                                        SyncopeEnduserSession.get().error(StringUtils.isBlank(sce.getMessage())
-                                                ? sce.getClass().getName()
-                                                : sce.getMessage());
-                                        notificationPanel.refresh(target);
-                                    }
-                                }
-
-                            }.setOutputMarkupId(true))));
-
-            add(new AjaxLink<Void>("delete") {
-
-                private static final long serialVersionUID = 3669569969172391336L;
-
-                @Override
-                public void onClick(final AjaxRequestTarget target) {
-                    UserRequestRestClient.cancelRequest(userRequest.getExecutionId(), null);
-                    target.add(container);
-                }
-
-            });
-        }
+        contentWrapper.add(container);
     }
 
     public static class URDataProvider implements IDataProvider<UserRequest> {
@@ -236,7 +159,7 @@
 
         @Override
         public Iterator<UserRequest> iterator(final long first, final long count) {
-            final int page = ((int) first / paginatorRows);
+            int page = ((int) first / paginatorRows);
             return UserRequestRestClient.listRequests((page < 0 ? 0 : page) + 1,
                     paginatorRows,
                     SyncopeEnduserSession.get().getSelfTO().getUsername(),
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserRequestDetails.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserRequestDetails.java
new file mode 100644
index 0000000..cc3bf6b
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/UserRequestDetails.java
@@ -0,0 +1,126 @@
+/*
+ * 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.panels;
+
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.enduser.rest.UserRequestRestClient;
+import org.apache.syncope.client.ui.commons.panels.NotificationPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.to.UserRequest;
+import org.apache.syncope.common.lib.to.UserRequestForm;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ExecStatus;
+import org.apache.syncope.ext.client.common.ui.panels.UserRequestFormPanel;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserRequestDetails extends Panel {
+
+    private static final long serialVersionUID = -2447602429647965090L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(UserRequestDetails.class);
+
+    private static final String USER_REQUEST_ERROR = "user_request_error";
+
+    public UserRequestDetails(
+            final String id,
+            final UserRequest userRequest,
+            final WebMarkupContainer container,
+            final NotificationPanel notificationPanel,
+            final PageReference pageRef) {
+
+        super(id);
+
+        UserRequestForm formTO = userRequest.getHasForm()
+                ? UserRequestRestClient.getForm(
+                        SyncopeEnduserSession.get().getSelfTO().getUsername(),
+                        userRequest.getTaskId()).orElse(null)
+                : null;
+
+        if (formTO == null || formTO.getProperties() == null || formTO.getProperties().isEmpty()) {
+            add(new Fragment("fragContainer", "formDetails", UserRequestDetails.this)
+                    .add(new Label("executionId", userRequest.getExecutionId()))
+                    .add(new Label("startTime", userRequest.getStartTime())));
+        } else {
+            Form<Void> form = new Form<>("userRequestWrapForm");
+
+            form.add(new UserRequestFormPanel("userRequestFormPanel", pageRef, formTO, false) {
+
+                private static final long serialVersionUID = 3617895525072546591L;
+
+                @Override
+                protected void viewDetails(final AjaxRequestTarget target) {
+                    // do nothing
+                }
+            });
+
+            form.add(new AjaxButton("submit") {
+
+                private static final long serialVersionUID = 4284361595033427185L;
+
+                @Override
+                protected void onSubmit(final AjaxRequestTarget target) {
+                    try {
+                        UserRequestRestClient.claimForm(formTO.getTaskId());
+                        ProvisioningResult<UserTO> result = UserRequestRestClient.submitForm(formTO);
+
+                        if (result.getPropagationStatuses().stream().
+                                anyMatch(p -> ExecStatus.FAILURE == p.getStatus()
+                                || ExecStatus.NOT_ATTEMPTED == p.getStatus())) {
+
+                            SyncopeEnduserSession.get().error(getString(USER_REQUEST_ERROR));
+                            notificationPanel.refresh(target);
+                        }
+
+                        target.add(container);
+                    } catch (SyncopeClientException sce) {
+                        LOG.error("Unable to submit user request form for BPMN process [{}]",
+                                formTO.getBpmnProcess(), sce);
+                        SyncopeEnduserSession.get().error(getString(USER_REQUEST_ERROR));
+                        notificationPanel.refresh(target);
+                    }
+                }
+
+            }.setOutputMarkupId(true));
+
+            add(new Fragment("fragContainer", "formProperties", UserRequestDetails.this).add(form));
+        }
+
+        add(new AjaxLink<Void>("delete") {
+
+            private static final long serialVersionUID = 3669569969172391336L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                UserRequestRestClient.cancelRequest(userRequest.getExecutionId(), null);
+                target.add(container);
+            }
+        });
+    }
+}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java
new file mode 100644
index 0000000..278d04b
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/BaseRestClient.java
@@ -0,0 +1,68 @@
+/*
+ * 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.rest;
+
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.search.OrderByClauseBuilder;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.syncope.client.ui.commons.rest.RestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class BaseRestClient implements RestClient {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(BaseRestClient.class);
+
+    private static final long serialVersionUID = 1523999867826481989L;
+
+    public static SyncopeService getSyncopeService() {
+        return getService(SyncopeService.class);
+    }
+
+    protected static <T> T getService(final Class<T> serviceClass) {
+        return SyncopeEnduserSession.get().getService(serviceClass);
+    }
+
+    protected static <T> T getService(final String etag, final Class<T> serviceClass) {
+        return SyncopeEnduserSession.get().getService(etag, serviceClass);
+    }
+
+    protected static <T> void resetClient(final Class<T> serviceClass) {
+        SyncopeEnduserSession.get().resetClient(serviceClass);
+    }
+
+    protected static String toOrderBy(final SortParam<String> sort) {
+        OrderByClauseBuilder builder = SyncopeClient.getOrderByClauseBuilder();
+
+        String property = sort.getProperty();
+        if (property.indexOf('#') != -1) {
+            property = property.substring(property.indexOf('#') + 1);
+        }
+
+        if (sort.isAscending()) {
+            builder.asc(property);
+        } else {
+            builder.desc(property);
+        }
+
+        return builder.build();
+    }
+}
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
index 71d9cf6..93a5670 100644
--- a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/client/enduser/rest/UserRequestRestClient.java
@@ -20,10 +20,13 @@
 
 import java.util.List;
 import java.util.Optional;
+import javax.ws.rs.core.GenericType;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.UserRequest;
 import org.apache.syncope.common.lib.to.UserRequestForm;
+import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.rest.api.beans.UserRequestQuery;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.syncope.common.rest.api.service.UserRequestService;
@@ -46,12 +49,11 @@
             final int size,
             final String username,
             final SortParam<String> sort) {
-
         return getService(UserRequestService.class).listRequests(new UserRequestQuery.Builder().
                 user(StringUtils.isBlank(username)
                         ? SyncopeEnduserSession.get().getSelfTO().getUsername()
                         : username).
-                page(page).size(size).orderBy(toOrderBy(sort)).build()).getResult();
+                page(page).size(size).build()).getResult();
     }
 
     public static void cancelRequest(final String executionId, final String reason) {
@@ -64,7 +66,7 @@
                 getTotalCount();
     }
 
-    public static List<UserRequestForm> getForms(final int page, final int size, final SortParam<String> sort) {
+    public static List<UserRequestForm> listForms(final int page, final int size, final SortParam<String> sort) {
         return getService(UserRequestService.class).
                 listForms(new UserRequestQuery.Builder().page(page).size(size).orderBy(toOrderBy(sort)).build()).
                 getResult();
@@ -77,11 +79,13 @@
                 taskId));
     }
 
-    public static void submitForm(final UserRequestForm form) {
-        getService(UserRequestService.class).submitForm(form);
+    public static ProvisioningResult<UserTO> submitForm(final UserRequestForm form) {
+        return getService(UserRequestService.class).submitForm(form).readEntity(
+                new GenericType<ProvisioningResult<UserTO>>() {
+        });
     }
 
-    public static void start(final String bpmnProcess, final String user) {
+    public static void startRequest(final String bpmnProcess, final String user) {
         getService(UserRequestService.class).startRequest(bpmnProcess, user, null);
     }
 
diff --git a/ext/flowable/client-enduser/src/main/java/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.java b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.java
new file mode 100644
index 0000000..f83c9a8
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/java/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.java
@@ -0,0 +1,220 @@
+/*
+ * 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.ext.client.common.ui.panels;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.commons.lang3.time.FastDateFormat;
+import org.apache.syncope.client.ui.commons.MapChoiceRenderer;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDateTimeFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxSpinnerFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.markup.html.form.FieldPanel;
+import org.apache.syncope.common.lib.to.UserRequestForm;
+import org.apache.syncope.common.lib.to.UserRequestFormProperty;
+import org.apache.syncope.common.lib.to.UserRequestFormPropertyValue;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.PropertyModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public abstract class UserRequestFormPanel extends Panel {
+
+    private static final long serialVersionUID = -8847854414429745216L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(UserRequestFormPanel.class);
+
+    public UserRequestFormPanel(final String id, final PageReference pageRef, final UserRequestForm form) {
+        this(id, pageRef, form, true);
+    }
+
+    public UserRequestFormPanel(final String id, final PageReference pageRef, final UserRequestForm form,
+            final boolean showDetails) {
+        super(id);
+
+        IModel<List<UserRequestFormProperty>> formProps = new LoadableDetachableModel<List<UserRequestFormProperty>>() {
+
+            private static final long serialVersionUID = 3169142472626817508L;
+
+            @Override
+            protected List<UserRequestFormProperty> load() {
+                return form.getProperties();
+            }
+        };
+
+        ListView<UserRequestFormProperty> propView = new ListView<UserRequestFormProperty>("propView", formProps) {
+
+            private static final long serialVersionUID = 9101744072914090143L;
+
+            @Override
+            @SuppressWarnings({ "unchecked", "rawtypes" })
+            protected void populateItem(final ListItem<UserRequestFormProperty> item) {
+                final UserRequestFormProperty prop = item.getModelObject();
+
+                String label = StringUtils.isBlank(prop.getName()) ? prop.getId() : prop.getName();
+
+                FieldPanel field;
+                switch (prop.getType()) {
+                    case Boolean:
+                        field = new AjaxDropDownChoicePanel("value", label, new PropertyModel<String>(prop, "value") {
+
+                            private static final long serialVersionUID = -3743432456095828573L;
+
+                            @Override
+                            public String getObject() {
+                                return StringUtils.isBlank(prop.getValue())
+                                        ? null
+                                        : prop.getValue().equals("true") ? "Yes" : "No";
+                            }
+
+                            @Override
+                            public void setObject(final String object) {
+                                prop.setValue(String.valueOf(object.equalsIgnoreCase("yes")));
+                            }
+
+                        }, false).setChoices(Arrays.asList(new String[] { "Yes", "No" }));
+                        break;
+
+                    case Date:
+                        FastDateFormat formatter = FastDateFormat.getInstance(prop.getDatePattern());
+                        field = new AjaxDateTimeFieldPanel("value", label, new PropertyModel<Date>(prop, "value") {
+
+                            private static final long serialVersionUID = -3743432456095828573L;
+
+                            @Override
+                            public Date getObject() {
+                                try {
+                                    return StringUtils.isBlank(prop.getValue())
+                                            ? null
+                                            : formatter.parse(prop.getValue());
+                                } catch (ParseException e) {
+                                    LOG.error("Unparsable date: {}", prop.getValue(), e);
+                                    return null;
+                                }
+                            }
+
+                            @Override
+                            public void setObject(final Date object) {
+                                prop.setValue(formatter.format(object));
+                            }
+
+                        }, formatter);
+                        break;
+
+                    case Enum:
+                        field = new AjaxDropDownChoicePanel(
+                                "value", label, new PropertyModel<String>(prop, "value"), false).
+                                setChoiceRenderer(new MapChoiceRenderer(prop.getEnumValues().stream().
+                                        collect(Collectors.toMap(
+                                                UserRequestFormPropertyValue::getKey,
+                                                UserRequestFormPropertyValue::getValue)))).
+                                setChoices(new ArrayList<>(prop.getEnumValues().stream().
+                                        map(UserRequestFormPropertyValue::getKey).collect(Collectors.toList())));
+                        break;
+
+                    case Dropdown:
+                        field = new AjaxDropDownChoicePanel(
+                                "value", label, new PropertyModel<String>(prop, "value"), false).
+                                setChoiceRenderer(new MapChoiceRenderer(prop.getDropdownValues().stream().
+                                        collect(Collectors.toMap(
+                                                UserRequestFormPropertyValue::getKey,
+                                                UserRequestFormPropertyValue::getValue)))).
+                                setChoices(prop.getDropdownValues().stream().
+                                        map(UserRequestFormPropertyValue::getKey).collect(Collectors.toList()));
+                        break;
+
+                    case Long:
+                        field = new AjaxSpinnerFieldPanel.Builder<Long>().build(
+                                "value",
+                                label,
+                                Long.class,
+                                new PropertyModel<Long>(prop, "value") {
+
+                            private static final long serialVersionUID = -7688359318035249200L;
+
+                            @Override
+                            public Long getObject() {
+                                return StringUtils.isBlank(prop.getValue())
+                                        ? null
+                                        : NumberUtils.toLong(prop.getValue());
+                            }
+
+                            @Override
+                            public void setObject(final Long object) {
+                                prop.setValue(String.valueOf(object));
+                            }
+                        });
+                        break;
+
+                    case Password:
+                        field = new AjaxPasswordFieldPanel("value", label, new PropertyModel<>(prop, "value"), false);
+                        break;
+
+                    case String:
+                    default:
+                        field = new AjaxTextFieldPanel("value", label, new PropertyModel<>(prop, "value"), false);
+                        break;
+                }
+
+                field.setReadOnly(!prop.isWritable());
+                if (prop.isRequired()) {
+                    field.addRequiredLabel();
+                }
+
+                item.add(field);
+            }
+        };
+
+        AjaxLink<String> userDetails = new AjaxLink<String>("userDetails") {
+
+            private static final long serialVersionUID = -4804368561204623354L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                viewDetails(target);
+            }
+        };
+        MetaDataRoleAuthorizationStrategy.authorize(userDetails, ENABLE, IdRepoEntitlement.USER_READ);
+
+        boolean enabled = form.getUserTO() != null;
+        userDetails.setVisible(enabled && showDetails).setEnabled(enabled);
+
+        add(propView);
+        add(userDetails);
+    }
+
+    protected abstract void viewDetails(AjaxRequestTarget target);
+}
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable$UserRequestDetails.html b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable$UserRequestDetails.html
deleted file mode 100644
index 8988d6d..0000000
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable$UserRequestDetails.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!--
-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:panel>
-    <span wicket:id="fragContainer"/>
-    <wicket:fragment wicket:id="formProperties">
-      <form wicket:id="userRequestWrapForm" class="form-horizontal">
-        <span wicket:id="userRequestFormPanel"></span>
-        <div>
-          <input wicket:id="submit" type="submit" wicket:message="value:submit" class="btn btn-success pull-right"/>
-        </div>
-      </form>
-    </wicket:fragment>
-    <wicket:fragment wicket:id="formDetails">
-      <span><wicket:message key="executionId"/>: <label wicket:id="executionId"/></span>
-      <span><wicket:message key="startTime"/>: <label wicket:id="startTime"/></span>
-    </wicket:fragment>
-    <input wicket:id="delete" type="button" wicket:message="value:delete" class="btn btn-danger pull-left">
-  </wicket:panel>
-</html>
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 b3cd215..2bd1aa3 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
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -19,40 +20,58 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
     <section class="content" wicket:id="content">
-      <div id="flowable_wrapper" class="container-fluid password_reset_wrapper">
-        <div class="row">
-          <div class="col-md-6 col-md-offset-3">
-
-            <div class="page-header">
-              <h1>
-                <wicket:message key="bpmn.process.title">[BPMN_PROCESSES_TITLE]</wicket:message>
-              </h1>
+      <div class="box">
+        <div class="row clearfix">
+          <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 card-red">
+                    <h4><wicket:message key="bpmn.process.title">[BPMN_PROCESSES_TITLE]</wicket:message></h4>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <span wicket:id="bpmnProcesses" class="bpmn-process-span">[BPMN PROCESSES]</span> 
+                          <input wicket:id="start" wicket:message="value:start" type="button" class="btn btn-success">
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+              <div class="box-body">
+                <div class="box-header formcard">
+                  <header class="card-container card-red">
+                    <h4><wicket:message key="requests.to.be.approved">[ACTIVE REQUEST]</wicket:message></h4>
+                  </header>
+                  <div class="card-container-body">
+                    <div>
+                      <div class="col-xs-12">
+                        <div class="form-group">
+                          <div wicket:id="userRequests">
+                            <div wicket:id="userRequestDetails"/>
+                          </div>
+                        </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>
-              <span wicket:id="bpmnProcesses">[BPMN PROCESSES]</span> 
-              <input wicket:id="start" wicket:message="value:start" type="button" class="btn btn-success">
-            </div>
-
-          </div>
-        </div>
-
-        <div class="row">
-          <div class="col-md-6 col-md-offset-3">
-            <table>
-              <tbody>
-                <tr wicket:id="userRequests">
-                  <td>
-                    <div wicket:id="userRequestDetails"/>
-                  </td>
-                </tr>
-              </tbody>
-              <tfoot>
-                <tr>
-                  <td wicket:id="navigator"></td>
-                </tr>
-              </tfoot>
-            </table>
           </div>
         </div>
       </div>
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties
index 1cb340b..61a1f70 100644
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable.properties
@@ -21,3 +21,4 @@
 startTime=Start time
 delete=Delete
 start=Start
+requests.to.be.approved=Your active requests
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_it.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_it.properties
index 896af9e..5f11651 100644
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_it.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_it.properties
@@ -21,3 +21,4 @@
 startTime=Eseguito il
 delete=Cancella
 start=Esegui
+requests.to.be.approved=Richieste in lavorazione
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ja.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ja.properties
index f08cec1..64bcf41 100644
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ja.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ja.properties
@@ -21,3 +21,4 @@
 startTime=\u59cb\u307e\u308b\u6642\u9593
 delete=\u30af\u30ea\u30a2
 start=\u958b\u59cb
+requests.to.be.approved=\u3042\u306a\u305f\u306e\u30a2\u30af\u30c6\u30a3\u30d6\u306a\u30ea\u30af\u30a8\u30b9\u30c8
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_pt_BR.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_pt_BR.properties
index 0fcaf09..947d75e 100644
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_pt_BR.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_pt_BR.properties
@@ -21,3 +21,4 @@
 startTime=Hora de in\u00edcio
 delete=Limpa
 start=Come\u00e7ar
+requests.to.be.approved=Seus pedidos ativos
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ru.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ru.properties
index 9164ee7..5dba976 100644
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ru.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/Flowable_ru.properties
@@ -21,3 +21,4 @@
 startTime=\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e\u0435 \u0432\u0440\u0435\u043c\u044f
 delete=\u0440\u0430\u0441\u0441\u0435\u0435\u0442\u0441\u044f
 start=\u041d\u0430\u0447\u043d\u0438\u0442\u0435
+requests.to.be.approved=\u0412\u0430\u0448\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 \u0437\u0430\u043f\u0440\u043e\u0441\u044b
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html
index a89d762..f5058ac 100644
--- a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/pages/UserRequestForms.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
diff --git a/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserRequestDetails.html b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserRequestDetails.html
new file mode 100644
index 0000000..84c189a
--- /dev/null
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/UserRequestDetails.html
@@ -0,0 +1,39 @@
+<!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:panel>
+    <div class="card-container-padding">
+      <span wicket:id="fragContainer"/>
+      <wicket:fragment wicket:id="formProperties">
+        <form wicket:id="userRequestWrapForm" class="form-horizontal">
+          <span wicket:id="userRequestFormPanel"></span>
+          <div>
+            <input wicket:id="submit" type="submit" wicket:message="value:submit" class="btn btn-success pull-right"/>
+          </div>
+        </form>
+      </wicket:fragment>
+      <wicket:fragment wicket:id="formDetails">
+        <span><wicket:message key="executionId"/>: <label wicket:id="executionId"/></span>
+        <span><wicket:message key="startTime"/>: <label wicket:id="startTime"/></span>
+      </wicket:fragment>
+      <input wicket:id="delete" type="button" wicket:message="value:delete" class="btn btn-danger pull-left">
+    </div>
+  </wicket:panel>
+</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.html
similarity index 73%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html
copy to ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.html
index e77ed5d..7a9b26b 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/UserDetails$EditUserPasswordPanel.html
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -18,12 +19,14 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
-    <div id="editUserChangePassword">
-      <div class="alert alert-warning">
-        <i class="fas fa-exclamation-triangle"></i> <label wicket:id="warning">[warning]</label>
-      </div>
+    <div wicket:id="propView">
+      <span wicket:id="value">[value]</span>
+    </div>
 
-      <div wicket:id="passwordPanel">[change password panel]</div>
+    <div style="margin: 20px 0">
+      <a href="#" alt="user details" class="btn btn-success btn-circle btn-lg" wicket:id="userDetails" wicket:message="title:userDetails">
+        <i class="fas fa-eye"></i>
+      </a>
     </div>
   </wicket:panel>
 </html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.properties
similarity index 93%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.properties
index 56c00af..450ff50 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel.properties
@@ -14,4 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+userDetails=User details
+userForm=Edit User
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_it.properties
similarity index 92%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_it.properties
index 56c00af..92c475d 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_it.properties
@@ -14,4 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+userDetails=Dettagli utente
+userForm=Modifica utente
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_ja.properties
similarity index 87%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
copy to ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_ja.properties
index 56c00af..5a9cc2d 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_ja.properties
@@ -14,4 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Auxiliary classes
+userDetails=\u30e6\u30fc\u30b6\u30fc\u8a73\u7d30
+userForm=\u30e6\u30fc\u30b6\u30fc\u3092\u7de8\u96c6
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_pt_BR.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_pt_BR.properties
similarity index 91%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_pt_BR.properties
rename to ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_pt_BR.properties
index c91f75c..00a8971 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/EnduserAuxClasses_pt_BR.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_pt_BR.properties
@@ -14,4 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-auxClasses.palette=Classes auxiliares
+userDetails=Detalhes do Usu\u00e1rio
+userForm=Detalhes do Usu\u00e1rio
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_ru.properties
similarity index 71%
copy from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
copy to ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_ru.properties
index cf3ca73..89dd721 100644
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SelfPwdResetPanel_pt_BR.properties
+++ b/ext/flowable/client-enduser/src/main/resources/org/apache/syncope/ext/client/common/ui/panels/UserRequestFormPanel_ru.properties
@@ -14,12 +14,5 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-username=Nome de usu\u00e1rio\n
-securityQuestion=Pergunta de Seguran\u00e7a
-securityAnswer=Resposta de seguran\u00e7a
-reload=recarregar
-not.loading=N\u00e3o est\u00e1 carregando?
-submit=Enviar
-cancel=Cancelar
-self.pwd.reset.success=Senha redefinida com sucesso
-domain=Domain
+userDetails=\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435
+userForm=\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435
diff --git a/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.java b/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.java
index ca89994..f521aff 100644
--- a/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.java
+++ b/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.java
@@ -18,12 +18,12 @@
  */
 package org.apache.syncope.client.enduser.panels;
 
-import org.apache.syncope.client.ui.commons.panels.AbstractOIDCSSOLoginFormPanel;
 import org.apache.syncope.client.ui.commons.BaseSession;
+import org.apache.syncope.client.ui.commons.panels.AbstractOIDCSSOLoginFormPanel;
 
 public class OIDCSSOLoginFormPanel extends AbstractOIDCSSOLoginFormPanel {
 
-    private static final long serialVersionUID = 4312771213433968542L;
+    private static final long serialVersionUID = 1154933731474854671L;
 
     public OIDCSSOLoginFormPanel(final String id, final BaseSession session) {
         super(id, session);
diff --git a/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserCodeConsumerResource.java b/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserCodeConsumerResource.java
index 2e7055c..f542b0b 100644
--- a/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserCodeConsumerResource.java
+++ b/ext/oidcc4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserCodeConsumerResource.java
@@ -21,7 +21,7 @@
 import com.fasterxml.jackson.core.JsonProcessingException;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.enduser.pages.OIDCClientLogin;
-import org.apache.syncope.client.enduser.pages.Self;
+import org.apache.syncope.client.enduser.pages.SelfRegistration;
 import org.apache.syncope.client.ui.commons.annotations.Resource;
 import org.apache.syncope.client.ui.commons.panels.OIDCC4UIConstants;
 import org.apache.syncope.client.ui.commons.resources.oidcc4ui.CodeConsumerResource;
@@ -44,7 +44,7 @@
     @Override
     protected Pair<Class<? extends WebPage>, PageParameters> getSelfRegInfo(final UserTO newUser)
             throws JsonProcessingException {
-
-        return Pair.of(Self.class, new PageParameters().add(Self.NEW_USER_PARAM, MAPPER.writeValueAsString(newUser)));
+        return Pair.of(SelfRegistration.class,
+                new PageParameters().add(SelfRegistration.NEW_USER_PARAM, MAPPER.writeValueAsString(newUser)));
     }
 }
diff --git a/ext/oidcc4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.html b/ext/oidcc4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.html
index ce0dbe8..9d4d6af 100644
--- a/ext/oidcc4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.html
+++ b/ext/oidcc4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/OIDCSSOLoginFormPanel.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -17,7 +18,9 @@
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-  <wicket:extend>
-
-  </wicket:extend>
+  <wicket:panel>
+    <div class="form-group">
+      <span wicket:id="ops"></span>
+    </div>
+  </wicket:panel>
 </html>
diff --git a/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.java b/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.java
index b94e979..ade3485 100644
--- a/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.java
+++ b/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.java
@@ -18,12 +18,12 @@
  */
 package org.apache.syncope.client.enduser.panels;
 
-import org.apache.syncope.client.ui.commons.panels.AbstractSAMLSSOLoginFormPanel;
 import org.apache.syncope.client.ui.commons.BaseSession;
+import org.apache.syncope.client.ui.commons.panels.AbstractSAMLSSOLoginFormPanel;
 
 public class SAMLSSOLoginFormPanel extends AbstractSAMLSSOLoginFormPanel {
 
-    private static final long serialVersionUID = 6825891694497952535L;
+    private static final long serialVersionUID = -5252094098970677128L;
 
     public SAMLSSOLoginFormPanel(final String id, final BaseSession session) {
         super(id, session);
diff --git a/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserAssertionConsumerResource.java b/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserAssertionConsumerResource.java
index a072111..2d74159 100644
--- a/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserAssertionConsumerResource.java
+++ b/ext/saml2sp4ui/client-enduser/src/main/java/org/apache/syncope/client/enduser/resources/EnduserAssertionConsumerResource.java
@@ -21,7 +21,7 @@
 import com.fasterxml.jackson.core.JsonProcessingException;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.enduser.pages.SAML2SPLogin;
-import org.apache.syncope.client.enduser.pages.Self;
+import org.apache.syncope.client.enduser.pages.SelfRegistration;
 import org.apache.syncope.client.ui.commons.SAML2SP4UIConstants;
 import org.apache.syncope.client.ui.commons.annotations.Resource;
 import org.apache.syncope.client.ui.commons.resources.saml2sp4ui.AssertionConsumerResource;
@@ -44,7 +44,7 @@
     @Override
     protected Pair<Class<? extends WebPage>, PageParameters> getSelfRegInfo(final UserTO newUser)
             throws JsonProcessingException {
-
-        return Pair.of(Self.class, new PageParameters().add(Self.NEW_USER_PARAM, MAPPER.writeValueAsString(newUser)));
+        return Pair.of(SelfRegistration.class,
+                new PageParameters().add(SelfRegistration.NEW_USER_PARAM, MAPPER.writeValueAsString(newUser)));
     }
 }
diff --git a/ext/saml2sp4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.html b/ext/saml2sp4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.html
index ce0dbe8..6290455 100644
--- a/ext/saml2sp4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.html
+++ b/ext/saml2sp4ui/client-enduser/src/main/resources/org/apache/syncope/client/enduser/panels/SAMLSSOLoginFormPanel.html
@@ -1,3 +1,4 @@
+<!DOCTYPE html>
 <!--
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
@@ -17,7 +18,9 @@
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-  <wicket:extend>
-
-  </wicket:extend>
+  <wicket:panel>
+    <div class="form-group">
+      <span wicket:id="idps"></span>
+    </div>
+  </wicket:panel>  
 </html>
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUITCase.java
similarity index 99%
rename from fit/core-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUITCase.java
rename to fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUITCase.java
index fa283fa..eed1c08 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/ui/AbstractUITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractUITCase.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.fit.ui;
+package org.apache.syncope.fit;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.fail;
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
index 59575de..654d677 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
@@ -41,7 +41,7 @@
 import org.apache.syncope.common.keymaster.client.self.SelfKeymasterClientContext;
 import org.apache.syncope.common.keymaster.client.zookeeper.ZookeeperKeymasterClientContext;
 import org.apache.syncope.common.rest.api.service.SyncopeService;
-import org.apache.syncope.fit.ui.AbstractUITCase;
+import org.apache.syncope.fit.AbstractUITCase;
 import org.apache.wicket.util.tester.FormTester;
 import org.apache.wicket.util.tester.WicketTester;
 import org.junit.jupiter.api.BeforeAll;
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractTypesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractTypesITCase.java
index 787cd44..13fde09 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractTypesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractTypesITCase.java
@@ -42,7 +42,7 @@
     }
 
     protected void browsingToRelationshipType() {
-        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types");
+        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types", false);
         TESTER.assertRenderedPage(Types.class);
 
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:0:link");
@@ -51,7 +51,7 @@
     }
 
     protected void browsingToAnyTypes() {
-        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types");
+        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types", false);
         TESTER.assertRenderedPage(Types.class);
 
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:1:link");
@@ -60,7 +60,7 @@
     }
 
     protected void browsingToAnyTypeClasses() {
-        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types");
+        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types", false);
         TESTER.assertRenderedPage(Types.class);
 
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:2:link");
@@ -69,7 +69,7 @@
     }
 
     protected void browsingToPlainSchemas() {
-        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types");
+        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types", false);
         TESTER.assertRenderedPage(Types.class);
 
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
@@ -78,7 +78,7 @@
     }
 
     protected void browsingToVirtualSchemas() {
-        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types");
+        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types", false);
         TESTER.assertRenderedPage(Types.class);
 
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:3:link");
@@ -114,16 +114,14 @@
         browsingToAnyTypeClasses();
 
         TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
-        TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer",
-                Modal.class);
+        TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
 
-        final FormTester formTester = TESTER.newFormTester(
+        FormTester formTester = TESTER.newFormTester(
                 "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:form");
         formTester.setValue("content:anyTypeClassDetailsPanel:form:key:textField", name);
 
         TESTER.clearFeedbackMessages();
-        TESTER.clickLink(
-                "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:dialog:footer:inputs:0:submit");
+        TESTER.clickLink("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:dialog:footer:inputs:0:submit");
         assertSuccessMessage();
         TESTER.clearFeedbackMessages();
     }
@@ -132,15 +130,13 @@
         browsingToAnyTypes();
 
         TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
-        TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer",
-                Modal.class);
+        TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
 
         final FormTester formTester = TESTER.newFormTester(
                 "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:form");
         formTester.setValue("content:anyTypeDetailsPanel:container:form:key:textField", name);
 
-        TESTER.clickLink(
-                "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:dialog:footer:inputs:0:submit");
+        TESTER.clickLink("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:dialog:footer:inputs:0:submit");
         assertSuccessMessage();
         TESTER.clearFeedbackMessages();
     }
@@ -150,8 +146,7 @@
 
         TESTER.clickLink("body:content:tabbedPanel:panel:container:content:add");
 
-        TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer",
-                Modal.class);
+        TESTER.assertComponent("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer", Modal.class);
 
         final FormTester formTester = TESTER.newFormTester(
                 "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:form");
@@ -159,8 +154,7 @@
         formTester.setValue(
                 "content:relationshipTypeDetails:container:form:description:textField", "test relationshipType");
 
-        TESTER.clickLink(
-                "body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:dialog:footer:inputs:0:submit");
+        TESTER.clickLink("body:content:tabbedPanel:panel:outerObjectsRepeater:0:outer:dialog:footer:inputs:0:submit");
 
         assertSuccessMessage();
         TESTER.clearFeedbackMessages();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AjaxBrowseITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AjaxBrowseITCase.java
index 0347d60..e59aac8 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AjaxBrowseITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AjaxBrowseITCase.java
@@ -58,28 +58,28 @@
 
         TESTER.assertRenderedPage(Dashboard.class);
 
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.assertRenderedPage(Realms.class);
 
-        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage");
+        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage", false);
         TESTER.assertRenderedPage(Topology.class);
 
-        TESTER.clickLink("body:reportsLI:reports");
+        TESTER.clickLink("body:reportsLI:reports", false);
         TESTER.assertRenderedPage(Reports.class);
 
-        TESTER.clickLink("body:configurationLI:configurationUL:logsLI:logs");
+        TESTER.clickLink("body:configurationLI:configurationUL:logsLI:logs", false);
         TESTER.assertRenderedPage(Logs.class);
 
-        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types");
+        TESTER.clickLink("body:configurationLI:configurationUL:typesLI:types", false);
         TESTER.assertRenderedPage(Types.class);
 
-        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
+        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security", false);
         TESTER.assertRenderedPage(Security.class);
 
-        TESTER.clickLink("body:configurationLI:configurationUL:policiesLI:policies");
+        TESTER.clickLink("body:configurationLI:configurationUL:policiesLI:policies", false);
         TESTER.assertRenderedPage(Policies.class);
 
-        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications");
+        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications", false);
         TESTER.assertRenderedPage(Notifications.class);
     }
 }
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 140bbd3..2f53c30 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
@@ -42,7 +42,7 @@
 
     @Test
     public void filteredSearch() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
 
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:3:link");
 
@@ -62,7 +62,7 @@
 
     @Test
     public void clickToClonePrinter() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:3:link");
 
         Component component = findComponentByProp("key", CONTAINER
@@ -84,7 +84,7 @@
 
     @Test
     public void editPrinter() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:3:link");
 
         Component component = findComponentByProp("key", CONTAINER
@@ -136,7 +136,7 @@
 
     @Test
     public void checkDeletePrinterLink() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:3:link");
 
         Component component = findComponentByProp("key", CONTAINER
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypeClassesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypeClassesITCase.java
index 02bb09a..538e308 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypeClassesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypeClassesITCase.java
@@ -86,8 +86,8 @@
 
     @Test
     public void update() {
-        final String plainSchema = "anyPlainSchema";
-        final String name = "anyTypeClassToUpdate";
+        String plainSchema = "anyPlainSchema";
+        String name = "anyTypeClassToUpdate";
         createAnyTypeClassWithoutSchema(name);
         createPlainSchema(plainSchema);
         browsingToAnyTypeClasses();
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java
index 63f4147..732f8bb 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AnyTypesITCase.java
@@ -87,7 +87,7 @@
         TESTER.assertLabel(component.getPageRelativePath() + ":cells:3:cell", "[csv]");
 
         // issue SYNCOPE-1111
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.assertRenderedPage(Realms.class);
         TESTER.assertLabel(
                 "body:content:body:container:content:tabbedPanel:tabs-container:tabs:4:link:title",
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BatchesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BatchesITCase.java
index 0021a28..05d5f43 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BatchesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/BatchesITCase.java
@@ -44,7 +44,7 @@
 
     @Test
     public void users() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -69,7 +69,7 @@
 
     @Test
     public void userResource() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -123,7 +123,7 @@
 
     private static void userStatusBatch(final int index, final String resource) {
         // suspend 
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -190,7 +190,7 @@
         assertEquals(resource, StatusBean.class.cast(component.getDefaultModelObject()).getResource());
 
         // re-activate
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         component = findComponentByProp("username", CONTAINER
@@ -254,7 +254,7 @@
 
     @Test
     public void groupResource() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:2:link");
 
         Component component = findComponentByProp("name", CONTAINER
@@ -303,7 +303,7 @@
 
     @Test
     public void printerResource() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:3:link");
 
         Component component = findComponentByProp("key", CONTAINER
@@ -353,7 +353,7 @@
 
     @Test
     public void executePropagationTask() {
-        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage");
+        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage", false);
 
         Component component = findComponentByProp("key", "body:resources", "resource-testdb");
         assertNotNull(component);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/DisplayAttributesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/DisplayAttributesITCase.java
index dbce025..b3ac491 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/DisplayAttributesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/DisplayAttributesITCase.java
@@ -29,7 +29,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.assertRenderedPage(Realms.class);
     }
 
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 7601f5e..4c2c900 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
@@ -44,7 +44,7 @@
 
     @Test
     public void read() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:2:link");
 
         Component component = findComponentByProp("name", CONTAINER
@@ -74,7 +74,7 @@
 
     @Test
     public void filteredSearch() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:2:link");
 
         TESTER.clickLink(
@@ -92,7 +92,7 @@
     }
 
     private static void cloneGroup(final String group) {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:2:link");
 
         Component component = findComponentByProp("name", CONTAINER
@@ -245,7 +245,7 @@
 
     @Test
     public void checkDeleteGroupLink() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:2:link");
 
         Component component = findComponentByProp("name", CONTAINER
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java
index fac17db..7beb2f5 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LinkedAccountsITCase.java
@@ -102,7 +102,7 @@
     public void cleanUp() {
         try {
             SyncopeConsoleSession.get().getService(UserService.class).delete(user.getKey());
-        } catch (final SyncopeClientException e) {
+        } catch (SyncopeClientException e) {
             if (e.getType() != ClientExceptionType.NotFound) {
                 throw e;
             }
@@ -112,7 +112,7 @@
     @Test
     public void createLinkedAccountAndMergeWithUser() {
         // Locate and select first user
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component verdiUserComponent = findComponentByProp("username", CONTAINER
@@ -183,10 +183,8 @@
         try {
             userService.read(user.getKey());
             fail("User must have been deleted; expect an exception here");
-        } catch (final SyncopeClientException e) {
-            if (e.getType() != ClientExceptionType.NotFound) {
-                fail(e.getMessage());
-            }
+        } catch (SyncopeClientException e) {
+            assertEquals(ClientExceptionType.NotFound, e.getType());
         }
         // User must include merged accounts now
         UserTO verdi = userService.read("verdi");
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
index 281d714..b2131c5 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/LogsITCase.java
@@ -42,7 +42,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:configurationLI:configurationUL:logsLI:logs");
+        TESTER.clickLink("body:configurationLI:configurationUL:logsLI:logs", false);
         TESTER.assertRenderedPage(Logs.class);
     }
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
index d059d04..118513f 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/NotificationsITCase.java
@@ -35,7 +35,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications");
+        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications", false);
         TESTER.assertRenderedPage(Notifications.class);
     }
 
@@ -110,7 +110,7 @@
         formTester.submit("content:form:buttons:finish");
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
-        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications");
+        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications", false);
     }
 
     @Test
@@ -147,7 +147,7 @@
 
     @Test
     public void execute() {
-        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications");
+        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications", false);
 
         Component result = findComponentByProp("subject",
                 "body:content:tabbedPanel:panel:container:content:searchContainer:resultTable:tablePanel:groupForm:"
@@ -178,7 +178,7 @@
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications");
+        TESTER.clickLink("body:configurationLI:configurationUL:notificationsLI:notifications", false);
 
         result = findComponentByProp("subject", "body:content:tabbedPanel:panel:container:content:"
                 + "searchContainer:resultTable:tablePanel:groupForm:checkgroup:dataTable", "Password Reset request");
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ParametersITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ParametersITCase.java
index 26fd172..309664c 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ParametersITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ParametersITCase.java
@@ -34,7 +34,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:keymasterLI:keymasterUL:parametersLI:parameters");
+        TESTER.clickLink("body:keymasterLI:keymasterUL:parametersLI:parameters", false);
         TESTER.assertRenderedPage(Parameters.class);
     }
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
index 708e31e..7e37ba2 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/PoliciesITCase.java
@@ -37,7 +37,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:configurationLI:configurationUL:policiesLI:policies");
+        TESTER.clickLink("body:configurationLI:configurationUL:policiesLI:policies", false);
         TESTER.assertRenderedPage(Policies.class);
     }
 
@@ -741,7 +741,7 @@
         composeDefaultAccountPolicy(name);
 
         // goto realms
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.assertRenderedPage(Realms.class);
 
         // edit root realm
@@ -844,7 +844,7 @@
         assertNull(component);
 
         // delete default policy
-        TESTER.clickLink("body:configurationLI:configurationUL:policiesLI:policies");
+        TESTER.clickLink("body:configurationLI:configurationUL:policiesLI:policies", false);
         TESTER.assertRenderedPage(Policies.class);
         deleteAccountPolicy(name);
     }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RealmsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RealmsITCase.java
index 8b4df01..a77eae8 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RealmsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RealmsITCase.java
@@ -34,7 +34,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.assertRenderedPage(Realms.class);
     }
 
@@ -65,7 +65,7 @@
                 + "0:action:action", Constants.ON_CLICK);
 
         // remove the new realm just created
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
 
         TESTER.executeAjaxEvent(
                 "body:content:realmChoicePanel:container:realmsFragment:realms:btn", Constants.ON_CLICK);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ReportsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ReportsITCase.java
index 04e1a5d..c1766d2 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ReportsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/ReportsITCase.java
@@ -35,7 +35,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:reportsLI:reports");
+        TESTER.clickLink("body:reportsLI:reports", false);
         TESTER.assertRenderedPage(Reports.class);
     }
 
@@ -63,11 +63,11 @@
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:reportsLI:reports");
+        TESTER.clickLink("body:reportsLI:reports", false);
     }
 
     private static void delete(final String name) {
-        TESTER.clickLink("body:reportsLI:reports");
+        TESTER.clickLink("body:reportsLI:reports", false);
 
         Component result = findComponentByProp(
                 "name", "body:content:tabbedPanel:panel:firstLevelContainer:first:container:"
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RolesITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RolesITCase.java
index c3d350d..93d02c8 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RolesITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/RolesITCase.java
@@ -34,7 +34,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
+        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security", false);
         TESTER.assertRenderedPage(Security.class);
     }
 
@@ -70,7 +70,7 @@
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
+        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security", false);
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java
index c8c0d25..e37a24f 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/SecurityQuestionsITCase.java
@@ -37,7 +37,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
+        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security", false);
         TESTER.assertRenderedPage(Security.class);
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:4:link");
     }
@@ -59,7 +59,7 @@
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security");
+        TESTER.clickLink("body:configurationLI:configurationUL:securityLI:security", false);
         TESTER.clickLink("body:content:tabbedPanel:tabs-container:tabs:4:link");
     }
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
index 88556b9..a764406 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/TopologyITCase.java
@@ -43,7 +43,7 @@
     @BeforeEach
     public void login() {
         doLogin(ADMIN_UNAME, ADMIN_PWD);
-        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage");
+        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage", false);
         TESTER.assertRenderedPage(Topology.class);
     }
 
@@ -184,7 +184,7 @@
         assertSuccessMessage();
 
         TESTER.cleanupFeedbackMessages();
-        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage");
+        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage", false);
 
         Component component = findComponentByProp("key", "body:resources", res);
         assertNotNull(component);
@@ -289,7 +289,7 @@
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage");
+        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage", false);
         component = findComponentByProp("key", "body:resources", res);
         assertNull(component);
     }
@@ -397,7 +397,7 @@
                 + "outerObjectsRepeater:1:outer:container:content:togglePanelContainer:container:"
                 + "actions:actions:actionRepeater:2:action:action");
 
-        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage");
+        TESTER.clickLink("body:idmPages:0:idmPageLI:idmPage", false);
 
         component = findComponentByProp("key", "body:resources", "resource-testdb");
         assertNotNull(component);
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 7a922b8..3f116dd 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
@@ -45,7 +45,7 @@
 
     @Test
     public void filteredSearch() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         TESTER.clickLink(
@@ -68,7 +68,7 @@
 
     @Test
     public void forceChangePassword() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -85,7 +85,7 @@
 
     @Test
     public void clickToCloneUser() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -109,7 +109,7 @@
 
     @Test
     public void editRelationships() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -189,7 +189,7 @@
 
     @Test
     public void editUser() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -255,7 +255,7 @@
 
     @Test
     public void editUserMembership() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.executeAjaxEvent("body:content:realmChoicePanel:container"
                 + ":realmsFragment:realms:btn", Constants.ON_CLICK);
         TESTER.executeAjaxEvent("body:content:realmChoicePanel:container"
@@ -349,7 +349,7 @@
 
     @Test
     public void editUserMemberships() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.executeAjaxEvent("body:content:realmChoicePanel:container"
                 + ":realmsFragment:realms:btn", Constants.ON_CLICK);
         TESTER.executeAjaxEvent("body:content:realmChoicePanel:container"
@@ -571,7 +571,7 @@
 
     @Test
     public void checkDeleteUsrLink() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -586,7 +586,7 @@
 
     @Test
     public void editDateTimeField() {
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -679,7 +679,7 @@
     public void changePassword() {
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         Component component = findComponentByProp("username", CONTAINER
@@ -705,7 +705,7 @@
         assertSuccessMessage();
         TESTER.cleanupFeedbackMessages();
 
-        TESTER.clickLink("body:realmsLI:realms");
+        TESTER.clickLink("body:realmsLI:realms", false);
         TESTER.clickLink("body:content:body:container:content:tabbedPanel:tabs-container:tabs:1:link");
 
         component = findComponentByProp("username", CONTAINER
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PlainSchemaITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PlainSchemaITCase.java
index 1d66c02..0f643cc 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PlainSchemaITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PlainSchemaITCase.java
@@ -184,58 +184,65 @@
 
         UserTO userTO = createUser(userCR).getEntity();
         assertNotNull(userTO);
-
-        UserUR userUR = new UserUR();
-        userUR.setKey(userTO.getKey());
-        // validation OK - application/pdf -> application/pdf
-        userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryPDF",
-                Base64.getEncoder().encodeToString(
-                        IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.pdf"))))).
-                operation(PatchOperation.ADD_REPLACE).
-                build());
-
-        updateUser(userUR);
-        assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryPDF"));
-
-        userUR = new UserUR();
-        userUR.setKey(userTO.getKey());
-        // validation KO - text/html -> application/pdf
         try {
+            UserUR userUR = new UserUR();
+            userUR.setKey(userTO.getKey());
+            // validation OK - application/pdf -> application/pdf
             userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryPDF",
                     Base64.getEncoder().encodeToString(
-                            IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.html"))))).
+                            IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.pdf"))))).
                     operation(PatchOperation.ADD_REPLACE).
                     build());
 
             updateUser(userUR);
-            fail("This should not be reacheable");
-        } catch (SyncopeClientException e) {
-            assertEquals(ClientExceptionType.InvalidValues, e.getType());
+            assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryPDF"));
+
+            userUR = new UserUR();
+            userUR.setKey(userTO.getKey());
+            // validation KO - text/html -> application/pdf
+            try {
+                userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryPDF",
+                        Base64.getEncoder().encodeToString(
+                                IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.html"))))).
+                        operation(PatchOperation.ADD_REPLACE).
+                        build());
+
+                updateUser(userUR);
+                fail("This should not be reacheable");
+            } catch (SyncopeClientException e) {
+                assertEquals(ClientExceptionType.InvalidValues, e.getType());
+            }
+
+            userUR = new UserUR();
+            userUR.setKey(userTO.getKey());
+            // validation ok - application/json -> application/json
+            userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryJSON",
+                    Base64.getEncoder().encodeToString(
+                            IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.json"))))).
+                    operation(PatchOperation.ADD_REPLACE).
+                    build());
+
+            updateUser(userUR);
+            assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryJSON"));
+
+            userUR = new UserUR();
+            userUR.setKey(userTO.getKey());
+            // no validation - application/xml -> application/json
+            userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryJSON2",
+                    Base64.getEncoder().encodeToString(
+                            IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.xml"))))).
+                    operation(PatchOperation.ADD_REPLACE).
+                    build());
+
+            updateUser(userUR);
+            assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryJSON2"));
+        } finally {
+            userService.delete(userTO.getKey());
+
+            schemaService.delete(SchemaType.PLAIN, schemaTOpdf.getKey());
+            schemaService.delete(SchemaType.PLAIN, schemaTOjson.getKey());
+            schemaService.delete(SchemaType.PLAIN, schemaTOjson2.getKey());
         }
-
-        userUR = new UserUR();
-        userUR.setKey(userTO.getKey());
-        // validation ok - application/json -> application/json
-        userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryJSON",
-                Base64.getEncoder().encodeToString(
-                        IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.json"))))).
-                operation(PatchOperation.ADD_REPLACE).
-                build());
-
-        updateUser(userUR);
-        assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryJSON"));
-
-        userUR = new UserUR();
-        userUR.setKey(userTO.getKey());
-        // no validation - application/xml -> application/json
-        userUR.getPlainAttrs().add(new AttrPatch.Builder(attr("BinaryJSON2",
-                Base64.getEncoder().encodeToString(
-                        IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.xml"))))).
-                operation(PatchOperation.ADD_REPLACE).
-                build());
-
-        updateUser(userUR);
-        assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryJSON2"));
     }
 
     @Test
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
index 1637720..b5acc24 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
@@ -494,6 +494,7 @@
         assertNotNull(schemaTO);
 
         ResourceTO ldap = resourceService.read(RESOURCE_NAME_LDAP);
+        UserTO userTO = null;
         try {
             // 1. clone the LDAP resource and add some sensible mappings
             ProvisionTO provisionGroup =
@@ -522,7 +523,7 @@
             // 1. create group with the new resource assigned
             GroupCR groupCR = new GroupCR();
             groupCR.setName("SYNCOPEGROUP1473-" + getUUIDString());
-            groupCR.setRealm("/");
+            groupCR.setRealm(SyncopeConstants.ROOT_REALM);
             groupCR.getResources().add(ldap.getKey());
 
             GroupTO groupTO = createGroup(groupCR).getEntity();
@@ -534,7 +535,7 @@
             userCR.getResources().add(ldap.getKey());
             userCR.getMemberships().add(new MembershipTO.Builder(groupTO.getKey()).build());
 
-            UserTO userTO = createUser(userCR).getEntity();
+            userTO = createUser(userCR).getEntity();
             assertNotNull(userTO);
 
             // 3. check attributes prepared for propagation
@@ -571,6 +572,10 @@
         } finally {
             try {
                 resourceService.delete(ldap.getKey());
+                if (userTO != null) {
+                    userService.delete(userTO.getKey());
+                }
+                schemaService.delete(SchemaType.PLAIN, schemaTO.getKey());
             } catch (Exception ignore) {
                 // ignore
             }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java
index 43f8288..1d5ea14 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AbstractEnduserITCase.java
@@ -23,10 +23,6 @@
 import com.giffing.wicket.spring.boot.starter.app.classscanner.candidates.WicketClassCandidatesHolder;
 import com.giffing.wicket.spring.boot.starter.configuration.extensions.core.settings.general.GeneralSettingsProperties;
 import com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.boot.actuator.WicketEndpointRepositoryDefault;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.apache.syncope.client.enduser.SyncopeWebApplication;
 import org.apache.syncope.client.enduser.commons.PreviewUtils;
@@ -36,35 +32,30 @@
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.client.ui.commons.ApplicationContextProvider;
 import org.apache.syncope.client.ui.commons.MIMETypesLoader;
-import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.syncope.common.keymaster.client.self.SelfKeymasterClientContext;
 import org.apache.syncope.common.keymaster.client.zookeeper.ZookeeperKeymasterClientContext;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.request.UserCR;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.syncope.common.rest.api.service.SecurityQuestionService;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
 import org.apache.syncope.common.rest.api.service.UserService;
-import org.apache.syncope.fit.ui.AbstractUITCase;
+import org.apache.syncope.fit.AbstractUITCase;
 import org.apache.wicket.util.tester.FormTester;
 import org.apache.wicket.util.tester.WicketTester;
+import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.BeforeAll;
 import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
 
 public abstract class AbstractEnduserITCase extends AbstractUITCase {
 
-    protected static final String ENV_KEY_CONTENT_TYPE = "jaxrsContentType";
-
-    protected static SyncopeClientFactoryBean clientFactory;
-
-    protected static SyncopeClient adminClient;
-
-    protected static UserService userService;
-
-    protected static SecurityQuestionService securityQuestionService;
-
     @ImportAutoConfiguration(classes = { SelfKeymasterClientContext.class, ZookeeperKeymasterClientContext.class })
     @Configuration
     public static class SyncopeEnduserWebApplicationTestConfig {
@@ -114,6 +105,14 @@
         }
     }
 
+    protected static SyncopeClientFactoryBean clientFactory;
+
+    protected static SyncopeClient adminClient;
+
+    protected static UserService userService;
+
+    protected static SecurityQuestionService securityQuestionService;
+
     @BeforeAll
     public static void setUp() {
         Locale.setDefault(Locale.ENGLISH);
@@ -133,11 +132,6 @@
     @BeforeAll
     public static void restSetup() {
         clientFactory = new SyncopeClientFactoryBean().setAddress(ADDRESS);
-
-        String envContentType = System.getProperty(ENV_KEY_CONTENT_TYPE);
-        if (StringUtils.isNotBlank(envContentType)) {
-            clientFactory.setContentType(envContentType);
-        }
         LOG.info("Performing IT with content type {}", clientFactory.getContentType().getMediaType());
 
         adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
@@ -182,7 +176,17 @@
         securityQuestionService = adminClient.getService(SecurityQuestionService.class);
     }
 
-    protected void doLogin(final String user, final String passwd) {
+    @AfterAll
+    public static void cleanUp() {
+        userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                fiql(SyncopeClient.getUserSearchConditionBuilder().
+                        is("username").equalTo("selfupdate").
+                        or("username").equalTo("selfpwdreset").
+                        or("username").equalTo("mustchangepassword").query()).
+                build()).getResult().forEach(user -> userService.delete(user.getKey()));
+    }
+
+    protected static void doLogin(final String user, final String passwd) {
         TESTER.startPage(Login.class);
         TESTER.assertRenderedPage(Login.class);
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AnonymousITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AnonymousITCase.java
new file mode 100644
index 0000000..2d22b27
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AnonymousITCase.java
@@ -0,0 +1,154 @@
+/*
+ * 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.fit.enduser;
+
+import static org.apache.syncope.fit.enduser.AbstractEnduserITCase.securityQuestionService;
+import static org.apache.syncope.fit.enduser.AbstractEnduserITCase.userService;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.apache.syncope.client.enduser.pages.Login;
+import org.apache.syncope.client.enduser.pages.SelfPasswordReset;
+import org.apache.syncope.client.enduser.pages.SelfRegistration;
+import org.apache.syncope.client.enduser.pages.SelfResult;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.request.StringReplacePatchItem;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.SecurityQuestionTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.wicket.util.tester.FormTester;
+import org.junit.jupiter.api.Test;
+
+public class AnonymousITCase extends AbstractEnduserITCase {
+
+    @Test
+    public void selfCreate() {
+        String username = "testUser";
+
+        TESTER.startPage(Login.class);
+        TESTER.assertRenderedPage(Login.class);
+
+        TESTER.clickLink("self-registration");
+        TESTER.assertRenderedPage(SelfRegistration.class);
+
+        String form = "body:contentWrapper:content:selfRegistrationPanel:form";
+        FormTester formTester = TESTER.newFormTester(form);
+
+        formTester.setValue("userDetailsPanelCard:contentPanel:username:textField", username);
+
+        formTester.setValue(
+                "userDetailsPanelCard:contentPanel:password:passwordPanel:passwordInnerForm:password:passwordField",
+                "Password123");
+        formTester.setValue(
+                "userDetailsPanelCard:contentPanel:password:passwordPanel:passwordInnerForm:confirmPassword:"
+                + "passwordField",
+                "Password123");
+
+        formTester.setValue(
+                "plainAttrsPanelCard:contentPanel:plainSchemas:schemas:6:panel:textField",
+                "Fullname");
+
+        formTester.setValue(
+                "plainAttrsPanelCard:contentPanel:plainSchemas:schemas:12:panel:textField",
+                "Surname");
+
+        formTester.setValue(
+                "plainAttrsPanelCard:contentPanel:plainSchemas:schemas:14:panel:textField",
+                username + "@syncope.apache.org");
+
+        try {
+            TESTER.executeAjaxEvent(form + ":submit", Constants.ON_CLICK);
+
+            TESTER.assertRenderedPage(SelfResult.class);
+
+            assertFalse(userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
+                    fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo(username).query()).
+                    build()).getResult().isEmpty());
+
+            assertNotNull(userService.read(username));
+
+            TESTER.cleanupFeedbackMessages();
+        } finally {
+            // cleanup
+            try {
+                userService.delete(username);
+            } catch (Exception e) {
+                // ignore
+            }
+        }
+    }
+
+    @Test
+    public void selfPasswordReset() {
+        SecurityQuestionTO question = securityQuestionService.read("887028ea-66fc-41e7-b397-620d7ea6dfbb");
+
+        UserTO selfpwdreset = userService.read("selfpwdreset");
+        userService.update(new UserUR.Builder(selfpwdreset.getKey()).
+                securityQuestion(new StringReplacePatchItem.Builder().value(question.getKey()).build()).
+                securityAnswer(new StringReplacePatchItem.Builder().value("ananswer").build()).
+                build());
+
+        TESTER.startPage(Login.class);
+        TESTER.assertRenderedPage(Login.class);
+
+        TESTER.clickLink("self-pwd-reset");
+
+        TESTER.assertRenderedPage(SelfPasswordReset.class);
+
+        String pwdResetForm = "body:contentWrapper:content:selfPwdResetForm";
+        FormTester formTester = TESTER.newFormTester(pwdResetForm);
+
+        // 1. set username to selfpwdreset
+        formTester.setValue("selfPasswordResetPanelCard:contentPanel:username", "selfpwdreset");
+        TESTER.executeAjaxEvent(
+                pwdResetForm + ":selfPasswordResetPanelCard:contentPanel:username", Constants.ON_BLUR);
+
+        // 2. check that the question has been populated
+        TESTER.assertModelValue(
+                pwdResetForm + ":selfPasswordResetPanelCard:contentPanel:securityQuestion",
+                question.getContent());
+
+        // 3. submit form and receive an error
+        TESTER.executeAjaxEvent(pwdResetForm + ":submit", Constants.ON_CLICK);
+        TESTER.assertErrorMessages("Invalid Security Answer");
+        TESTER.cleanupFeedbackMessages();
+
+        // 3.1 set the correct answer
+        formTester = TESTER.newFormTester(pwdResetForm);
+        formTester.setValue("selfPasswordResetPanelCard:contentPanel:username", "selfpwdreset");
+        TESTER.executeAjaxEvent(
+                pwdResetForm + ":selfPasswordResetPanelCard:contentPanel:username", Constants.ON_BLUR);
+        TESTER.assertModelValue(
+                pwdResetForm + ":selfPasswordResetPanelCard:contentPanel:securityQuestion",
+                question.getContent());
+        formTester.setValue("selfPasswordResetPanelCard:contentPanel:securityAnswer", "ananswer");
+        TESTER.executeAjaxEvent(
+                pwdResetForm + ":selfPasswordResetPanelCard:contentPanel:securityAnswer", Constants.ON_CHANGE);
+
+        // 4. submit form
+        TESTER.executeAjaxEvent(pwdResetForm + ":submit", Constants.ON_CLICK);
+
+        TESTER.assertRenderedPage(SelfResult.class);
+
+        TESTER.cleanupFeedbackMessages();
+    }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java
new file mode 100644
index 0000000..2ac7f98
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/AuthenticatedITCase.java
@@ -0,0 +1,122 @@
+/*
+ * 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.fit.enduser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.io.IOException;
+import org.apache.syncope.client.enduser.pages.Dashboard;
+import org.apache.syncope.client.enduser.pages.EditUser;
+import org.apache.syncope.client.enduser.pages.Login;
+import org.apache.syncope.client.enduser.pages.MustChangePassword;
+import org.apache.syncope.client.enduser.pages.SelfResult;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
+import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
+import org.apache.syncope.common.lib.request.UserUR;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.fit.FlowableDetector;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.util.tester.FormTester;
+import org.junit.jupiter.api.Test;
+
+public class AuthenticatedITCase extends AbstractEnduserITCase {
+
+    @Test
+    public void login() throws IOException {
+        TESTER.startPage(Login.class);
+        TESTER.assertRenderedPage(Login.class);
+
+        doLogin("bellini", "password");
+
+        TESTER.assertNoErrorMessage();
+        TESTER.assertRenderedPage(Dashboard.class);
+    }
+
+    @Test
+    public void mustChangePassword() {
+        UserTO mustchangepassword = userService.read("mustchangepassword");
+        userService.update(new UserUR.Builder(mustchangepassword.getKey()).
+                mustChangePassword(new BooleanReplacePatchItem.Builder().value(Boolean.TRUE).build()).build());
+
+        TESTER.startPage(Login.class);
+        doLogin("mustchangepassword", "password123");
+
+        TESTER.assertRenderedPage(MustChangePassword.class);
+
+        String changePwdForm = "body:contentWrapper:content:changePasswordPanel:changePassword";
+        TESTER.assertComponent(changePwdForm + ":password", AjaxPasswordFieldPanel.class);
+        TESTER.assertComponent(changePwdForm + ":confirmPassword", AjaxPasswordFieldPanel.class);
+
+        FormTester formTester = TESTER.newFormTester(changePwdForm);
+
+        assertNotNull(formTester);
+        // 1. set new password
+        formTester.setValue(findComponentById(changePwdForm + ":password", "passwordField"), "password124");
+        // 2. confirm password
+        formTester.setValue(findComponentById(changePwdForm + ":confirmPassword", "passwordField"), "password124");
+        // 3. submit form
+        TESTER.executeAjaxEvent(changePwdForm + ":submit", Constants.ON_CLICK);
+
+        TESTER.assertRenderedPage(SelfResult.class);
+
+        TESTER.cleanupFeedbackMessages();
+        TESTER.startPage(Login.class);
+
+        doLogin("mustchangepassword", "password124");
+
+        TESTER.assertNoErrorMessage();
+        TESTER.assertRenderedPage(Dashboard.class);
+    }
+
+    @Test
+    public void selfUpdate() {
+        String username = "selfupdate";
+        String newEmail = "selfupdate@email.com";
+
+        TESTER.startPage(Login.class);
+        doLogin(username, "password123");
+
+        TESTER.assertComponent("body:contentWrapper:content:userProfileInfo:userProfile", WebMarkupContainer.class);
+
+        TESTER.clickLink("body:sidebar:profileLI:profileUL:edituserLI:edituser", false);
+        TESTER.assertRenderedPage(EditUser.class);
+
+        String form = "body:contentWrapper:content:editUserPanel:form";
+        FormTester formTester = TESTER.newFormTester(form);
+        formTester.setValue(
+                "plainAttrsPanelCard:contentPanel:plainSchemas:schemas:4:panel:textField",
+                newEmail);
+
+        // check required fields were correctly set
+        TESTER.assertNoInfoMessage();
+        TESTER.assertNoErrorMessage();
+
+        TESTER.executeAjaxEvent(form + ":submit", Constants.ON_CLICK);
+
+        TESTER.assertRenderedPage(SelfResult.class);
+
+        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(SYNCOPE_SERVICE)
+                ? "active" : "created", userService.read(username).getStatus());
+        assertEquals(newEmail, userService.read(username).getPlainAttr("email").get().getValues().get(0));
+
+        TESTER.cleanupFeedbackMessages();
+    }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/EnduserITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/EnduserITCase.java
deleted file mode 100644
index 3c9545e..0000000
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/enduser/EnduserITCase.java
+++ /dev/null
@@ -1,305 +0,0 @@
-/*
- * 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.fit.enduser;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.client.enduser.pages.Login;
-import org.apache.syncope.client.enduser.pages.SelfPasswordReset;
-import org.apache.syncope.client.enduser.pages.MustChangePassword;
-import org.apache.syncope.client.lib.SyncopeClient;
-import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.common.lib.SyncopeConstants;
-import org.apache.syncope.common.lib.request.BooleanReplacePatchItem;
-import org.apache.syncope.common.lib.request.StringReplacePatchItem;
-import org.apache.syncope.common.lib.request.UserUR;
-import org.apache.syncope.common.lib.to.SecurityQuestionTO;
-import org.apache.syncope.common.lib.to.UserTO;
-import org.apache.syncope.common.rest.api.beans.AnyQuery;
-import org.apache.syncope.fit.FlowableDetector;
-import org.apache.wicket.extensions.markup.html.form.palette.component.Choices;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.PasswordTextField;
-import org.apache.wicket.markup.html.form.TextField;
-import org.apache.wicket.util.tester.FormTester;
-import org.junit.jupiter.api.Test;
-
-public class EnduserITCase extends AbstractEnduserITCase {
-
-    private static final String WIZARD_FORM = "body:wizard:form";
-
-    @Test
-    public void selfCreate() {
-        String username = "testUser";
-
-        TESTER.startPage(Login.class);
-        TESTER.assertRenderedPage(Login.class);
-
-        TESTER.clickLink("self-registration");
-
-        TESTER.assertComponent(WIZARD_FORM + ":view:username:textField", TextField.class);
-        FormTester formTester = TESTER.newFormTester(WIZARD_FORM);
-        assertNotNull(formTester);
-        formTester.setValue("view:username:textField", username);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        // check required field is correctly set
-        TESTER.assertNoInfoMessage();
-        TESTER.assertNoErrorMessage();
-
-        TESTER.assertComponent(WIZARD_FORM + ":view:auxClasses:paletteField:choices", Choices.class);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        TESTER.assertComponent(WIZARD_FORM + ":view:groupsContainer:groups:form:filter:textField",
-                TextField.class);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "fullname").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "surname").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "userId").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-
-        formTester = TESTER.newFormTester(WIZARD_FORM);
-        assertNotNull(formTester);
-        formTester.setValue(findComponentByMarkupId(WIZARD_FORM
-                + ":view:plainSchemas:tabs:0:body:content:schemas",
-                "fullname").getPageRelativePath().replace(WIZARD_FORM + ':', StringUtils.EMPTY) + ":textField",
-                "User fullname");
-        formTester.setValue(findComponentByMarkupId(WIZARD_FORM
-                + ":view:plainSchemas:tabs:0:body:content:schemas",
-                "surname").getPageRelativePath().replace(WIZARD_FORM + ':', StringUtils.EMPTY) + ":textField",
-                "User surname");
-        formTester.setValue(
-                findComponentByMarkupId(WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "userId").
-                        getPageRelativePath().replace(WIZARD_FORM + ':', StringUtils.EMPTY) + ":textField",
-                "test@email.com");
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        // check required fields were correctly set
-        TESTER.assertNoInfoMessage();
-        TESTER.assertNoErrorMessage();
-
-        TESTER.assertComponent(WIZARD_FORM
-                + ":view:derSchemas:tabs:0:body:content:schemas:0:panel:textField",
-                TextField.class);
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-        TESTER.assertComponent(WIZARD_FORM + ":view:virSchemas:tabs:0:body:content:schemas:0:panel:"
-                + "multiValueContainer:innerForm:content:field-label",
-                Label.class);
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-        TESTER.assertComponent(WIZARD_FORM + ":view:resources:paletteField:choices", Choices.class);
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:finish", Constants.ON_CLICK);
-
-        TESTER.assertRenderedPage(Login.class);
-        TESTER.assertComponent("login:username", TextField.class);
-
-        assertFalse(userService.search(new AnyQuery.Builder().realm(SyncopeConstants.ROOT_REALM).
-                fiql(SyncopeClient.getUserSearchConditionBuilder().is("username").equalTo(username).query()).
-                build()).getResult().isEmpty());
-
-        assertNotNull(userService.read(username));
-
-        TESTER.cleanupFeedbackMessages();
-
-        // cleanup
-        userService.delete(username);
-    }
-
-    @Test
-    public void selfPasswordReset() {
-        SecurityQuestionTO question = securityQuestionService.read("887028ea-66fc-41e7-b397-620d7ea6dfbb");
-
-        UserTO selfpwdreset = userService.read("selfpwdreset");
-        userService.update(new UserUR.Builder(selfpwdreset.getKey()).
-                securityQuestion(new StringReplacePatchItem.Builder().value(question.getKey()).build()).
-                securityAnswer(new StringReplacePatchItem.Builder().value("ananswer").build()).
-                build());
-
-        String pwdResetForm = "body:content:selfPwdResetForm";
-        TESTER.startPage(Login.class);
-        TESTER.assertRenderedPage(Login.class);
-
-        TESTER.clickLink("self-pwd-reset");
-
-        TESTER.assertRenderedPage(SelfPasswordReset.class);
-
-        TESTER.assertComponent(pwdResetForm + ":selfPwdResetPanel:username", TextField.class);
-        TESTER.assertComponent(pwdResetForm + ":selfPwdResetPanel:securityQuestion", TextField.class);
-
-        FormTester formTester = TESTER.newFormTester(pwdResetForm);
-        assertNotNull(formTester);
-
-        // 1. set username to selfpwdreset
-        formTester.setValue(findComponentById(pwdResetForm + ":selfPwdResetPanel", "username"), "selfpwdreset");
-
-        // 2. check that the question has been populated
-        TESTER.executeAjaxEvent(pwdResetForm + ":selfPwdResetPanel:username", Constants.ON_BLUR);
-        TESTER.assertModelValue(pwdResetForm + ":selfPwdResetPanel:securityQuestion", question.getContent());
-
-        // 3. submit form and receive an error
-        formTester = TESTER.newFormTester(pwdResetForm);
-        assertNotNull(formTester);
-        TESTER.executeAjaxEvent(pwdResetForm + ":selfPwdResetPanel:submit", Constants.ON_CLICK);
-        TESTER.assertErrorMessages("Invalid security answer");
-        TESTER.cleanupFeedbackMessages();
-
-        // 3.1 set the correct answer
-        formTester = TESTER.newFormTester(pwdResetForm);
-        assertNotNull(formTester);
-        TESTER.assertComponent(pwdResetForm + ":selfPwdResetPanel:securityAnswer", TextField.class);
-        formTester.setValue("selfPwdResetPanel:securityAnswer", "ananswer");
-        TESTER.executeAjaxEvent(pwdResetForm + ":selfPwdResetPanel:securityAnswer", Constants.ON_CHANGE);
-        TESTER.assertComponent(pwdResetForm + ":selfPwdResetPanel:securityAnswer", TextField.class);
-
-        // 4. submit form
-        TESTER.assertNoFeedbackMessage(0);
-        TESTER.assertNoErrorMessage();
-        TESTER.assertComponent(pwdResetForm + ":selfPwdResetPanel:submit", Button.class);
-        TESTER.executeAjaxEvent(pwdResetForm + ":selfPwdResetPanel:submit", Constants.ON_CLICK);
-        TESTER.assertRenderedPage(Login.class);
-        TESTER.assertComponent("login:username", TextField.class);
-
-        TESTER.cleanupFeedbackMessages();
-    }
-
-    @Test
-    public void mustChangePassword() {
-        UserTO mustchangepassword = userService.read("mustchangepassword");
-        userService.update(new UserUR.Builder(mustchangepassword.getKey()).
-                mustChangePassword(new BooleanReplacePatchItem.Builder().value(Boolean.TRUE).build()).build());
-
-        TESTER.startPage(Login.class);
-        doLogin("mustchangepassword", "password123");
-
-        TESTER.assertRenderedPage(MustChangePassword.class);
-
-        final String changePwdForm = "changePassword";
-        TESTER.assertComponent(changePwdForm + ":username", TextField.class);
-        TESTER.assertComponent(changePwdForm + ":password:passwordField", PasswordTextField.class);
-        TESTER.
-                assertComponent(changePwdForm + ":confirmPassword:passwordField", PasswordTextField.class);
-        TESTER.assertModelValue(changePwdForm + ":username", "mustchangepassword");
-
-        FormTester formTester = TESTER.newFormTester(changePwdForm);
-
-        assertNotNull(formTester);
-        // 1. set new password
-        formTester.setValue(findComponentById(changePwdForm + ":password", "passwordField"), "password124");
-        // 2. confirm password
-        formTester.setValue(findComponentById(changePwdForm + ":confirmPassword", "passwordField"),
-                "password124");
-        // 3. submit form
-        TESTER.executeAjaxEvent(changePwdForm + ":submit", Constants.ON_CLICK);
-
-        TESTER.assertRenderedPage(Login.class);
-        TESTER.assertComponent("login:username", TextField.class);
-
-        TESTER.cleanupFeedbackMessages();
-
-        doLogin("mustchangepassword", "password124");
-        TESTER.assertComponent(WIZARD_FORM + ":view:username:textField", TextField.class);
-    }
-
-    @Test
-    public void selfUpdate() {
-        String username = "selfupdate";
-        String newEmail = "selfupdate@email.com";
-
-        TESTER.startPage(Login.class);
-        doLogin(username, "password123");
-
-        TESTER.assertComponent(WIZARD_FORM + ":view:username:textField", TextField.class);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        TESTER.assertComponent(WIZARD_FORM + ":view:auxClasses:paletteField:choices", Choices.class);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        TESTER.assertComponent(WIZARD_FORM + ":view:groupsContainer:groups:form:filter:textField",
-                TextField.class);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "fullname").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "surname").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "userId").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-
-        FormTester formTester = TESTER.newFormTester(WIZARD_FORM);
-        assertNotNull(formTester);
-        TESTER.assertComponent(findComponentByMarkupId(
-                WIZARD_FORM + ":view:plainSchemas:tabs:0:body:content:schemas", "email").getPageRelativePath()
-                + ":textField",
-                TextField.class);
-        formTester.setValue(findComponentByMarkupId(WIZARD_FORM
-                + ":view:plainSchemas:tabs:0:body:content:schemas",
-                "email").getPageRelativePath().replace(WIZARD_FORM + ':', StringUtils.EMPTY) + ":textField",
-                newEmail);
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-
-        // check required fields were correctly set
-        TESTER.assertNoInfoMessage();
-        TESTER.assertNoErrorMessage();
-
-        TESTER.assertComponent(WIZARD_FORM
-                + ":view:derSchemas:tabs:0:body:content:schemas:0:panel:textField",
-                TextField.class);
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-        TESTER.assertComponent(WIZARD_FORM + ":view:virSchemas:tabs:0:body:content:schemas:0:panel:"
-                + "multiValueContainer:innerForm:content:field-label",
-                Label.class);
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:next", Constants.ON_CLICK);
-        TESTER.assertComponent(WIZARD_FORM + ":view:resources:paletteField:choices", Choices.class);
-
-        TESTER.executeAjaxEvent(WIZARD_FORM + ":buttons:finish", Constants.ON_CLICK);
-
-        TESTER.assertRenderedPage(Login.class);
-        TESTER.assertComponent("login:username", TextField.class);
-
-        assertEquals(FlowableDetector.isFlowableEnabledForUserWorkflow(SYNCOPE_SERVICE)
-                ? "active" : "created", userService.read(username).getStatus());
-        assertEquals(newEmail, userService.read(username).getPlainAttr("email").get().getValues().get(0));
-
-        TESTER.cleanupFeedbackMessages();
-    }
-}
diff --git a/fit/core-reference/src/test/resources/enduser.properties b/fit/core-reference/src/test/resources/enduser.properties
index 6c48a20..09a8d6e 100644
--- a/fit/core-reference/src/test/resources/enduser.properties
+++ b/fit/core-reference/src/test/resources/enduser.properties
@@ -25,6 +25,15 @@
 captcha=false
 csrf=false
 
+# Sidebar
+sidebar=org.apache.syncope.client.enduser.panels.Sidebar
+
+# Page
+page.profile=org.apache.syncope.client.enduser.pages.Dashboard
+page.edituser=org.apache.syncope.client.enduser.pages.EditUser
+page.editchangepassword=org.apache.syncope.client.enduser.pages.EditChangePassword
+page.editsecurityquestion=org.apache.syncope.client.enduser.pages.EditSecurityQuestion
+
 security.headers.X-XSS-Protection=1; mode=block
 security.headers.Strict-Transport-Security=max-age=31536000; includeSubDomains; preload
 security.headers.X-Content-Type-Options=nosniff
diff --git a/fit/enduser-reference/src/main/resources/enduser.properties b/fit/enduser-reference/src/main/resources/enduser.properties
index 1d62418..b4061af 100644
--- a/fit/enduser-reference/src/main/resources/enduser.properties
+++ b/fit/enduser-reference/src/main/resources/enduser.properties
@@ -26,6 +26,15 @@
 captcha=true
 csrf=true
 
+# Sidebar
+sidebar=org.apache.syncope.client.enduser.panels.Sidebar
+
+# Page
+page.profile=org.apache.syncope.client.enduser.pages.Dashboard
+page.edituser=org.apache.syncope.client.enduser.pages.EditUser
+page.editchangepassword=org.apache.syncope.client.enduser.pages.EditChangePassword
+page.editsecurityquestion=org.apache.syncope.client.enduser.pages.EditSecurityQuestion
+
 security.headers.X-XSS-Protection=1; mode=block
 security.headers.Strict-Transport-Security=max-age=31536000; includeSubDomains; preload
 security.headers.X-Content-Type-Options=nosniff