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

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

commit 31a422f0762a0bfa5b5f35d533630c9fb61bcd14
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Apr 2 15:31:13 2026 +0200

    [SYNCOPE-1958] Check account and passord policy compliance before save
---
 .../ui/commons/rest/UserComplianceRestClient.java  | 26 +++++++++
 .../client/ui/commons/wizards/AjaxWizard.java      |  6 +-
 .../ui/commons/wizards/any/PasswordPanel.java      | 65 ++++++++++++++--------
 .../client/console/rest/UserRestClient.java        | 11 +++-
 .../client/console/status/ChangePasswordModal.java |  2 +-
 .../client/console/wizards/any/UserDetails.java    | 43 ++++++++++++--
 .../client/console/wizards/any/UserDetails.html    |  8 ++-
 .../enduser/pages/SelfConfirmPasswordReset.java    |  9 ++-
 .../client/enduser/panels/any/UserDetails.java     | 55 ++++++++++++++----
 .../client/enduser/rest/UserSelfRestClient.java    | 10 +++-
 .../client/enduser/panels/any/UserDetails.html     |  8 ++-
 .../apache/syncope/fit/console/PoliciesITCase.java |  2 +-
 .../apache/syncope/fit/console/RealmsITCase.java   |  6 +-
 .../apache/syncope/fit/console/UsersITCase.java    |  8 +--
 .../syncope/fit/enduser/AnonymousITCase.java       |  2 +-
 15 files changed, 199 insertions(+), 62 deletions(-)

diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/rest/UserComplianceRestClient.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/rest/UserComplianceRestClient.java
new file mode 100644
index 0000000000..e541717cb1
--- /dev/null
+++ 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/rest/UserComplianceRestClient.java
@@ -0,0 +1,26 @@
+/*
+ * 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.rest;
+
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
+
+public interface UserComplianceRestClient extends RestClient {
+
+    void compliance(ComplianceQuery query);
+}
diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
index d47a412536..e79e5d435c 100644
--- 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
+++ 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/wizards/AjaxWizard.java
@@ -199,11 +199,11 @@ public abstract class AjaxWizard<T extends Serializable> 
extends Wizard
     public final void onFinish() {
         AjaxRequestTarget target = 
RequestCycle.get().find(AjaxRequestTarget.class).orElse(null);
         try {
-            final Serializable res = onApply(target);
+            Serializable applied = onApply(target);
             if (eventSink == null) {
-                send(this, Broadcast.BUBBLE, new NewItemFinishEvent<>(item, 
target).setResult(res));
+                send(this, Broadcast.BUBBLE, new NewItemFinishEvent<>(item, 
target).setResult(applied));
             } else {
-                send(eventSink, Broadcast.EXACT, new 
NewItemFinishEvent<>(item, target).setResult(res));
+                send(eventSink, Broadcast.EXACT, new 
NewItemFinishEvent<>(item, target).setResult(applied));
             }
         } catch (TimeoutException te) {
             LOG.warn("Operation took too long", te);
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 214c64ff92..8c99e37a9a 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
@@ -22,9 +22,14 @@ import 
de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.stre
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxPasswordFieldPanel;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.SyncopePasswordStrengthConfig;
+import org.apache.syncope.client.ui.commons.rest.UserComplianceRestClient;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
 import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.FormComponent;
 import org.apache.wicket.markup.html.form.PasswordTextField;
 import 
org.apache.wicket.markup.html.form.validation.EqualPasswordInputValidator;
+import org.apache.wicket.markup.html.form.validation.IFormValidator;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.PropertyModel;
@@ -38,17 +43,8 @@ public class PasswordPanel extends Panel {
             final String id,
             final UserWrapper wrapper,
             final Boolean storePasswordInSyncope,
-            final boolean templateMode) {
-
-        this(id, wrapper, templateMode, storePasswordInSyncope, null);
-    }
-
-    public PasswordPanel(
-            final String id,
-            final UserWrapper wrapper,
             final boolean templateMode,
-            final Boolean storePasswordInSyncope,
-            final PasswordStrengthBehavior passwordStrengthBehavior) {
+            final UserComplianceRestClient restClient) {
 
         super(id);
         setOutputMarkupId(true);
@@ -56,14 +52,14 @@ public class PasswordPanel extends Panel {
         Form<?> form = new Form<>("passwordInnerForm");
         add(form);
 
-        AjaxPasswordFieldPanel confirmPasswordField = new 
AjaxPasswordFieldPanel(
+        AjaxPasswordFieldPanel confirmPassword = new AjaxPasswordFieldPanel(
                 "confirmPassword", "confirmPassword", Model.of(), false);
-        ((PasswordTextField) 
confirmPasswordField.getField()).setResetPassword(false);
-        
form.add(confirmPasswordField.setPlaceholder("confirmPassword").setMarkupId("confirmPassword"));
+        ((PasswordTextField) 
confirmPassword.getField()).setResetPassword(false);
+        
form.add(confirmPassword.setPlaceholder("confirmPassword").setMarkupId("confirmPassword"));
 
         if (templateMode) {
-            confirmPasswordField.setEnabled(false);
-            confirmPasswordField.setVisible(false);
+            confirmPassword.setEnabled(false);
+            confirmPassword.setVisible(false);
 
             AjaxTextFieldPanel passwordField = new AjaxTextFieldPanel(
                     "password", "password", new 
PropertyModel<>(wrapper.getInnerObject(), "password"), false);
@@ -73,18 +69,41 @@ public class PasswordPanel extends Panel {
             form.add(passwordField);
             passwordField.enableJexlHelp();
         } else {
-            AjaxPasswordFieldPanel passwordField = new AjaxPasswordFieldPanel(
+            AjaxPasswordFieldPanel password = new AjaxPasswordFieldPanel(
                     "password",
                     "password",
                     new PropertyModel<>(wrapper.getInnerObject(), "password"),
                     false,
-                    passwordStrengthBehavior);
-            passwordField.setRequired(true);
-            passwordField.setMarkupId("password");
-            passwordField.setPlaceholder("password");
-            ((PasswordTextField) 
passwordField.getField()).setResetPassword(false);
-            form.add(passwordField);
-            form.add(new EqualPasswordInputValidator(passwordField.getField(), 
confirmPasswordField.getField()));
+                    new PasswordStrengthBehavior(new 
SyncopePasswordStrengthConfig()));
+            password.setRequired(true);
+            password.setMarkupId("password");
+            password.setPlaceholder("password");
+            ((PasswordTextField) password.getField()).setResetPassword(false);
+            form.add(password);
+            form.add(new EqualPasswordInputValidator(password.getField(), 
confirmPassword.getField()));
+            form.add(new IFormValidator() {
+
+                private static final long serialVersionUID = 
-73522462874258637L;
+
+                @Override
+                public FormComponent<?>[] getDependentFormComponents() {
+                    return null;
+                }
+
+                @Override
+                public void validate(final Form<?> form) {
+                    ComplianceQuery quey = new ComplianceQuery.Builder().
+                            realm(wrapper.getInnerObject().getRealm()).
+                            password(password.getField().getInput()).
+                            resources(wrapper.getInnerObject().getResources()).
+                            build();
+                    try {
+                        restClient.compliance(quey);
+                    } catch (Exception e) {
+                        password.getField().error(e.getMessage());
+                    }
+                }
+            });
         }
 
         AjaxCheckBoxPanel storePasswordInSyncopePanel = new 
AjaxCheckBoxPanel("storePasswordInSyncope",
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
index 224bbb8065..cc3cae9679 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/rest/UserRestClient.java
@@ -22,7 +22,9 @@ import jakarta.ws.rs.core.GenericType;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.rest.UserComplianceRestClient;
 import org.apache.syncope.client.ui.commons.status.Status;
 import org.apache.syncope.client.ui.commons.status.StatusBean;
 import org.apache.syncope.client.ui.commons.status.StatusUtils;
@@ -35,14 +37,16 @@ import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.StatusRType;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
 import org.apache.syncope.common.rest.api.service.AnyService;
+import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.syncope.common.rest.api.service.UserService;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 
 /**
  * Console client for invoking rest users services.
  */
-public class UserRestClient extends AbstractAnyRestClient<UserTO> {
+public class UserRestClient extends AbstractAnyRestClient<UserTO> implements 
UserComplianceRestClient {
 
     private static final long serialVersionUID = -1575748964398293968L;
 
@@ -51,6 +55,11 @@ public class UserRestClient extends 
AbstractAnyRestClient<UserTO> {
         return UserService.class;
     }
 
+    @Override
+    public void compliance(final ComplianceQuery query) {
+        
SyncopeConsoleSession.get().getAnonymousService(UserSelfService.class).compliance(query);
+    }
+
     public ProvisioningResult<UserTO> create(final UserCR createReq) {
         return getService(UserService.class).create(createReq).readEntity(new 
GenericType<>() {
         });
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 bc77114bb6..02696dce1a 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
@@ -60,7 +60,7 @@ public class ChangePasswordModal extends 
AbstractModalPanel<AnyWrapper<UserTO>>
         super(baseModal, pageRefer);
         this.wrapper = wrapper;
 
-        PasswordPanel passwordPanel = new PasswordPanel("passwordPanel", 
wrapper, false, false);
+        PasswordPanel passwordPanel = new PasswordPanel("passwordPanel", 
wrapper, false, false, userRestClient);
         passwordPanel.setOutputMarkupId(true);
         add(passwordPanel);
 
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 ccb2003c32..a82f23c814 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
@@ -20,6 +20,7 @@ package org.apache.syncope.client.console.wizards.any;
 
 import java.util.List;
 import org.apache.commons.lang3.Strings;
+import org.apache.syncope.client.console.rest.UserRestClient;
 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.AjaxCheckBoxPanel;
@@ -28,6 +29,7 @@ import 
org.apache.syncope.client.ui.commons.wicket.markup.html.bootstrap.tabs.Ac
 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.UserTO;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
 import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -35,15 +37,22 @@ 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.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.FormComponent;
+import org.apache.wicket.markup.html.form.validation.IFormValidator;
 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.spring.injection.annot.SpringBean;
 
 public class UserDetails extends Details<UserTO> {
 
     private static final long serialVersionUID = 6592027822510220463L;
 
+    @SpringBean
+    protected UserRestClient restClient;
+
     public UserDetails(
             final UserWrapper wrapper,
             final boolean templateMode,
@@ -58,9 +67,13 @@ public class UserDetails extends Details<UserTO> {
         // ------------------------
         // Username
         // ------------------------
+        Form<?> form = new Form<>("usernameInnerForm");
+        add(form);
+
         AjaxTextFieldPanel username = new AjaxTextFieldPanel(
                 Constants.USERNAME_FIELD_NAME, Constants.USERNAME_FIELD_NAME,
                 new PropertyModel<>(userTO, Constants.USERNAME_FIELD_NAME), 
false);
+        form.add(username);
 
         if (wrapper.getPreviousUserTO() != null && Strings.CS.compare(
                 wrapper.getPreviousUserTO().getUsername(), 
wrapper.getInnerObject().getUsername()) != 0) {
@@ -72,8 +85,31 @@ public class UserDetails extends Details<UserTO> {
             username.enableJexlHelp();
         } else {
             username.addRequiredLabel();
+
+            form.add(new IFormValidator() {
+
+                private static final long serialVersionUID = 
-73522462874258637L;
+
+                @Override
+                public FormComponent<?>[] getDependentFormComponents() {
+                    return null;
+                }
+
+                @Override
+                public void validate(final Form<?> form) {
+                    ComplianceQuery quey = new ComplianceQuery.Builder().
+                            realm(wrapper.getInnerObject().getRealm()).
+                            username(username.getField().getInput()).
+                            resources(wrapper.getInnerObject().getResources()).
+                            build();
+                    try {
+                        restClient.compliance(quey);
+                    } catch (Exception e) {
+                        username.getField().error(e.getMessage());
+                    }
+                }
+            });
         }
-        add(username);
         // ------------------------
 
         // ------------------------
@@ -121,7 +157,6 @@ public class UserDetails extends Details<UserTO> {
                 }.setBody(new ResourceModel("password.change", "Change 
password ..."));
             }
         };
-
         accordion.setOutputMarkupId(true);
         accordion.setVisible(showPasswordManagement);
         add(accordion);
@@ -133,7 +168,7 @@ public class UserDetails extends Details<UserTO> {
         return new UserInformationPanel(id, anyTO);
     }
 
-    public static class EditUserPasswordPanel extends Panel {
+    protected class EditUserPasswordPanel extends Panel {
 
         private static final long serialVersionUID = -8198836979773590078L;
 
@@ -141,7 +176,7 @@ public class UserDetails extends Details<UserTO> {
             super(id);
             setOutputMarkupId(true);
             add(new Label("warning", new 
ResourceModel("password.change.warning")));
-            add(new PasswordPanel("passwordPanel", wrapper, false, 
templateMode));
+            add(new PasswordPanel("passwordPanel", wrapper, false, 
templateMode, restClient));
         }
     }
 }
diff --git 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/UserDetails.html
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/UserDetails.html
index e7fa169f79..58b2e693fb 100644
--- 
a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/UserDetails.html
+++ 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wizards/any/UserDetails.html
@@ -18,9 +18,11 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml"; 
xmlns:wicket="http://wicket.apache.org";>
   <wicket:extend>
-    <div class="form-group mb-3">
-      <span wicket:id="username"/>
-    </div>
+    <form wicket:id="usernameInnerForm">    
+      <div class="form-group mb-3">
+        <span wicket:id="username"/>
+      </div>
+    </form>
 
     <div class="form-group mb-3">
       <span wicket:id="mustChangePassword"/>
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 e3663ee547..3f30f4ae5a 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,11 +18,10 @@
  */
 package org.apache.syncope.client.enduser.pages;
 
-import 
de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
 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.SyncopePasswordStrengthConfig;
 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;
@@ -37,6 +36,7 @@ import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.form.StatelessForm;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
 import org.wicketstuff.kendo.ui.widget.notification.Notification;
 
 public class SelfConfirmPasswordReset extends BasePage {
@@ -45,6 +45,9 @@ public class SelfConfirmPasswordReset extends BasePage {
 
     private static final String CONFIRM_PASSWORD_RESET = 
"confirmPasswordReset";
 
+    @SpringBean
+    protected UserSelfRestClient restClient;
+
     public SelfConfirmPasswordReset(final PageParameters parameters) {
         super(parameters, CONFIRM_PASSWORD_RESET);
 
@@ -74,7 +77,7 @@ public class SelfConfirmPasswordReset extends BasePage {
                 new UserWrapper(fakeUserTO),
                 false,
                 false,
-                new PasswordStrengthBehavior(new 
SyncopePasswordStrengthConfig()));
+                restClient);
         passwordPanel.setOutputMarkupId(true);
 
         form.add(new CardPanel.Builder<PasswordPanel>()
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
index aa0bbabafc..730d3ea728 100644
--- 
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
@@ -18,30 +18,35 @@
  */
 package org.apache.syncope.client.enduser.panels.any;
 
-import 
de.agilecoders.wicket.extensions.markup.html.bootstrap.form.password.strength.PasswordStrengthBehavior;
 import java.util.List;
 import java.util.Optional;
 import org.apache.commons.lang3.Strings;
+import org.apache.syncope.client.enduser.rest.UserSelfRestClient;
 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.markup.html.form.SyncopePasswordStrengthConfig;
 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.SyncopeConstants;
 import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.rest.api.beans.ComplianceQuery;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.FormComponent;
+import org.apache.wicket.markup.html.form.validation.IFormValidator;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
 
 public class UserDetails extends Details<UserTO> {
 
     private static final long serialVersionUID = 6592027822510220463L;
 
-    protected final AjaxTextFieldPanel username;
+    @SpringBean
+    protected UserSelfRestClient restClient;
 
     protected final UserTO userTO;
 
@@ -49,18 +54,46 @@ public class UserDetails extends Details<UserTO> {
         super(id, pageRef);
 
         userTO = wrapper.getInnerObject();
+
         // ------------------------
         // Username
         // ------------------------
-        username = new AjaxTextFieldPanel("username", "username", new 
PropertyModel<>(userTO, "username"), false);
+        Form<?> form = new Form<>("usernameInnerForm");
+        add(form);
+
+        AjaxTextFieldPanel username = new AjaxTextFieldPanel(
+                "username", "username", new PropertyModel<>(userTO, 
"username"), false);
+
+        if (wrapper.getPreviousUserTO() != null && Strings.CS.compare(
+                wrapper.getPreviousUserTO().getUsername(), 
wrapper.getInnerObject().getUsername()) != 0) {
 
-        if (wrapper.getPreviousUserTO() != null && Strings.CS.
-                compare(wrapper.getPreviousUserTO().getUsername(), 
wrapper.getInnerObject().getUsername()) != 0) {
             username.showExternAction(new LabelInfo("externalAction", 
wrapper.getPreviousUserTO().getUsername()));
         }
 
-        username.addRequiredLabel();
-        add(username);
+        form.add(username.addRequiredLabel());
+        form.add(new IFormValidator() {
+
+            private static final long serialVersionUID = -73522462874258637L;
+
+            @Override
+            public FormComponent<?>[] getDependentFormComponents() {
+                return null;
+            }
+
+            @Override
+            public void validate(final Form<?> form) {
+                ComplianceQuery quey = new ComplianceQuery.Builder().
+                        realm(wrapper.getInnerObject().getRealm()).
+                        username(username.getField().getInput()).
+                        resources(wrapper.getInnerObject().getResources()).
+                        build();
+                try {
+                    restClient.compliance(quey);
+                } catch (Exception e) {
+                    username.getField().error(e.getMessage());
+                }
+            }
+        });
         // ------------------------
 
         // ------------------------
@@ -78,7 +111,7 @@ public class UserDetails extends Details<UserTO> {
         return destinationRealm;
     }
 
-    protected static class EditUserPasswordPanel extends Panel {
+    protected class EditUserPasswordPanel extends Panel {
 
         private static final long serialVersionUID = -8198836979773590078L;
 
@@ -89,9 +122,9 @@ public class UserDetails extends Details<UserTO> {
             add(new PasswordPanel(
                     "passwordPanel",
                     wrapper,
-                    false,
                     wrapper.getInnerObject().getKey() == null,
-                    new PasswordStrengthBehavior(new 
SyncopePasswordStrengthConfig())));
+                    false,
+                    restClient));
         }
     }
 }
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 93e114b10e..8c90bffde3 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
@@ -19,14 +19,17 @@
 package org.apache.syncope.client.enduser.rest;
 
 import jakarta.ws.rs.core.GenericType;
+import org.apache.syncope.client.enduser.SyncopeEnduserSession;
+import org.apache.syncope.client.ui.commons.rest.UserComplianceRestClient;
 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.syncope.common.rest.api.beans.ComplianceQuery;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 
-public class UserSelfRestClient extends BaseRestClient {
+public class UserSelfRestClient extends BaseRestClient implements 
UserComplianceRestClient {
 
     private static final long serialVersionUID = -1575748964398293968L;
 
@@ -38,6 +41,11 @@ public class UserSelfRestClient extends BaseRestClient {
         getService(UserSelfService.class).requestPasswordReset(username, 
securityAnswer);
     }
 
+    @Override
+    public void compliance(final ComplianceQuery query) {
+        
SyncopeEnduserSession.get().getAnonymousService(UserSelfService.class).compliance(query);
+    }
+
     public ProvisioningResult<UserTO> create(final UserCR createReq) {
         return 
getService(UserSelfService.class).create(createReq).readEntity(new 
GenericType<>() {
         });
diff --git 
a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
 
b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
index 85d266ef57..18587c0c2a 100644
--- 
a/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
+++ 
b/client/idrepo/enduser/src/main/resources/org/apache/syncope/client/enduser/panels/any/UserDetails.html
@@ -23,9 +23,11 @@ under the License.
       <div class="form-group mb-3">
         <span wicket:id="destinationRealm">[DESTINATION REALM]</span>
       </div>
-      <div class="form-group mb-3">
-        <span wicket:id="username"/>
-      </div>
+      <form wicket:id="usernameInnerForm">
+        <div class="form-group mb-3">
+          <span wicket:id="username"/>
+        </div>
+      </form>
       <wicket:child/>
     </div>
   </wicket:panel>
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 b5e5654420..8906b8e431 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
@@ -785,7 +785,7 @@ public class PoliciesITCase extends AbstractConsoleITCase {
 
         TESTER.assertComponent(
                 
"body:content:body:container:content:tabbedPanel:panel:searchResult:"
-                + 
"outerObjectsRepeater:0:outer:form:content:form:view:username:textField",
+                + 
"outerObjectsRepeater:0:outer:form:content:form:view:usernameInnerForm:username:textField",
                 TextField.class);
 
         formTester = TESTER.newFormTester(
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 478923c43a..8193c34b9d 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
@@ -129,7 +129,7 @@ public class RealmsITCase extends AbstractConsoleITCase {
         TESTER.assertComponent("body:templateModal", Modal.class);
 
         formTester = 
TESTER.newFormTester("body:templateModal:form:content:form");
-        formTester.setValue("view:username:textField", "'k' + firstname");
+        formTester.setValue("view:usernameInnerForm:username:textField", "'k' 
+ firstname");
         formTester.submit("buttons:finish");
 
         assertSuccessMessage();
@@ -147,11 +147,11 @@ public class RealmsITCase extends AbstractConsoleITCase {
 
         TESTER.assertComponent("body:templateModal", Modal.class);
 
-        
TESTER.assertModelValue("body:templateModal:form:content:form:view:username:textField",
+        
TESTER.assertModelValue("body:templateModal:form:content:form:view:usernameInnerForm:username:textField",
                 "'k' + firstname");
 
         formTester = 
TESTER.newFormTester("body:templateModal:form:content:form");
-        formTester.setValue("view:username:textField", "");
+        formTester.setValue("view:usernameInnerForm:username:textField", "");
         formTester.submit("buttons:finish");
 
         assertSuccessMessage();
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 a8848445a3..190c18a6b2 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
@@ -100,7 +100,7 @@ public class UsersITCase extends AbstractConsoleITCase {
                 + "actions:actions:actionRepeater:10:action:action");
 
         TESTER.assertComponent(TAB_PANEL
-                + 
"outerObjectsRepeater:0:outer:form:content:form:view:username:textField",
+                + 
"outerObjectsRepeater:0:outer:form:content:form:view:usernameInnerForm:username:textField",
                 TextField.class);
 
         FormTester formTester = TESTER.newFormTester(TAB_PANEL
@@ -125,7 +125,7 @@ public class UsersITCase extends AbstractConsoleITCase {
                 + "actions:actions:actionRepeater:0:action:action");
 
         TESTER.assertComponent(TAB_PANEL
-                + 
"outerObjectsRepeater:0:outer:form:content:form:view:username:textField",
+                + 
"outerObjectsRepeater:0:outer:form:content:form:view:usernameInnerForm:username:textField",
                 TextField.class);
 
         FormTester formTester = TESTER.newFormTester(TAB_PANEL
@@ -522,7 +522,7 @@ public class UsersITCase extends AbstractConsoleITCase {
                 + "actions:actions:actionRepeater:0:action:action");
 
         TESTER.assertComponent(TAB_PANEL
-                + 
"outerObjectsRepeater:0:outer:form:content:form:view:username:textField",
+                + 
"outerObjectsRepeater:0:outer:form:content:form:view:usernameInnerForm:username:textField",
                 TextField.class);
 
         FormTester formTester = TESTER.newFormTester(TAB_PANEL
@@ -568,7 +568,7 @@ public class UsersITCase extends AbstractConsoleITCase {
                 + "actions:actions:actionRepeater:0:action:action");
 
         TESTER.assertComponent(TAB_PANEL
-                + 
"outerObjectsRepeater:0:outer:form:content:form:view:username:textField",
+                + 
"outerObjectsRepeater:0:outer:form:content:form:view:usernameInnerForm:username:textField",
                 TextField.class);
 
         formTester = TESTER.newFormTester(TAB_PANEL + 
"outerObjectsRepeater:0:outer:form:content:form");
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
index ba881a1469..b190552831 100644
--- 
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
@@ -51,7 +51,7 @@ public class AnonymousITCase extends AbstractEnduserITCase {
         String form = "body:contentWrapper:content:selfRegistrationPanel:form";
         FormTester formTester = TESTER.newFormTester(form);
 
-        
formTester.setValue("userDetailsPanelCard:contentPanel:username:textField", 
username);
+        
formTester.setValue("userDetailsPanelCard:contentPanel:usernameInnerForm:username:textField",
 username);
 
         formTester.setValue(
                 
"userDetailsPanelCard:contentPanel:password:passwordPanel:passwordInnerForm:password:passwordField",


Reply via email to