Fixing EnduserITCase
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 b177eff..2a8ad07 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
@@ -22,6 +22,7 @@
 import de.agilecoders.wicket.extensions.markup.html.bootstrap.form.select.BootstrapSelect;
 import java.security.AccessControlException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
@@ -31,6 +32,7 @@
 import javax.ws.rs.core.HttpHeaders;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
 import org.apache.wicket.ajax.markup.html.form.AjaxButton;
@@ -129,7 +131,7 @@
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                target.add(form);
+                getLanguageOnChangeComponents().forEach(target::add);
             }
         }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
 
@@ -137,7 +139,7 @@
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                target.add(form);
+                getLanguageOnChangeComponents().forEach(target::add);
             }
         });
         form.add(languageSelect.setOutputMarkupId(true));
@@ -149,7 +151,7 @@
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                target.add(form);
+                getDomainOnChangeComponents().forEach(target::add);
             }
         }).add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
 
@@ -157,7 +159,7 @@
 
             @Override
             protected void onUpdate(final AjaxRequestTarget target) {
-                target.add(form);
+                getDomainOnChangeComponents().forEach(target::add);
             }
         });
         form.add(domainSelect.setOutputMarkupId(true));
@@ -191,6 +193,14 @@
         add(form);
     }
 
+    protected Collection<Component> getLanguageOnChangeComponents() {
+        return List.of(form);
+    }
+
+    protected Collection<Component> getDomainOnChangeComponents() {
+        return List.of(form);
+    }
+
     @Override
     public void renderHead(final IHeaderResponse response) {
         super.renderHead(response);
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 a7a3dd2..c0f9c47 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
@@ -44,6 +44,7 @@
 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.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.wicket.Session;
@@ -104,7 +105,10 @@
 
         if (root instanceof SyncopeClientException) {
             SyncopeClientException sce = (SyncopeClientException) root;
-            if (!sce.isComposite()) {
+            if (sce.getType() == ClientExceptionType.InvalidSecurityAnswer) {
+                message = getApplication().getResourceSettings().getLocalizer().
+                        getString("invalid.security.answer", null);
+            } else if (!sce.isComposite()) {
                 message = sce.getElements().stream().collect(Collectors.joining(", "));
             }
         } else if (root instanceof AccessControlException || root instanceof ForbiddenException) {
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/AjaxCaptchaFieldPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/AjaxCaptchaFieldPanel.java
deleted file mode 100644
index 5599a45..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/markup/html/form/AjaxCaptchaFieldPanel.java
+++ /dev/null
@@ -1,78 +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 org.apache.syncope.client.ui.commons.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
-import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.markup.ComponentTag;
-import org.apache.wicket.markup.html.form.RequiredTextField;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.ResourceModel;
-import org.apache.wicket.validation.IValidator;
-
-public class AjaxCaptchaFieldPanel extends Panel {
-
-    private static final long serialVersionUID = 238940918106696068L;
-
-    private RequiredTextField<String> field;
-
-    public AjaxCaptchaFieldPanel(final String id, final String name, final IModel<String> model) {
-        this(id, name, model, true);
-    }
-
-    public AjaxCaptchaFieldPanel(
-            final String id, final String name, final IModel<String> model, final boolean enableOnChange) {
-
-        super(id, model);
-
-        field = new RequiredTextField<String>("textField", model, String.class) {
-
-            private static final long serialVersionUID = -8751221833502425836L;
-
-            @Override
-            protected void onComponentTag(final ComponentTag tag) {
-                super.onComponentTag(tag);
-                // clear the field after each render
-                tag.put("value", "");
-            }
-        };
-        field.setLabel(new ResourceModel(name, name));
-
-        add(field.setOutputMarkupId(true));
-
-        if (enableOnChange && !field.isEnabled()) {
-            field.add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
-
-                private static final long serialVersionUID = -1107858522700306810L;
-
-                @Override
-                protected void onUpdate(final AjaxRequestTarget target) {
-                    // nothing to do
-                }
-            });
-        }
-    }
-
-    public void addValidator(final IValidator<? super String> validator) {
-        this.field.add(validator);
-    }
-
-}
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 bd23df1..c9f9a91 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
@@ -20,13 +20,17 @@
 
 import java.security.AccessControlException;
 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.ui.commons.BaseLogin;
 import org.apache.syncope.client.ui.commons.BaseSession;
+import org.apache.wicket.Component;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.panel.Panel;
@@ -40,11 +44,26 @@
     @SpringBean
     private ClassPathScanImplementationLookup lookup;
 
+    private final BookmarkablePageLink<Void> selfRegistration;
+
+    private final BookmarkablePageLink<Void> selfPwdReset;
+
     public Login(final PageParameters parameters) {
         super(parameters);
 
-        add(new BookmarkablePageLink<>("self-registration", Self.class).setOutputMarkupId(true));
-        add(new BookmarkablePageLink<>("self-pwd-reset", SelfPasswordReset.class).setOutputMarkupId(true));
+        selfRegistration = new BookmarkablePageLink<>("self-registration", Self.class);
+        add(selfRegistration.setOutputMarkupId(true));
+
+        selfPwdReset = new BookmarkablePageLink<>("self-pwd-reset", SelfPasswordReset.class);
+        add(selfPwdReset.setOutputMarkupId(true));
+    }
+
+    @Override
+    protected Collection<Component> getLanguageOnChangeComponents() {
+        return Stream.concat(
+                super.getLanguageOnChangeComponents().stream(),
+                List.of(selfRegistration, selfPwdReset).stream()).
+                collect(Collectors.toList());
     }
 
     @Override
@@ -57,8 +76,8 @@
         List<Panel> ssoLoginFormPanels = new ArrayList<>();
         lookup.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);
             }
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
index 46cda9c..595ff0c 100644
--- 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
@@ -24,6 +24,7 @@
 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;
@@ -33,7 +34,6 @@
 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.syncope.common.rest.api.service.UserSelfService;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
@@ -162,7 +162,6 @@
             protected void onSubmit(final AjaxRequestTarget target) {
                 boolean checked = true;
                 if (SyncopeWebApplication.get().isCaptchaEnabled()) {
-                    // captcha check
                     checked = captcha.captchaCheck();
                 }
                 if (!checked) {
@@ -170,9 +169,8 @@
                     ((BaseEnduserWebPage) pageRef.getPage()).getNotificationPanel().refresh(target);
                 } else {
                     try {
-                        SyncopeEnduserSession.get().getService(UserSelfService.class).
-                                requestPasswordReset(usernameText, securityAnswerText);
-                        final PageParameters parameters = new PageParameters();
+                        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) {
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 56aa2ff..95a35ce 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
@@ -62,4 +62,7 @@
         getService(UserSelfService.class).mustChangePassword(password);
     }
 
+    public static void requestPasswordReset(final String username, final String securityAnswer) {
+        getService(UserSelfService.class).requestPasswordReset(username, securityAnswer);
+    }
 }
diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.java
deleted file mode 100644
index 0d510e2..0000000
--- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.java
+++ /dev/null
@@ -1,72 +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.markup.html.form.AjaxCaptchaFieldPanel;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.ajax.markup.html.form.AjaxButton;
-import org.apache.wicket.extensions.markup.html.captcha.CaptchaImageResource;
-import org.apache.wicket.markup.html.image.Image;
-import org.apache.wicket.markup.html.panel.Panel;
-import org.apache.wicket.model.Model;
-
-public abstract class AbstractCaptchaPanel<T> extends Panel {
-
-    private static final long serialVersionUID = -4310189409064713307L;
-
-    protected String randomText;
-
-    protected final Model<String> captchaText = new Model<>();
-
-    protected final CaptchaImageResource captchaImageResource;
-
-    public AbstractCaptchaPanel(final String id) {
-        super(id);
-        this.setOutputMarkupId(true);
-
-        captchaImageResource = createCaptchaImageResource();
-        final Image captchaImage = new Image("image", captchaImageResource);
-        captchaImage.setOutputMarkupId(true);
-        add(captchaImage);
-
-        AjaxButton reloadCaptchaButton = new AjaxButton("reloadButton") {
-
-            private static final long serialVersionUID = -957948639666058749L;
-
-            @Override
-            protected void onSubmit(final AjaxRequestTarget target) {
-                captchaImageResource.invalidate();
-                target.add(captchaImage);
-            }
-
-        };
-        reloadCaptchaButton.setDefaultFormProcessing(false);
-        add(reloadCaptchaButton);
-
-        add(new AjaxCaptchaFieldPanel("captcha", "captcha", captchaText)
-                .setOutputMarkupId(true)
-                .setOutputMarkupPlaceholderTag(true));
-    }
-
-    protected abstract CaptchaImageResource createCaptchaImageResource();
-
-    protected abstract void reload();
-
-    public abstract boolean captchaCheck();
-}
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
index 2c14e44..c46755c 100644
--- 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
@@ -46,5 +46,4 @@
     public boolean evaluate() {
         return SyncopeWebApplication.get().isCaptchaEnabled();
     }
-
 }
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/wizards/any/CaptchaPanel.java
index 45d95b0..4b886bf 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/wizards/any/CaptchaPanel.java
@@ -21,9 +21,15 @@
 import java.security.SecureRandom;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.text.RandomStringGenerator;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
 import org.apache.wicket.extensions.markup.html.captcha.CaptchaImageResource;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
 
-public class CaptchaPanel<T> extends AbstractCaptchaPanel<T> {
+public class CaptchaPanel<T> extends Panel {
 
     private static final long serialVersionUID = 1169850573252481471L;
 
@@ -34,13 +40,16 @@
             withinRange('a', 'z').
             build();
 
+    private String randomText;
+
+    private final Model<String> captchaText = new Model<>();
+
+    private final CaptchaImageResource captchaImageResource;
+
     public CaptchaPanel(final String id) {
         super(id);
-    }
 
-    @Override
-    protected CaptchaImageResource createCaptchaImageResource() {
-        return new CaptchaImageResource() {
+        captchaImageResource = new CaptchaImageResource() {
 
             private static final long serialVersionUID = 2436829189992948005L;
 
@@ -51,18 +60,35 @@
                 return super.render();
             }
         };
+        Image captchaImage = new Image("image", captchaImageResource);
+        captchaImage.setOutputMarkupId(true);
+        add(captchaImage);
+
+        add(new AjaxButton("reloadButton") {
+
+            private static final long serialVersionUID = -957948639666058749L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                captchaImageResource.invalidate();
+                target.add(captchaImage);
+            }
+
+        }.setDefaultFormProcessing(false));
+
+        add(new AjaxTextFieldPanel("captcha", "captcha", captchaText).
+                hideLabel().
+                setOutputMarkupId(true).
+                setOutputMarkupPlaceholderTag(true));
     }
 
-    @Override
-    protected void reload() {
+    public void reload() {
         this.captchaImageResource.invalidate();
     }
 
-    @Override
     public boolean captchaCheck() {
         return StringUtils.isBlank(captchaText.getObject()) || StringUtils.isBlank(randomText)
                 ? false
                 : captchaText.getObject().equals(randomText);
     }
-
 }
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/SyncopeWebApplication.properties
index 91038e7..7d49574 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/SyncopeWebApplication.properties
@@ -79,3 +79,4 @@
 
 captcha_error=Captcha entered does not match
 
+invalid.security.answer=Invalid security answer
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/SyncopeWebApplication_it.properties
index 1dde420..862b6c4 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/SyncopeWebApplication_it.properties
@@ -48,7 +48,6 @@
 create=Crea
 key=Chiave
 types=Tipi
-dashboard=Cruscotto
 realms=Realm
 roles=Ruoli
 policies=Politiche
@@ -77,3 +76,4 @@
 
 captcha_error=Il Captcha inserito non \u00e8 corretto
 
+invalid.security.answer=Risposta di sicurezza non valida
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/SyncopeWebApplication_ja.properties
index 8b178a3..8bc5b4e 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/SyncopeWebApplication_ja.properties
@@ -47,7 +47,6 @@
 create=\u4f5c\u6210
 key=\u30ad\u30fc
 types=\u30bf\u30a4\u30d7
-dashboard=\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9
 realms=\u30ec\u30eb\u30e0
 roles=\u30ed\u30fc\u30eb
 policies=\u30dd\u30ea\u30b7\u30fc
@@ -74,3 +73,4 @@
 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
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/SyncopeWebApplication_pt_BR.properties
index 2b60968..3e9c35c 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/SyncopeWebApplication_pt_BR.properties
@@ -48,7 +48,6 @@
 create=Criar
 key=Chave
 types=Tipos
-dashboard=Painel de instrumentos
 realms=Realm
 roles=\t\nFun\u00e7\u00e3o\n\t
 policies=Pol\u00edticas
@@ -76,3 +75,4 @@
 after=After
 
 captcha_error=Captcha entered does not match
+invalid.security.answer=Invalid security answer
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/SyncopeWebApplication_ru.properties
index d374ab0..aed3730 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/SyncopeWebApplication_ru.properties
@@ -47,7 +47,6 @@
 create=\u0421\u043e\u0437\u0434\u0430\u0442\u044c
 key=\u0418\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440
 types=\u0422\u0438\u043f\u044b \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432
-dashboard=\u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f
 realms=\u041e\u0431\u043b\u0430\u0441\u0442\u0438
 roles=\u0420\u043e\u043b\u0438
 policies=\u041f\u043e\u043b\u0438\u0442\u0438\u043a\u0438
@@ -75,3 +74,4 @@
 after=After
 
 captcha_error=Captcha entered does not match
+invalid.security.answer=Invalid security answer
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/AjaxCaptchaFieldPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/AjaxCaptchaFieldPanel.html
deleted file mode 100644
index e71b390..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/markup/html/form/AjaxCaptchaFieldPanel.html
+++ /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.
--->
-<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-  <wicket:panel>
-    <fieldset class="input-group">
-      <div id="captcha_text" class="captcha_elem">
-        <input type="text" size="27" wicket:id="textField" placeholder=""/>
-      </div>
-      <div id="captcha_message" class="captcha_elem">
-        <wicket:message key="captcha.message">[CAPTCHA_MESSAGE]</wicket:message>
-      </div>
-    </fieldset>
-  </wicket:panel>
-</html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.html
deleted file mode 100644
index c3164b1..0000000
--- a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.html
+++ /dev/null
@@ -1,24 +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">
-  <wicket:panel>
-    <wicket:child/>
-  </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.html b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.html
index 1a3fa63..f6a845b 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/wizards/any/CaptchaPanel.html
@@ -19,21 +19,20 @@
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:panel>
-    <wicket:extend>
-      <div class="captcha_block">
-        <div id="captcha_buttons">
-          <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-refresh" 
-                  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>
-        </div>
-        <div>
-          <span wicket:id="captcha">[CAPTCHA]</span>
-        </div>
+    <div class="captcha_block">
+      <div id="captcha_buttons">
+        <img wicket:id="image" />
       </div>
-    </wicket:extend>
+      <div id="captcha_image" class="captcha_elem">
+        <button wicket:id="reloadButton" id="refresh" type="button" class="btn btn-default btn-xs fa fa-refresh" 
+                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>
+      </div>
+      <div>
+        <label for="captcha"><wicket:message key="captcha.message"/></label>
+        <span wicket:id="captcha">[CAPTCHA]</span>
+      </div>
+    </div>
   </wicket:panel>
 </html>
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_it.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_it.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_it.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_it.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_ja.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ja.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_ja.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ja.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_pt_BR.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_pt_BR.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_pt_BR.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_pt_BR.properties
diff --git a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_ru.properties b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ru.properties
similarity index 100%
rename from client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/AbstractCaptchaPanel_ru.properties
rename to client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/wizards/any/CaptchaPanel_ru.properties
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 7e44616..bc4b1fe 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
@@ -25,6 +25,7 @@
 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;
@@ -108,6 +109,8 @@
 
     @BeforeAll
     public static void setUp() {
+        Locale.setDefault(Locale.ENGLISH);
+
         AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
         ctx.register(SyncopeEnduserWebApplicationTestConfig.class);
         ctx.register(SyncopeWebApplication.class);
@@ -133,7 +136,7 @@
         adminClient = clientFactory.create(ADMIN_UNAME, ADMIN_PWD);
 
         userService = adminClient.getService(UserService.class);
-        securityQuestionService = adminClient.getService(SecurityQuestionService.class);
+
         // create test user for must change password
         userService.create(new UserCR.Builder(SyncopeConstants.ROOT_REALM, "mustchangepassword").
                 password("password123").
@@ -146,6 +149,7 @@
                 plainAttr(attr("email", "mustchangepassword@apache.org")).
                 plainAttr(attr("loginDate", DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(new Date()))).
                 build());
+
         // create test user for self password reset
         userService.create(new UserCR.Builder(SyncopeConstants.ROOT_REALM, "selfpwdreset").
                 password("password123").
@@ -157,6 +161,7 @@
                 plainAttr(attr("email", "selfpwdreset@apache.org")).
                 plainAttr(attr("loginDate", DateFormatUtils.ISO_8601_EXTENDED_DATETIME_FORMAT.format(new Date()))).
                 build());
+
         // create test user for self update
         userService.create(new UserCR.Builder(SyncopeConstants.ROOT_REALM, "selfupdate").
                 password("password123").
@@ -166,6 +171,8 @@
                 plainAttr(attr("ctype", "a type")).
                 plainAttr(attr("userId", "selfupdate@apache.org")).
                 build());
+
+        securityQuestionService = adminClient.getService(SecurityQuestionService.class);
     }
 
     protected void doLogin(final String user, final String passwd) {
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
index 28e7e59..082f478 100644
--- 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
@@ -34,7 +34,6 @@
 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.lib.types.PatchOperation;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
 import org.apache.wicket.extensions.markup.html.form.palette.component.Choices;
 import org.apache.wicket.markup.html.basic.Label;
@@ -140,19 +139,14 @@
     @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()
-                        .operation(PatchOperation.ADD_REPLACE)
-                        .value(question.getKey())
-                        .build())
-                .securityAnswer(new StringReplacePatchItem.Builder()
-                        .operation(PatchOperation.ADD_REPLACE)
-                        .value("ananswer")
-                        .build())
-                .build());
 
-        final String pwdResetForm = "body:content:selfPwdResetForm";
+        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);
 
@@ -165,27 +159,29 @@
 
         FormTester formTester = TESTER.newFormTester(pwdResetForm);
         assertNotNull(formTester);
+
         // 1. set username to selfpwdreset
-        formTester.setValue(findComponentById(pwdResetForm + ":selfPwdResetPanel", "username"),
-                "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());
+        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("InvalidSecurityAnswer []");
+        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);
+        TESTER.assertComponent(pwdResetForm + ":selfPwdResetPanel:securityAnswer", TextField.class);
+
         // 4. submit form
         TESTER.assertNoFeedbackMessage(0);
         TESTER.assertNoErrorMessage();
@@ -200,10 +196,8 @@
     @Test
     public void mustChangePassword() {
         UserTO mustchangepassword = userService.read("mustchangepassword");
-        userService.update(new UserUR.Builder(mustchangepassword.getKey())
-                .mustChangePassword(new BooleanReplacePatchItem.Builder()
-                        .operation(PatchOperation.ADD_REPLACE)
-                        .value(Boolean.TRUE).build()).build());
+        userService.update(new UserUR.Builder(mustchangepassword.getKey()).
+                mustChangePassword(new BooleanReplacePatchItem.Builder().value(Boolean.TRUE).build()).build());
 
         TESTER.startPage(Login.class);
         doLogin("mustchangepassword", "password123");