From 930fbaf7279f9b92a4ac8cb50c764b914d19e54c Mon Sep 17 00:00:00 2001 From: TatoniMatteo Date: Wed, 15 Jul 2026 17:56:52 +0200 Subject: [PATCH 1/2] [SYNCOPE-1984] - Add support to realm-based enduser formLayout configuration --- .../client/enduser/SyncopeWebApplication.java | 7 ++- .../enduser/layout/UserFormLayouts.java | 48 +++++++++++++++++++ .../src/main/resources/customFormLayout.json | 33 +++++++------ 3 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java 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 0ac4a5684e8..1cf7981426c 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 @@ -31,6 +31,7 @@ import java.util.Optional; import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup; import org.apache.syncope.client.enduser.layout.UserFormLayoutInfo; +import org.apache.syncope.client.enduser.layout.UserFormLayouts; import org.apache.syncope.client.enduser.pages.BasePage; import org.apache.syncope.client.enduser.pages.Dashboard; import org.apache.syncope.client.enduser.pages.Login; @@ -48,6 +49,7 @@ import org.apache.syncope.client.ui.commons.themes.AdminLTE; import org.apache.syncope.common.keymaster.client.api.ServiceOps; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.wicket.Page; import org.apache.wicket.Session; import org.apache.wicket.WicketRuntimeException; @@ -92,7 +94,7 @@ public static SyncopeWebApplication get() { protected final List resources; - protected UserFormLayoutInfo customFormLayout; + protected UserFormLayouts customFormLayout; protected final DynamicMenuStringResourceLoader dynamicMenuStringResourceLoader; @@ -271,7 +273,8 @@ public ClassPathScanImplementationLookup getLookup() { } public UserFormLayoutInfo getCustomFormLayout() { - return customFormLayout; + String userRealm = SyncopeEnduserSession.get().getSelfTO(true).getRealm(); + return customFormLayout.getLayout(userRealm == null ? SyncopeConstants.ROOT_REALM : userRealm); } public Class getSidebar() { diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java new file mode 100644 index 00000000000..db652a21c9f --- /dev/null +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java @@ -0,0 +1,48 @@ +package org.apache.syncope.client.enduser.layout; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.common.lib.SyncopeConstants; + +public class UserFormLayouts implements Serializable { + + private static final long serialVersionUID = 9106933641699158419L; + + private final Map layouts; + + public UserFormLayouts() { + this(null); + } + + @JsonCreator + public UserFormLayouts(@JsonProperty("layouts") final Map layouts) { + this.layouts = layouts == null ? new HashMap<>() : new HashMap<>(layouts); + this.layouts.putIfAbsent(SyncopeConstants.ROOT_REALM, new UserFormLayoutInfo()); + } + + public Map getLayouts() { + return layouts; + } + + public UserFormLayoutInfo getLayout(final String realm) { + if (StringUtils.isNotBlank(realm)) { + String current = realm; + + while (current != null) { + UserFormLayoutInfo layout = layouts.get(current); + if (layout != null) { + return layout; + } + + int lastSlash = current.lastIndexOf('/'); + current = lastSlash <= 0 ? null : current.substring(0, lastSlash); + } + } + + return layouts.get(SyncopeConstants.ROOT_REALM); + } +} diff --git a/client/idrepo/enduser/src/main/resources/customFormLayout.json b/client/idrepo/enduser/src/main/resources/customFormLayout.json index 4a11228aeb5..9dbd0b06ef0 100644 --- a/client/idrepo/enduser/src/main/resources/customFormLayout.json +++ b/client/idrepo/enduser/src/main/resources/customFormLayout.json @@ -1,19 +1,22 @@ { - "formClass": "org.apache.syncope.client.enduser.panels.UserFormPanel", - "auxClasses": true, - "groups": true, - "plainAttrs": true, - "derAttrs": true, - "resources": true, - "whichPlainAttrs": {}, - "whichDerAttrs": {}, - "passwordManagement": true, - "detailsManagement": true, - "sidebarLayout": { - "editUserEnabled": true, - "passwordManagementEnabled": true, - "securityQuestionManagementEnabled": true, - "extensionsEnabled": { + "layouts": { + "/": { + "formClass": "org.apache.syncope.client.enduser.panels.UserFormPanel", + "auxClasses": true, + "groups": true, + "plainAttrs": true, + "derAttrs": true, + "resources": true, + "whichPlainAttrs": {}, + "whichDerAttrs": {}, + "passwordManagement": true, + "detailsManagement": true, + "sidebarLayout": { + "editUserEnabled": true, + "passwordManagementEnabled": true, + "securityQuestionManagementEnabled": true, + "extensionsEnabled": {} + } } } } From 60e5cd7447d877978407860375871c3b3e2ec7a1 Mon Sep 17 00:00:00 2001 From: TatoniMatteo Date: Thu, 16 Jul 2026 10:13:02 +0200 Subject: [PATCH 2/2] [SYNCOPE-1984] - Resolve comments --- .../client/enduser/SyncopeWebApplication.java | 17 +++++++-- .../enduser/layout/UserFormLayouts.java | 38 +++++++++++++------ 2 files changed, 40 insertions(+), 15 deletions(-) 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 1cf7981426c..d37ec0887bc 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 @@ -50,6 +50,7 @@ import org.apache.syncope.common.keymaster.client.api.ServiceOps; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.UserTO; import org.apache.wicket.Page; import org.apache.wicket.Session; import org.apache.wicket.WicketRuntimeException; @@ -94,7 +95,7 @@ public static SyncopeWebApplication get() { protected final List resources; - protected UserFormLayouts customFormLayout; + protected UserFormLayouts customFormLayouts; protected final DynamicMenuStringResourceLoader dynamicMenuStringResourceLoader; @@ -230,7 +231,7 @@ public IResource getResource() { getResourceSettings().getStringResourceLoaders().add(dynamicMenuStringResourceLoader); try (InputStream is = resourceLoader.getResource(props.getCustomFormLayout()).getInputStream()) { - customFormLayout = MAPPER.readValue(is, new TypeReference<>() { + customFormLayouts = MAPPER.readValue(is, new TypeReference<>() { }); } catch (Exception e) { throw new WicketRuntimeException("Could not read " + props.getCustomFormLayout(), e); @@ -273,8 +274,16 @@ public ClassPathScanImplementationLookup getLookup() { } public UserFormLayoutInfo getCustomFormLayout() { - String userRealm = SyncopeEnduserSession.get().getSelfTO(true).getRealm(); - return customFormLayout.getLayout(userRealm == null ? SyncopeConstants.ROOT_REALM : userRealm); + String userRealm = SyncopeConstants.ROOT_REALM; + try { + UserTO userTO = SyncopeEnduserSession.get().getSelfTO(true); + if (userTO != null) { + userRealm = userTO.getRealm(); + } + } catch (Exception e) { + LOG.warn("Could not get user realm from self", e); + } + return customFormLayouts.getLayout(userRealm); } public Class getSidebar() { diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java index db652a21c9f..3a00e4ba07e 100644 --- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/layout/UserFormLayouts.java @@ -1,3 +1,21 @@ +/* + * 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.annotation.JsonCreator; @@ -14,10 +32,6 @@ public class UserFormLayouts implements Serializable { private final Map layouts; - public UserFormLayouts() { - this(null); - } - @JsonCreator public UserFormLayouts(@JsonProperty("layouts") final Map layouts) { this.layouts = layouts == null ? new HashMap<>() : new HashMap<>(layouts); @@ -29,20 +43,22 @@ public Map getLayouts() { } public UserFormLayoutInfo getLayout(final String realm) { - if (StringUtils.isNotBlank(realm)) { - String current = realm; + if (!StringUtils.isBlank(realm)) { + UserFormLayoutInfo layout = layouts.get(realm); + if (layout != null) { + return layout; + } - while (current != null) { - UserFormLayoutInfo layout = layouts.get(current); + String current = StringUtils.substringBeforeLast(realm, "/"); + while (!SyncopeConstants.ROOT_REALM.equals(current)) { + layout = layouts.get(current); if (layout != null) { return layout; } - int lastSlash = current.lastIndexOf('/'); - current = lastSlash <= 0 ? null : current.substring(0, lastSlash); + current = StringUtils.substringBeforeLast(current, "/"); } } - return layouts.get(SyncopeConstants.ROOT_REALM); } }