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

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


The following commit(s) were added to refs/heads/master by this push:
     new 9219476  [SYNCOPE-1545] Allow to manage and deploy ClientApp's 
properties (#255)
9219476 is described below

commit 921947687a25efbefaa71a1178a0c3d918236fbb
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Tue Mar 30 19:45:16 2021 +0200

    [SYNCOPE-1545] Allow to manage and deploy ClientApp's properties (#255)
---
 .github/workflows/codeql-analysis.yml              |  10 --
 client/am/console/pom.xml                          |  10 ++
 .../client/console/SyncopeAMConsoleContext.java    |  10 +-
 .../clientapps/ClientAppDirectoryPanel.java        |  34 +++++
 .../clientapps/ClientAppModalPanelBuilder.java     |   6 +-
 .../ClientAppPropertiesDirectoryPanel.java         | 150 +++++++++++++++++++++
 .../ClientAppPropertiesModalPanel.java}            |  22 +--
 .../clientapps/ClientAppPropertyWizardBuilder.java |  87 ++++++++++++
 .../client/console/commons/AMConstants.java        |   2 +
 ... AMClassPathScanImplementationContributor.java} |  15 +--
 .../apache/syncope/client/console/pages/WA.java    |   3 +-
 .../console/panels/AuthModuleDirectoryPanel.java   |   7 +-
 .../console/panels/WAConfigDirectoryPanel.java     |  90 +------------
 .../console/wizards/AuthModuleWizardBuilder.java   |  11 +-
 .../clientapps/ClientAppDirectoryPanel.properties  |   2 +
 .../ClientAppDirectoryPanel_fr_CA.properties       |   2 +
 .../ClientAppDirectoryPanel_it.properties          |   2 +
 .../ClientAppDirectoryPanel_ja.properties          |   2 +
 .../ClientAppDirectoryPanel_pt_BR.properties       |   2 +
 .../ClientAppDirectoryPanel_ru.properties          |   2 +
 .../clientapps/ClientAppPropertiesModalPanel.html  |  23 ++++
 .../ClientAppPropertyWizardBuilder$AttrStep.html   |  24 ++++
 client/idm/console/pom.xml                         |   4 -
 .../client/console/SyncopeIdMConsoleContext.java   |   7 +
 .../commons/IdMImplementationInfoProvider.java     |  16 ++-
 ...IdMClassPathScanImplementationContributor.java} |  25 ++--
 .../markup/html/form/AjaxGridFieldPanel.java       |   4 +-
 client/idrepo/console/pom.xml                      |   4 -
 .../client/console/SyncopeConsoleApplication.java  |   9 +-
 .../client/console/SyncopeWebApplication.java      |   4 +
 .../commons/IdRepoImplementationInfoProvider.java  |  21 ++-
 .../ClassPathScanImplementationContributor.java    |  11 +-
 .../init/ClassPathScanImplementationLookup.java    |  90 +++----------
 .../syncope/client/console/pages/BasePage.java     |  16 +--
 .../syncope/client/console/pages/Dashboard.java    |   4 +-
 .../apache/syncope/client/console/pages/Login.java |   7 +-
 .../client/console/panels/AbstractModalPanel.java  |   6 +-
 .../console/panels/AttrListDirectoryPanel.java     | 119 ++++++++++++++++
 .../client/console/panels/DirectoryPanel.java      |   2 +-
 .../console/panels/ParametersDirectoryPanel.java   |   4 +-
 .../client/console/wizards/WizardMgtPanel.java     |   2 -
 .../panels/AttrListDirectoryPanel.properties}      |   0
 .../AttrListDirectoryPanel_fr_CA.properties}       |   0
 .../panels/AttrListDirectoryPanel_it.properties}   |   0
 .../panels/AttrListDirectoryPanel_ja.properties}   |   0
 .../AttrListDirectoryPanel_pt_BR.properties}       |   0
 .../panels/AttrListDirectoryPanel_ru.properties}   |   0
 .../syncope/client/console/AbstractTest.java       |   3 +-
 .../syncope/common/lib/policy/AccessPolicyTO.java  |   1 +
 .../common/lib/policy/AttrReleasePolicyTO.java     |   1 +
 .../syncope/common/lib/policy/AuthPolicyTO.java    |   1 +
 .../apache/syncope/common/lib/to/ClientAppTO.java  |  14 ++
 .../syncope/common/lib/types/OIDCGrantType.java    |  25 ++--
 .../syncope/common/lib/types/OIDCResponseType.java |  25 ++--
 .../lib/policy/AbstractCorrelationRuleConf.java    |   0
 .../lib/policy/DefaultPullCorrelationRuleConf.java |   0
 .../lib/policy/DefaultPushCorrelationRuleConf.java |   0
 .../common/lib/policy/ProvisioningPolicyTO.java    |   0
 .../common/lib/policy/PullCorrelationRuleConf.java |   0
 .../syncope/common/lib/policy/PullPolicyTO.java    |   0
 .../common/lib/policy/PushCorrelationRuleConf.java |   0
 .../syncope/common/lib/policy/PushPolicyTO.java    |   0
 .../apache/syncope/common/lib/policy/PolicyTO.java |   4 +-
 .../persistence/api/entity/auth/ClientApp.java     |   6 +
 .../jpa/entity/auth/AbstractClientApp.java         |  22 +++
 .../java/data/ClientAppDataBinderImpl.java         |  51 ++++---
 .../syncope/fit/console/AbstractConsoleITCase.java |   3 +-
 ...PTOMapper.java => AbstractClientAppMapper.java} |  43 +++---
 .../syncope/wa/starter/mapping/CASSPTOMapper.java  |  19 +--
 .../wa/starter/mapping/ClientAppMapper.java        |   2 +-
 .../syncope/wa/starter/mapping/OIDCRPTOMapper.java |  17 +--
 .../starter/mapping/RegisteredServiceMapper.java   |   2 +-
 .../wa/starter/mapping/SAML2SPTOMapper.java        |  22 +--
 73 files changed, 756 insertions(+), 376 deletions(-)

diff --git a/.github/workflows/codeql-analysis.yml 
b/.github/workflows/codeql-analysis.yml
index 634db47..cb00413 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -42,16 +42,6 @@ jobs:
     steps:
     - name: Checkout repository
       uses: actions/checkout@v2
-      with:
-        # We must fetch at least the immediate parents so that if this is
-        # a pull request then we can checkout the head.
-        fetch-depth: 2
-
-    # If this run was triggered by a pull request event, then checkout
-    # the head of the pull request instead of the merge commit.
-    - run: git checkout HEAD^2
-      if: ${{ github.event_name == 'pull_request' }}
-
     - name: Setup Java JDK
       uses: actions/[email protected]
       with:
diff --git a/client/am/console/pom.xml b/client/am/console/pom.xml
index a853803..afd6f11 100644
--- a/client/am/console/pom.xml
+++ b/client/am/console/pom.xml
@@ -94,6 +94,16 @@ under the License.
       <resource>
         <directory>src/main/resources</directory>
         <filtering>true</filtering>
+        <excludes>
+          <exclude>org/apache/syncope/**/*.properties</exclude>
+        </excludes>
+      </resource>
+      <resource>
+        <directory>src/main/resources</directory>
+        <filtering>false</filtering>
+        <includes>
+          <include>org/apache/syncope/**/*.properties</include>
+        </includes>
       </resource>
     </resources>
   </build>
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/SyncopeAMConsoleContext.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/SyncopeAMConsoleContext.java
index b3882fe..e3ff01a 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/SyncopeAMConsoleContext.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/SyncopeAMConsoleContext.java
@@ -22,8 +22,8 @@ import 
org.apache.syncope.client.console.commons.AMPolicyTabProvider;
 import org.apache.syncope.client.console.commons.AMRealmPolicyProvider;
 import org.apache.syncope.client.console.commons.PolicyTabProvider;
 import org.apache.syncope.client.console.commons.RealmPolicyProvider;
-import 
org.apache.syncope.client.console.init.AMClassPathScanImplementationLookup;
-import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
+import 
org.apache.syncope.client.console.init.AMClassPathScanImplementationContributor;
+import 
org.apache.syncope.client.console.init.ClassPathScanImplementationContributor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
@@ -31,10 +31,8 @@ import org.springframework.context.annotation.Configuration;
 public class SyncopeAMConsoleContext {
 
     @Bean
-    public ClassPathScanImplementationLookup 
classPathScanImplementationLookup() {
-        AMClassPathScanImplementationLookup lookup = new 
AMClassPathScanImplementationLookup();
-        lookup.load();
-        return lookup;
+    public ClassPathScanImplementationContributor 
amClassPathScanImplementationContributor() {
+        return new AMClassPathScanImplementationContributor();
     }
 
     @Bean
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java
index 84d37e7..7964935 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.java
@@ -30,6 +30,7 @@ import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
 import org.apache.syncope.client.console.panels.DirectoryPanel;
 import org.apache.syncope.client.console.rest.ClientAppRestClient;
 import 
org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.KeyPropertyColumn;
+import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionLink.ActionType;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
@@ -49,6 +50,7 @@ import 
org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
 
 public abstract class ClientAppDirectoryPanel<T extends ClientAppTO>
@@ -58,6 +60,8 @@ public abstract class ClientAppDirectoryPanel<T extends 
ClientAppTO>
 
     private final ClientAppType type;
 
+    protected final BaseModal<T> propertiesModal;
+
     public ClientAppDirectoryPanel(final String id, final ClientAppType type, 
final PageReference pageRef) {
         super(id, pageRef, true);
         this.type = type;
@@ -70,6 +74,20 @@ public abstract class ClientAppDirectoryPanel<T extends 
ClientAppTO>
         });
         setFooterVisibility(true);
 
+        propertiesModal = new BaseModal<>(Constants.OUTER) {
+
+            private static final long serialVersionUID = 389935548143327858L;
+
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+                setFooterVisible(false);
+            }
+        };
+        propertiesModal.size(Modal.Size.Large);
+        propertiesModal.setWindowClosedCallback(target -> 
propertiesModal.show(false));
+        addOuterObject(propertiesModal);
+
         disableCheckBoxes();
     }
 
@@ -114,6 +132,22 @@ public abstract class ClientAppDirectoryPanel<T extends 
ClientAppTO>
 
             @Override
             public void onClick(final AjaxRequestTarget target, final 
ClientAppTO ignore) {
+                model.setObject(ClientAppRestClient.read(type, 
model.getObject().getKey()));
+                target.add(propertiesModal.setContent(new 
ClientAppPropertiesModalPanel<>(
+                        propertiesModal,
+                        new ClientAppPropertiesDirectoryPanel<>("properties", 
propertiesModal, type, model, pageRef),
+                        pageRef)));
+                propertiesModal.header(new 
Model<>(getString("properties.title", new Model<>(model.getObject()))));
+                propertiesModal.show(true);
+            }
+        }, ActionLink.ActionType.TYPE_EXTENSIONS, 
AMEntitlement.CLIENTAPP_UPDATE);
+
+        panel.add(new ActionLink<T>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final 
ClientAppTO ignore) {
                 ClientAppTO clone = 
SerializationUtils.clone(model.getObject());
                 clone.setKey(null);
                 clone.setClientAppId(null);
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
index 2b16c3b..3e95a39 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
@@ -148,7 +148,7 @@ public class ClientAppModalPanelBuilder<T extends 
ClientAppTO> extends AbstractM
                     new PropertyModel<>(clientAppTO, 
"clientAppId")).setRequired(true));
 
             fields.add(new AjaxTextFieldPanel(
-                    "field", "description", new PropertyModel<>(clientAppTO, 
"description"), false).setRequired(true));
+                    "field", "description", new PropertyModel<>(clientAppTO, 
"description"), false));
 
             AjaxDropDownChoicePanel<String> accessPolicy = new 
AjaxDropDownChoicePanel<>(
                     "field", "accessPolicy", new PropertyModel<>(clientAppTO, 
"accessPolicy"), false);
@@ -168,11 +168,11 @@ public class ClientAppModalPanelBuilder<T extends 
ClientAppTO> extends AbstractM
                     "field", "authPolicy", new PropertyModel<>(clientAppTO, 
"authPolicy"), false);
             authPolicy.setChoiceRenderer(new PolicyRenderer(authPolicies));
             authPolicy.setChoices(new 
ArrayList<>(authPolicies.getObject().keySet()));
+            authPolicy.setRequired(true);
             ((AbstractSingleSelectChoice<?>) 
authPolicy.getField()).setNullValid(true);
             fields.add(authPolicy);
 
-            fields.add(new AjaxTextFieldPanel(
-                    "field", "theme", new PropertyModel<>(clientAppTO, 
"theme"), false).setRequired(true));
+            fields.add(new AjaxTextFieldPanel("field", "theme", new 
PropertyModel<>(clientAppTO, "theme"), false));
 
             switch (type) {
                 case CASSP:
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertiesDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertiesDirectoryPanel.java
new file mode 100644
index 0000000..3b05408
--- /dev/null
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertiesDirectoryPanel.java
@@ -0,0 +1,150 @@
+/*
+ * 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.console.clientapps;
+
+import java.util.List;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.AMConstants;
+import org.apache.syncope.client.console.panels.AttrListDirectoryPanel;
+import org.apache.syncope.client.console.rest.ClientAppRestClient;
+import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import 
org.apache.syncope.client.ui.commons.wizards.AjaxWizard.EditItemActionEvent;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.ClientAppTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.model.IModel;
+
+public class ClientAppPropertiesDirectoryPanel<T extends ClientAppTO> extends 
AttrListDirectoryPanel {
+
+    private static final long serialVersionUID = 1L;
+
+    private final BaseModal<T> propertiesModal;
+
+    private final ClientAppType type;
+
+    private final IModel<T> model;
+
+    public ClientAppPropertiesDirectoryPanel(
+            final String id,
+            final BaseModal<T> propertiesModal,
+            final ClientAppType type,
+            final IModel<T> model,
+            final PageReference pageRef) {
+
+        super(id, pageRef, false);
+
+        this.propertiesModal = propertiesModal;
+        this.type = type;
+        this.model = model;
+
+        setOutputMarkupId(true);
+
+        enableUtilityButton();
+        setFooterVisibility(false);
+
+        addNewItemPanelBuilder(new ClientAppPropertyWizardBuilder(type, 
model.getObject(), new Attr(), pageRef), true);
+
+        initResultTable();
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof ExitEvent) {
+            AjaxRequestTarget target = 
ExitEvent.class.cast(event.getPayload()).getTarget();
+            propertiesModal.close(target);
+        } else if (event.getPayload() instanceof EditItemActionEvent) {
+            @SuppressWarnings("unchecked")
+            EditItemActionEvent<T> payload = (EditItemActionEvent<T>) 
event.getPayload();
+            payload.getTarget().ifPresent(actionTogglePanel::close);
+        }
+        super.onEvent(event);
+    }
+
+    @Override
+    protected AttrListProvider dataProvider() {
+        return new ClientAppPropertiesProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return AMConstants.PREF_CLIENTAPP_PROPERTIES_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected ActionsPanel<Attr> getActions(final IModel<Attr> model) {
+        ActionsPanel<Attr> panel = super.getActions(model);
+
+        panel.add(new ActionLink<Attr>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Attr 
ignore) {
+                send(ClientAppPropertiesDirectoryPanel.this, Broadcast.EXACT,
+                        new 
AjaxWizard.EditItemActionEvent<>(model.getObject(), target));
+            }
+        }, ActionLink.ActionType.EDIT, AMEntitlement.CLIENTAPP_UPDATE);
+
+        panel.add(new ActionLink<Attr>() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Attr 
ignore) {
+                try {
+                    
ClientAppPropertiesDirectoryPanel.this.model.getObject().getProperties().remove(model.getObject());
+                    ClientAppRestClient.update(type, 
ClientAppPropertiesDirectoryPanel.this.model.getObject());
+
+                    
SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                    target.add(container);
+                } catch (Exception e) {
+                    LOG.error("While deleting {}", 
model.getObject().getSchema(), e);
+                    SyncopeConsoleSession.get().onException(e);
+                }
+                ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }, ActionLink.ActionType.DELETE, AMEntitlement.CLIENTAPP_UPDATE, true);
+
+        return panel;
+    }
+
+    protected final class ClientAppPropertiesProvider extends AttrListProvider 
{
+
+        private static final long serialVersionUID = -185944053385660794L;
+
+        private ClientAppPropertiesProvider(final int paginatorRows) {
+            super(paginatorRows);
+        }
+
+        @Override
+        protected List<Attr> list() {
+            return model.getObject().getProperties();
+        }
+    }
+}
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertiesModalPanel.java
similarity index 53%
copy from 
client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
copy to 
client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertiesModalPanel.java
index 9bb8e4e..b8530ad 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertiesModalPanel.java
@@ -16,19 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.client.console.commons;
+package org.apache.syncope.client.console.clientapps;
 
-public final class AMConstants {
+import org.apache.syncope.client.console.panels.AbstractModalPanel;
+import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.common.lib.to.ClientAppTO;
+import org.apache.wicket.PageReference;
 
-    public static final String PREF_GATEWAYROUTE_PAGINATOR_ROWS = 
"gatewayroute.paginator.rows";
+public class ClientAppPropertiesModalPanel<T extends ClientAppTO> extends 
AbstractModalPanel<T> {
 
-    public static final String PREF_WACONFIG_PAGINATOR_ROWS = 
"waconfig.paginator.rows";
+    private static final long serialVersionUID = 1L;
 
-    public static final String PREF_AUTHMODULE_PAGINATOR_ROWS = 
"authmodule.paginator.rows";
+    public ClientAppPropertiesModalPanel(
+            final BaseModal<T> modal,
+            final ClientAppPropertiesDirectoryPanel<T> directoryPanel,
+            final PageReference pageRef) {
 
-    public static final String PREF_CLIENTAPP_PAGINATOR_ROWS = 
"clientapp.paginator.rows";
-
-    private AMConstants() {
-        // private constructor for static utility class
+        super(modal, pageRef);
+        add(directoryPanel);
     }
 }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertyWizardBuilder.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertyWizardBuilder.java
new file mode 100644
index 0000000..a735c80
--- /dev/null
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppPropertyWizardBuilder.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.console.clientapps;
+
+import java.io.Serializable;
+import org.apache.syncope.client.console.rest.ClientAppRestClient;
+import 
org.apache.syncope.client.console.wicket.markup.html.form.MultiFieldPanel;
+import org.apache.syncope.client.console.wizards.BaseAjaxWizardBuilder;
+import org.apache.syncope.client.ui.commons.Constants;
+import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.ClientAppTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.extensions.wizard.WizardModel;
+import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+
+public class ClientAppPropertyWizardBuilder extends 
BaseAjaxWizardBuilder<Attr> {
+
+    private static final long serialVersionUID = 1L;
+
+    private final ClientAppType type;
+
+    private final ClientAppTO clientApp;
+
+    public ClientAppPropertyWizardBuilder(
+            final ClientAppType type,
+            final ClientAppTO clientApp,
+            final Attr attr,
+            final PageReference pageRef) {
+
+        super(attr, pageRef);
+        this.type = type;
+        this.clientApp = clientApp;
+    }
+
+    @Override
+    protected WizardModel buildModelSteps(final Attr modelObject, final 
WizardModel wizardModel) {
+        wizardModel.add(new AttrStep(modelObject));
+        return wizardModel;
+    }
+
+    private static class AttrStep extends WizardStep {
+
+        private static final long serialVersionUID = 1L;
+
+        AttrStep(final Attr modelObject) {
+            AjaxTextFieldPanel schema = new AjaxTextFieldPanel(
+                    Constants.KEY_FIELD_NAME, Constants.KEY_FIELD_NAME, new 
PropertyModel<>(modelObject, "schema"));
+            schema.addRequiredLabel();
+            schema.setEnabled(modelObject.getSchema() == null);
+            add(schema);
+
+            AjaxTextFieldPanel value = new AjaxTextFieldPanel("panel", 
"values", new Model<>());
+            add(new MultiFieldPanel.Builder<String>(
+                    new PropertyModel<>(modelObject, 
"values")).build("values", "values", value));
+        }
+    }
+
+    @Override
+    protected Serializable onApplyInternal(final Attr modelObject) {
+        clientApp.getProperties().removeIf(p -> 
modelObject.getSchema().equals(p.getSchema()));
+        clientApp.getProperties().add(modelObject);
+
+        ClientAppRestClient.update(type, clientApp);
+
+        return null;
+    }
+}
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
index 9bb8e4e..82d5f51 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/commons/AMConstants.java
@@ -28,6 +28,8 @@ public final class AMConstants {
 
     public static final String PREF_CLIENTAPP_PAGINATOR_ROWS = 
"clientapp.paginator.rows";
 
+    public static final String PREF_CLIENTAPP_PROPERTIES_PAGINATOR_ROWS = 
"clientapp.properties.paginator.rows";
+
     private AMConstants() {
         // private constructor for static utility class
     }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationLookup.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java
similarity index 75%
copy from 
client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationLookup.java
copy to 
client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java
index c10a799..25acc7d 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationLookup.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationContributor.java
@@ -18,26 +18,23 @@
  */
 package org.apache.syncope.client.console.init;
 
+import java.util.Optional;
 import org.apache.syncope.common.lib.auth.AuthModuleConf;
 import 
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
 import org.springframework.core.type.filter.AssignableTypeFilter;
 
-public class AMClassPathScanImplementationLookup extends 
ClassPathScanImplementationLookup {
+public class AMClassPathScanImplementationContributor implements 
ClassPathScanImplementationContributor {
 
     @Override
-    protected ClassPathScanningCandidateComponentProvider scanner() {
-        ClassPathScanningCandidateComponentProvider scanner = super.scanner();
-
+    public void extend(final ClassPathScanningCandidateComponentProvider 
scanner) {
         scanner.addIncludeFilter(new 
AssignableTypeFilter(AuthModuleConf.class));
-
-        return scanner;
     }
 
-    @SuppressWarnings("unchecked")
     @Override
-    protected void additional(final Class<?> clazz) {
+    public Optional<String> getLabel(final Class<?> clazz) {
         if (AuthModuleConf.class.isAssignableFrom(clazz)) {
-            addClass(AuthModuleConf.class.getName(), clazz);
+            return Optional.of(AuthModuleConf.class.getName());
         }
+        return Optional.empty();
     }
 }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
index a316b7e..1b291e9 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java
@@ -30,6 +30,7 @@ import 
org.apache.syncope.client.console.panels.WAConfigDirectoryPanel;
 import org.apache.syncope.client.console.rest.WAConfigRestClient;
 import org.apache.syncope.client.ui.commons.Constants;
 import 
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.common.keymaster.client.api.ServiceOps;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.lib.SyncopeConstants;
@@ -71,7 +72,7 @@ public class WA extends BasePage {
                     LOG.error("While pushing to WA", e);
                     SyncopeConsoleSession.get().onException(e);
                 }
-                ((BasePage) 
getPageReference().getPage()).getNotificationPanel().refresh(target);
+                ((BaseWebPage) 
getPageReference().getPage()).getNotificationPanel().refresh(target);
             }
         };
         push.setEnabled(!serviceOps.list(NetworkService.Type.WA).isEmpty()
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AuthModuleDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AuthModuleDirectoryPanel.java
index ea5683e..65290a4 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AuthModuleDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/AuthModuleDirectoryPanel.java
@@ -27,7 +27,6 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.AMConstants;
 import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
-import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.pages.BasePage;
 import 
org.apache.syncope.client.console.panels.AuthModuleDirectoryPanel.AuthModuleProvider;
 import org.apache.syncope.client.console.rest.AuthModuleRestClient;
@@ -53,20 +52,18 @@ import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.StringResourceModel;
-import org.apache.wicket.spring.injection.annot.SpringBean;
 
 public class AuthModuleDirectoryPanel
         extends DirectoryPanel<AuthModuleTO, AuthModuleTO, AuthModuleProvider, 
AuthModuleRestClient> {
 
-    @SpringBean
-    private ClassPathScanImplementationLookup lookup;
+    private static final long serialVersionUID = 1L;
 
     public AuthModuleDirectoryPanel(final String id, final PageReference 
pageRef) {
         super(id, pageRef);
 
         disableCheckBoxes();
 
-        this.addNewItemPanelBuilder(new AuthModuleWizardBuilder(lookup, new 
AuthModuleTO(), pageRef), true);
+        this.addNewItemPanelBuilder(new AuthModuleWizardBuilder(new 
AuthModuleTO(), pageRef), true);
 
         MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, 
AMEntitlement.AUTH_MODULE_CREATE);
 
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel.java
index 7bbe336..8fc4c9f 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel.java
@@ -18,59 +18,30 @@
  */
 package org.apache.syncope.client.console.panels;
 
-import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
 import java.util.List;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.AMConstants;
-import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
-import org.apache.syncope.client.console.pages.BasePage;
-import 
org.apache.syncope.client.console.panels.WAConfigDirectoryPanel.WAConfigProvider;
 import org.apache.syncope.client.console.rest.WAConfigRestClient;
 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.WizardMgtPanel;
 import org.apache.syncope.client.ui.commons.Constants;
-import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
 import org.apache.syncope.client.ui.commons.wizards.AbstractModalPanelBuilder;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.types.AMEntitlement;
-import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
-import 
org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
-import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
-import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
-import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.StringResourceModel;
 
-public class WAConfigDirectoryPanel
-        extends DirectoryPanel<Attr, Attr, WAConfigProvider, 
WAConfigRestClient> {
+public class WAConfigDirectoryPanel extends AttrListDirectoryPanel {
 
     private static final long serialVersionUID = 1538796157345L;
 
     public WAConfigDirectoryPanel(final String id, final PageReference 
pageRef) {
-        super(id, new Builder<Attr, Attr, WAConfigRestClient>(new 
WAConfigRestClient(), pageRef) {
-
-            private static final long serialVersionUID = 8769126634538601689L;
-
-            @Override
-            protected WizardMgtPanel<Attr> newInstance(final String id, final 
boolean wizardInModal) {
-                throw new UnsupportedOperationException();
-            }
-        });
-
-        itemKeyFieldName = "schema";
-        disableCheckBoxes();
+        super(id, pageRef, true);
 
         this.addNewItemPanelBuilder(new AbstractModalPanelBuilder<Attr>(new 
Attr(), pageRef) {
 
@@ -82,7 +53,6 @@ public class WAConfigDirectoryPanel
             }
         }, true);
 
-        modal.size(Modal.Size.Default);
         initResultTable();
     }
 
@@ -97,36 +67,6 @@ public class WAConfigDirectoryPanel
     }
 
     @Override
-    protected Collection<ActionLink.ActionType> getBatches() {
-        return List.of();
-    }
-
-    @Override
-    protected List<IColumn<Attr, String>> getColumns() {
-        final List<IColumn<Attr, String>> columns = new ArrayList<>();
-        columns.add(new PropertyColumn<>(new ResourceModel("schema"), 
"schema"));
-        columns.add(new PropertyColumn<Attr, String>(new 
ResourceModel("values"), "values") {
-
-            private static final long serialVersionUID = -1822504503325964706L;
-
-            @Override
-            public void populateItem(
-                    final Item<ICellPopulator<Attr>> item,
-                    final String componentId,
-                    final IModel<Attr> rowModel) {
-
-                if (rowModel.getObject().getValues().toString().length() > 96) 
{
-                    item.add(new Label(componentId, getString("tooLong")).
-                            add(new AttributeModifier("style", 
"font-style:italic")));
-                } else {
-                    super.populateItem(item, componentId, rowModel);
-                }
-            }
-        });
-        return columns;
-    }
-
-    @Override
     public ActionsPanel<Attr> getActions(final IModel<Attr> model) {
         ActionsPanel<Attr> panel = super.getActions(model);
 
@@ -158,40 +98,24 @@ public class WAConfigDirectoryPanel
                     LOG.error("While deleting {}", 
model.getObject().getSchema(), e);
                     SyncopeConsoleSession.get().onException(e);
                 }
-                ((BasePage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
+                ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
             }
         }, ActionLink.ActionType.DELETE, AMEntitlement.WA_CONFIG_DELETE, true);
 
         return panel;
     }
 
-    protected static final class WAConfigProvider extends 
DirectoryDataProvider<Attr> {
+    protected static final class WAConfigProvider extends AttrListProvider {
 
         private static final long serialVersionUID = -185944053385660794L;
 
-        private final SortableDataProviderComparator<Attr> comparator;
-
         private WAConfigProvider(final int paginatorRows) {
             super(paginatorRows);
-            setSort("schema", SortOrder.ASCENDING);
-            comparator = new SortableDataProviderComparator<>(this);
-        }
-
-        @Override
-        public Iterator<Attr> iterator(final long first, final long count) {
-            List<Attr> result = WAConfigRestClient.list();
-            result.sort(comparator);
-            return result.subList((int) first, (int) first + (int) 
count).iterator();
-        }
-
-        @Override
-        public long size() {
-            return WAConfigRestClient.list().size();
         }
 
         @Override
-        public IModel<Attr> model(final Attr object) {
-            return new CompoundPropertyModel<>(object);
+        protected List<Attr> list() {
+            return WAConfigRestClient.list();
         }
     }
 }
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/AuthModuleWizardBuilder.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/AuthModuleWizardBuilder.java
index b71bc2e..520d21d 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/AuthModuleWizardBuilder.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/wizards/AuthModuleWizardBuilder.java
@@ -22,7 +22,7 @@ import java.io.Serializable;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
-import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.panels.BeanPanel;
 import org.apache.syncope.client.console.rest.AuthModuleRestClient;
 import 
org.apache.syncope.client.console.wizards.mapping.AuthModuleMappingPanel;
@@ -44,12 +44,11 @@ import org.springframework.util.ClassUtils;
 
 public class AuthModuleWizardBuilder extends 
BaseAjaxWizardBuilder<AuthModuleTO> {
 
+    private static final long serialVersionUID = 1L;
+
     private final LoadableDetachableModel<List<String>> authModuleConfs;
 
-    public AuthModuleWizardBuilder(
-            final ClassPathScanImplementationLookup lookup,
-            final AuthModuleTO defaultItem,
-            final PageReference pageRef) {
+    public AuthModuleWizardBuilder(final AuthModuleTO defaultItem, final 
PageReference pageRef) {
 
         super(defaultItem, pageRef);
 
@@ -59,7 +58,7 @@ public class AuthModuleWizardBuilder extends 
BaseAjaxWizardBuilder<AuthModuleTO>
 
             @Override
             protected List<String> load() {
-                return lookup.getClasses(AuthModuleConf.class).stream().
+                return 
SyncopeWebApplication.get().getLookup().getClasses(AuthModuleConf.class).stream().
                         
map(Class::getName).sorted().collect(Collectors.toList());
             }
         };
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
index d4fc071..79eff6d 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
@@ -54,3 +54,5 @@ theme=Theme
 authPolicy=Authentication Policy
 accessPolicy=Access Policy
 attrReleasePolicy=Attribute Release Policy
+properties.title=Properties for ${name}
+type_extensions.title=properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
index a344081..e7cfc6d 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
@@ -54,3 +54,5 @@ theme=Theme
 authPolicy=Authentication Policy
 accessPolicy=Access Policy
 attrReleasePolicy=Attribute Release Policy
+properties.title=Properties for ${name}
+type_extensions.title=properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
index e2d542b..0e56d19 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
@@ -54,3 +54,5 @@ theme=Tema
 authPolicy=Politica Autenticazione
 accessPolicy=Politica Accesso
 attrReleasePolicy=Politica Rilascio Attributi
+properties.title=Propriet\u00e0 di ${name}
+type_extensions.title=propriet\u00e0
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
index 3fc7c34..aa9925b 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
@@ -54,3 +54,5 @@ theme=Theme
 authPolicy=Authentication Policy
 accessPolicy=Access Policy
 attrReleasePolicy=Attribute Release Policy
+properties.title=Properties for ${name}
+type_extensions.title=properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
index d4fc071..79eff6d 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
@@ -54,3 +54,5 @@ theme=Theme
 authPolicy=Authentication Policy
 accessPolicy=Access Policy
 attrReleasePolicy=Attribute Release Policy
+properties.title=Properties for ${name}
+type_extensions.title=properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
index 1727710..c0e48eb 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
@@ -55,3 +55,5 @@ theme=Theme
 authPolicy=Authentication Policy
 accessPolicy=Access Policy
 attrReleasePolicy=Attribute Release Policy
+properties.title=Properties for ${name}
+type_extensions.title=properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppPropertiesModalPanel.html
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppPropertiesModalPanel.html
new file mode 100644
index 0000000..69bafa9
--- /dev/null
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppPropertiesModalPanel.html
@@ -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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"; 
xmlns:wicket="http://wicket.apache.org";>
+  <wicket:extend>
+    <div wicket:id="properties"></div>
+  </wicket:extend>
+</html>
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppPropertyWizardBuilder$AttrStep.html
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppPropertyWizardBuilder$AttrStep.html
new file mode 100644
index 0000000..0c74006
--- /dev/null
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppPropertyWizardBuilder$AttrStep.html
@@ -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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"; 
xmlns:wicket="http://wicket.apache.org";>
+  <wicket:panel>
+    <span wicket:id="key">[schema]</span>
+    <span wicket:id="values">[values]</span>
+  </wicket:panel>
+</html>
diff --git a/client/idm/console/pom.xml b/client/idm/console/pom.xml
index f24d381..aac4062 100644
--- a/client/idm/console/pom.xml
+++ b/client/idm/console/pom.xml
@@ -96,8 +96,6 @@ under the License.
         <filtering>true</filtering>
         <excludes>
           <exclude>org/apache/syncope/**/*.properties</exclude>
-          <exclude>**/*.woff</exclude>
-          <exclude>**/*.woff2</exclude>
         </excludes>
       </resource>
       <resource>
@@ -105,8 +103,6 @@ under the License.
         <filtering>false</filtering>
         <includes>
           <include>org/apache/syncope/**/*.properties</include>
-          <include>**/*.woff</include>
-          <include>**/*.woff2</include>
         </includes>
       </resource>
     </resources>
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/SyncopeIdMConsoleContext.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/SyncopeIdMConsoleContext.java
index d2b58b4..15b3e07 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/SyncopeIdMConsoleContext.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/SyncopeIdMConsoleContext.java
@@ -36,11 +36,18 @@ import 
org.apache.syncope.client.console.commons.VirSchemaDetailsPanelProvider;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import 
org.apache.syncope.client.console.commons.AnyDirectoryPanelAdditionalActionLinksProvider;
+import 
org.apache.syncope.client.console.init.ClassPathScanImplementationContributor;
+import 
org.apache.syncope.client.console.init.IdMClassPathScanImplementationContributor;
 
 @Configuration
 public class SyncopeIdMConsoleContext {
 
     @Bean
+    public ClassPathScanImplementationContributor 
idmClassPathScanImplementationContributor() {
+        return new IdMClassPathScanImplementationContributor();
+    }
+
+    @Bean
     public ExternalResourceProvider resourceProvider() {
         return new IdMExternalResourceProvider();
     }
diff --git 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
index 3a7196e..0337aaa 100644
--- 
a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
@@ -23,6 +23,8 @@ import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
+import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
@@ -47,13 +49,13 @@ public class IdMImplementationInfoProvider extends 
IdRepoImplementationInfoProvi
         if (viewMode == ViewMode.JSON_BODY) {
             switch (implementation.getType()) {
                 case IdMImplementationType.PULL_CORRELATION_RULE:
-                    classes = 
lookup.getPullCorrelationRuleConfs().keySet().stream().
-                            collect(Collectors.toList());
+                    classes = 
lookup.getClasses(PullCorrelationRuleConf.class).stream().
+                            map(Class::getName).collect(Collectors.toList());
                     break;
 
                 case IdMImplementationType.PUSH_CORRELATION_RULE:
-                    classes = 
lookup.getPushCorrelationRuleConfs().keySet().stream().
-                            collect(Collectors.toList());
+                    classes = 
lookup.getClasses(PushCorrelationRuleConf.class).stream().
+                            map(Class::getName).collect(Collectors.toList());
                     break;
 
                 default:
@@ -111,11 +113,13 @@ public class IdMImplementationInfoProvider extends 
IdRepoImplementationInfoProvi
         Class<?> clazz;
         switch (implementationType) {
             case IdMImplementationType.PULL_CORRELATION_RULE:
-                clazz = lookup.getPullCorrelationRuleConfs().get(name);
+                clazz = 
lookup.getClasses(PullCorrelationRuleConf.class).stream().
+                        filter(c -> 
c.getName().equals(name)).findFirst().orElse(null);
                 break;
 
             case IdMImplementationType.PUSH_CORRELATION_RULE:
-                clazz = lookup.getPushCorrelationRuleConfs().get(name);
+                clazz = 
lookup.getClasses(PushCorrelationRuleConf.class).stream().
+                        filter(c -> 
c.getName().equals(name)).findFirst().orElse(null);
                 break;
 
             default:
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationLookup.java
 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/init/IdMClassPathScanImplementationContributor.java
similarity index 56%
rename from 
client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationLookup.java
rename to 
client/idm/console/src/main/java/org/apache/syncope/client/console/init/IdMClassPathScanImplementationContributor.java
index c10a799..f141331 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/init/AMClassPathScanImplementationLookup.java
+++ 
b/client/idm/console/src/main/java/org/apache/syncope/client/console/init/IdMClassPathScanImplementationContributor.java
@@ -18,26 +18,27 @@
  */
 package org.apache.syncope.client.console.init;
 
-import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import java.util.Optional;
+import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
+import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
 import 
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
 import org.springframework.core.type.filter.AssignableTypeFilter;
 
-public class AMClassPathScanImplementationLookup extends 
ClassPathScanImplementationLookup {
+public class IdMClassPathScanImplementationContributor implements 
ClassPathScanImplementationContributor {
 
     @Override
-    protected ClassPathScanningCandidateComponentProvider scanner() {
-        ClassPathScanningCandidateComponentProvider scanner = super.scanner();
-
-        scanner.addIncludeFilter(new 
AssignableTypeFilter(AuthModuleConf.class));
-
-        return scanner;
+    public void extend(final ClassPathScanningCandidateComponentProvider 
scanner) {
+        scanner.addIncludeFilter(new 
AssignableTypeFilter(PullCorrelationRuleConf.class));
+        scanner.addIncludeFilter(new 
AssignableTypeFilter(PushCorrelationRuleConf.class));
     }
 
-    @SuppressWarnings("unchecked")
     @Override
-    protected void additional(final Class<?> clazz) {
-        if (AuthModuleConf.class.isAssignableFrom(clazz)) {
-            addClass(AuthModuleConf.class.getName(), clazz);
+    public Optional<String> getLabel(final Class<?> clazz) {
+        if (PullCorrelationRuleConf.class.isAssignableFrom(clazz)
+                || PushCorrelationRuleConf.class.isAssignableFrom(clazz)) {
+
+            return Optional.of(clazz.getName());
         }
+        return Optional.empty();
     }
 }
diff --git 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxGridFieldPanel.java
 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxGridFieldPanel.java
index 9b12d10..ce2153e 100644
--- 
a/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxGridFieldPanel.java
+++ 
b/client/idrepo/common-ui/src/main/java/org/apache/syncope/client/ui/commons/markup/html/form/AjaxGridFieldPanel.java
@@ -37,6 +37,8 @@ import 
org.wicketstuff.egrid.provider.EditableListDataProvider;
 
 public class AjaxGridFieldPanel<K, V, S> extends Panel {
 
+    private static final long serialVersionUID = 1L;
+
     public AjaxGridFieldPanel(final String id, final String name, final 
IModel<Map<K, V>> model) {
         super(id, model);
 
@@ -73,7 +75,7 @@ public class AjaxGridFieldPanel<K, V, S> extends Panel {
         });
     }
 
-    public AjaxGridFieldPanel hideLabel() {
+    public AjaxGridFieldPanel<K, V, S> hideLabel() {
         Component label = get(AbstractFieldPanel.LABEL);
         if (label != null) {
             label.setVisible(false);
diff --git a/client/idrepo/console/pom.xml b/client/idrepo/console/pom.xml
index 456973c..8f53c23 100644
--- a/client/idrepo/console/pom.xml
+++ b/client/idrepo/console/pom.xml
@@ -181,8 +181,6 @@ under the License.
         <filtering>true</filtering>
         <excludes>
           <exclude>org/apache/syncope/**/*.properties</exclude>
-          <exclude>**/*.woff</exclude>
-          <exclude>**/*.woff2</exclude>
         </excludes>
       </resource>
       <resource>
@@ -190,8 +188,6 @@ under the License.
         <filtering>false</filtering>
         <includes>
           <include>org/apache/syncope/**/*.properties</include>
-          <include>**/*.woff</include>
-          <include>**/*.woff2</include>
         </includes>
       </resource>
 
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
index 469116c..fc0e993 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
@@ -38,6 +38,7 @@ import org.apache.syncope.client.console.commons.PreviewUtils;
 import org.apache.syncope.client.console.commons.RealmPolicyProvider;
 import org.apache.syncope.client.console.commons.StatusProvider;
 import org.apache.syncope.client.console.commons.VirSchemaDetailsPanelProvider;
+import 
org.apache.syncope.client.console.init.ClassPathScanImplementationContributor;
 import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.init.MIMETypesLoader;
 import org.apache.syncope.client.console.wizards.any.UserFormFinalizerUtils;
@@ -45,6 +46,7 @@ import 
org.apache.syncope.client.ui.commons.ApplicationContextProvider;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -52,6 +54,7 @@ import 
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConf
 import 
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import 
org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 
 @SpringBootApplication(exclude = {
@@ -59,6 +62,9 @@ import org.springframework.context.annotation.Bean;
     HttpMessageConvertersAutoConfiguration.class })
 public class SyncopeConsoleApplication extends SpringBootServletInitializer {
 
+    @Autowired
+    private ApplicationContext ctx;
+
     public static void main(final String[] args) {
         SpringApplication.run(SyncopeConsoleApplication.class, args);
     }
@@ -87,7 +93,8 @@ public class SyncopeConsoleApplication extends 
SpringBootServletInitializer {
     @ConditionalOnMissingBean(name = "classPathScanImplementationLookup")
     @Bean
     public ClassPathScanImplementationLookup 
classPathScanImplementationLookup() {
-        ClassPathScanImplementationLookup lookup = new 
ClassPathScanImplementationLookup();
+        ClassPathScanImplementationLookup lookup = new 
ClassPathScanImplementationLookup(
+                
ctx.getBeansOfType(ClassPathScanImplementationContributor.class).values());
         lookup.load();
         return lookup;
     }
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 eb9872a..30b3b32 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
@@ -371,6 +371,10 @@ public class SyncopeWebApplication extends 
WicketBootSecuredWebApplication {
                 : Dashboard.class;
     }
 
+    public ClassPathScanImplementationLookup getLookup() {
+        return lookup;
+    }
+
     public Class<? extends BasePage> getPageClass(final String key) {
         return pageClasses.get(key);
     }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
index 6b4d2f1..887df15 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
@@ -25,6 +25,9 @@ import java.util.stream.Collectors;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
+import org.apache.syncope.common.lib.policy.AccountRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordRuleConf;
+import org.apache.syncope.common.lib.report.ReportletConf;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
@@ -60,15 +63,18 @@ public class IdRepoImplementationInfoProvider implements 
ImplementationInfoProvi
         } else if (viewMode == ViewMode.JSON_BODY) {
             switch (implementation.getType()) {
                 case IdRepoImplementationType.REPORTLET:
-                    classes = new 
ArrayList<>(lookup.getReportletConfs().keySet());
+                    classes = lookup.getClasses(ReportletConf.class).stream().
+                            map(Class::getName).collect(Collectors.toList());
                     break;
 
                 case IdRepoImplementationType.ACCOUNT_RULE:
-                    classes = new 
ArrayList<>(lookup.getAccountRuleConfs().keySet());
+                    classes = 
lookup.getClasses(AccountRuleConf.class).stream().
+                            map(Class::getName).collect(Collectors.toList());
                     break;
 
                 case IdRepoImplementationType.PASSWORD_RULE:
-                    classes = new 
ArrayList<>(lookup.getPasswordRuleConfs().keySet());
+                    classes = 
lookup.getClasses(PasswordRuleConf.class).stream().
+                            map(Class::getName).collect(Collectors.toList());
                     break;
 
                 default:
@@ -129,15 +135,18 @@ public class IdRepoImplementationInfoProvider implements 
ImplementationInfoProvi
         Class<?> clazz = null;
         switch (implementationType) {
             case IdRepoImplementationType.REPORTLET:
-                clazz = lookup.getReportletConfs().get(name);
+                clazz = lookup.getClasses(ReportletConf.class).stream().
+                        filter(c -> 
c.getName().equals(name)).findFirst().orElse(null);
                 break;
 
             case IdRepoImplementationType.ACCOUNT_RULE:
-                clazz = lookup.getAccountRuleConfs().get(name);
+                clazz = lookup.getClasses(AccountRuleConf.class).stream().
+                        filter(c -> 
c.getName().equals(name)).findFirst().orElse(null);
                 break;
 
             case IdRepoImplementationType.PASSWORD_RULE:
-                clazz = lookup.getPasswordRuleConfs().get(name);
+                clazz = lookup.getClasses(PasswordRuleConf.class).stream().
+                        filter(c -> 
c.getName().equals(name)).findFirst().orElse(null);
                 break;
 
             default:
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationContributor.java
similarity index 70%
copy from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
copy to 
client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationContributor.java
index 9c6e850..a392f20 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationContributor.java
@@ -16,9 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.policy;
+package org.apache.syncope.client.console.init;
 
-@FunctionalInterface
-public interface PullCorrelationRuleConf extends RuleConf {
+import java.util.Optional;
+import 
org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
 
+public interface ClassPathScanImplementationContributor {
+
+    void extend(ClassPathScanningCandidateComponentProvider scanner);
+
+    Optional<String> getLabel(Class<?> clazz);
 }
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
index cc2c2e1..b49ed98 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
@@ -46,8 +46,6 @@ import 
org.apache.syncope.client.console.widgets.BaseExtWidget;
 import org.apache.syncope.client.console.widgets.ExtAlertWidget;
 import org.apache.syncope.common.lib.policy.AccountRuleConf;
 import org.apache.syncope.common.lib.policy.PasswordRuleConf;
-import org.apache.syncope.common.lib.policy.PullCorrelationRuleConf;
-import org.apache.syncope.common.lib.policy.PushCorrelationRuleConf;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.GroupTO;
@@ -95,6 +93,17 @@ public class ClassPathScanImplementationLookup {
         });
     }
 
+    /**
+     * This method can be overridden by subclasses to customize classpath scan.
+     *
+     * @return basePackage for classpath scanning
+     */
+    protected static String getBasePackage() {
+        return DEFAULT_BASE_PACKAGE;
+    }
+
+    private final Collection<ClassPathScanImplementationContributor> 
contributors;
+
     private Map<String, List<Class<?>>> classes;
 
     private List<Class<? extends BasePage>> idRepoPages;
@@ -103,23 +112,8 @@ public class ClassPathScanImplementationLookup {
 
     private List<Class<? extends BasePage>> amPages;
 
-    private Map<String, Class<? extends ReportletConf>> reportletConfs;
-
-    private Map<String, Class<? extends AccountRuleConf>> accountRuleConfs;
-
-    private Map<String, Class<? extends PasswordRuleConf>> passwordRuleConfs;
-
-    private Map<String, Class<? extends PullCorrelationRuleConf>> 
pullCorrelationRuleConfs;
-
-    private Map<String, Class<? extends PushCorrelationRuleConf>> 
pushCorrelationRuleConfs;
-
-    /**
-     * This method can be overridden by subclasses to customize classpath scan.
-     *
-     * @return basePackage for classpath scanning
-     */
-    protected static String getBasePackage() {
-        return DEFAULT_BASE_PACKAGE;
+    public ClassPathScanImplementationLookup(final 
Collection<ClassPathScanImplementationContributor> contributors) {
+        this.contributors = contributors;
     }
 
     protected ClassPathScanningCandidateComponentProvider scanner() {
@@ -135,10 +129,10 @@ public class ClassPathScanImplementationLookup {
         scanner.addIncludeFilter(new 
AssignableTypeFilter(ReportletConf.class));
         scanner.addIncludeFilter(new 
AssignableTypeFilter(AccountRuleConf.class));
         scanner.addIncludeFilter(new 
AssignableTypeFilter(PasswordRuleConf.class));
-        scanner.addIncludeFilter(new 
AssignableTypeFilter(PullCorrelationRuleConf.class));
-        scanner.addIncludeFilter(new 
AssignableTypeFilter(PushCorrelationRuleConf.class));
         scanner.addIncludeFilter(new 
AssignableTypeFilter(AbstractResource.class));
 
+        contributors.forEach(contributor -> contributor.extend(scanner));
+
         return scanner;
     }
 
@@ -151,21 +145,12 @@ public class ClassPathScanImplementationLookup {
         clazzes.add(clazz);
     }
 
-    protected void additional(final Class<?> clazz) {
-        // nothing to do
-    }
-
     @SuppressWarnings("unchecked")
     public void load() {
         classes = new HashMap<>();
         idRepoPages = new ArrayList<>();
         idmPages = new ArrayList<>();
         amPages = new ArrayList<>();
-        reportletConfs = new HashMap<>();
-        accountRuleConfs = new HashMap<>();
-        passwordRuleConfs = new HashMap<>();
-        pullCorrelationRuleConfs = new HashMap<>();
-        pushCorrelationRuleConfs = new HashMap<>();
 
         scanner().findCandidateComponents(getBasePackage()).forEach(bd -> {
             try {
@@ -208,15 +193,11 @@ public class ClassPathScanImplementationLookup {
                     } else if 
(BaseSSOLoginFormPanel.class.isAssignableFrom(clazz)) {
                         addClass(BaseSSOLoginFormPanel.class.getName(), clazz);
                     } else if (ReportletConf.class.isAssignableFrom(clazz)) {
-                        reportletConfs.put(clazz.getName(), (Class<? extends 
ReportletConf>) clazz);
+                        addClass(ReportletConf.class.getName(), clazz);
                     } else if (AccountRuleConf.class.isAssignableFrom(clazz)) {
-                        accountRuleConfs.put(clazz.getName(), (Class<? extends 
AccountRuleConf>) clazz);
+                        addClass(AccountRuleConf.class.getName(), clazz);
                     } else if (PasswordRuleConf.class.isAssignableFrom(clazz)) 
{
-                        passwordRuleConfs.put(clazz.getName(), (Class<? 
extends PasswordRuleConf>) clazz);
-                    } else if 
(PullCorrelationRuleConf.class.isAssignableFrom(clazz)) {
-                        pullCorrelationRuleConfs.put(clazz.getName(), (Class<? 
extends PullCorrelationRuleConf>) clazz);
-                    } else if 
(PushCorrelationRuleConf.class.isAssignableFrom(clazz)) {
-                        pushCorrelationRuleConfs.put(clazz.getName(), (Class<? 
extends PushCorrelationRuleConf>) clazz);
+                        addClass(PasswordRuleConf.class.getName(), clazz);
                     } else if (AbstractResource.class.isAssignableFrom(clazz)) 
{
                         if (clazz.isAnnotationPresent(Resource.class)) {
                             addClass(AbstractResource.class.getName(), clazz);
@@ -225,7 +206,8 @@ public class ClassPathScanImplementationLookup {
                                     Resource.class.getName(), clazz.getName());
                         }
                     } else {
-                        additional(clazz);
+                        contributors.forEach(contributor -> 
contributor.getLabel(clazz).
+                                ifPresent(label -> addClass(label, clazz)));
                     }
                 }
             } catch (Throwable t) {
@@ -257,18 +239,6 @@ public class ClassPathScanImplementationLookup {
         }
 
         classes.forEach((category, clazzes) -> LOG.debug("{} found: {}", 
category, clazzes));
-
-        reportletConfs = Collections.unmodifiableMap(reportletConfs);
-        accountRuleConfs = Collections.unmodifiableMap(accountRuleConfs);
-        passwordRuleConfs = Collections.unmodifiableMap(passwordRuleConfs);
-        pullCorrelationRuleConfs = 
Collections.unmodifiableMap(pullCorrelationRuleConfs);
-        pushCorrelationRuleConfs = 
Collections.unmodifiableMap(pushCorrelationRuleConfs);
-
-        LOG.debug("Reportlet configurations found: {}", reportletConfs);
-        LOG.debug("Account Rule configurations found: {}", accountRuleConfs);
-        LOG.debug("Password Rule configurations found: {}", passwordRuleConfs);
-        LOG.debug("Pull Correlation Rule configurations found: {}", 
pullCorrelationRuleConfs);
-        LOG.debug("Push Correlation Rule configurations found: {}", 
pushCorrelationRuleConfs);
     }
 
     public List<Class<? extends BasePage>> getIdRepoPageClasses() {
@@ -320,24 +290,4 @@ public class ClassPathScanImplementationLookup {
                 map(clazz -> (Class<? extends UserFormFinalizer>) clazz).
                 collect(Collectors.toList());
     }
-
-    public Map<String, Class<? extends ReportletConf>> getReportletConfs() {
-        return reportletConfs;
-    }
-
-    public Map<String, Class<? extends AccountRuleConf>> getAccountRuleConfs() 
{
-        return accountRuleConfs;
-    }
-
-    public Map<String, Class<? extends PasswordRuleConf>> 
getPasswordRuleConfs() {
-        return passwordRuleConfs;
-    }
-
-    public Map<String, Class<? extends PullCorrelationRuleConf>> 
getPullCorrelationRuleConfs() {
-        return pullCorrelationRuleConfs;
-    }
-
-    public Map<String, Class<? extends PushCorrelationRuleConf>> 
getPushCorrelationRuleConfs() {
-        return pushCorrelationRuleConfs;
-    }
 }
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 999630c..e67ef02 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
@@ -25,11 +25,11 @@ import java.util.List;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.BookmarkablePageLinkBuilder;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.annotations.AMPage;
 import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.annotations.IdMPage;
 import org.apache.syncope.client.ui.commons.HttpResourceStream;
-import 
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.rest.SyncopeRestClient;
 import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem;
 import org.apache.syncope.client.console.widgets.ExtAlertWidget;
@@ -66,7 +66,6 @@ import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 import org.apache.wicket.request.resource.ContentDisposition;
-import org.apache.wicket.spring.injection.annot.SpringBean;
 
 public class BasePage extends BaseWebPage {
 
@@ -74,9 +73,6 @@ public class BasePage extends BaseWebPage {
 
     protected static final HeaderItem META_IE_EDGE = new 
MetaHeaderItem("X-UA-Compatible", "IE=edge");
 
-    @SpringBean
-    protected ClassPathScanImplementationLookup lookup;
-
     public BasePage() {
         this(null);
     }
@@ -155,7 +151,7 @@ public class BasePage extends BaseWebPage {
         MetaDataRoleAuthorizationStrategy.authorize(link, WebPage.RENDER, 
IdRepoEntitlement.REPORT_LIST);
         liContainer.add(link);
 
-        List<Class<? extends BasePage>> idmPageClasses = 
lookup.getIdMPageClasses();
+        List<Class<? extends BasePage>> idmPageClasses = 
SyncopeWebApplication.get().getLookup().getIdMPageClasses();
         ListView<Class<? extends BasePage>> idmPages = new ListView<Class<? 
extends BasePage>>(
                 "idmPages", idmPageClasses) {
 
@@ -195,7 +191,7 @@ public class BasePage extends BaseWebPage {
         idmPages.setOutputMarkupId(true);
         body.add(idmPages);
 
-        List<Class<? extends BasePage>> amPageClasses = 
lookup.getAMPageClasses();
+        List<Class<? extends BasePage>> amPageClasses = 
SyncopeWebApplication.get().getLookup().getAMPageClasses();
         ListView<Class<? extends BasePage>> amPages = new ListView<Class<? 
extends BasePage>>(
                 "amPages", amPageClasses) {
 
@@ -445,7 +441,8 @@ public class BasePage extends BaseWebPage {
         }
 
         // Extensions
-        List<Class<? extends ExtAlertWidget<?>>> extAlertWidgetClasses = 
lookup.getExtAlertWidgetClasses();
+        List<Class<? extends ExtAlertWidget<?>>> extAlertWidgetClasses =
+                
SyncopeWebApplication.get().getLookup().getExtAlertWidgetClasses();
         ListView<Class<? extends ExtAlertWidget<?>>> extAlertWidgets = new 
ListView<Class<? extends ExtAlertWidget<?>>>(
                 "extAlertWidgets", extAlertWidgetClasses) {
 
@@ -468,7 +465,8 @@ public class BasePage extends BaseWebPage {
         };
         body.add(extAlertWidgets);
 
-        List<Class<? extends BaseExtPage>> extPageClasses = 
lookup.getClasses(BaseExtPage.class);
+        List<Class<? extends BaseExtPage>> extPageClasses =
+                
SyncopeWebApplication.get().getLookup().getClasses(BaseExtPage.class);
 
         WebMarkupContainer extensionsLI = new 
WebMarkupContainer(getLIContainerId("extensions"));
         extensionsLI.setOutputMarkupPlaceholderTag(true);
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
index fdd1691..c503062 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Dashboard.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.pages;
 import 
de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel;
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.syncope.client.console.SyncopeWebApplication;
 import org.apache.syncope.client.console.panels.DashboardAccessTokensPanel;
 import org.apache.syncope.client.console.panels.DashboardControlPanel;
 import org.apache.syncope.client.console.panels.DashboardExtensionsPanel;
@@ -79,7 +80,8 @@ public class Dashboard extends BasePage {
             }
         });
 
-        List<Class<? extends BaseExtWidget>> extWidgetClasses = 
lookup.getClasses(BaseExtWidget.class);
+        List<Class<? extends BaseExtWidget>> extWidgetClasses =
+                
SyncopeWebApplication.get().getLookup().getClasses(BaseExtWidget.class);
         if (!extWidgetClasses.isEmpty()) {
             tabs.add(new AbstractTab(new ResourceModel("extensions")) {
 
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 0a664ad..37944e8 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
@@ -24,7 +24,6 @@ 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.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.ui.commons.BaseLogin;
 import org.apache.syncope.client.ui.commons.BaseSession;
 import org.apache.syncope.client.ui.commons.panels.BaseSSOLoginFormPanel;
@@ -33,15 +32,11 @@ import 
org.apache.wicket.authentication.IAuthenticationStrategy;
 import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
 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 = 5889157642852559004L;
 
-    @SpringBean
-    private ClassPathScanImplementationLookup lookup;
-
     public Login(final PageParameters parameters) {
         super(parameters);
     }
@@ -54,7 +49,7 @@ public class Login extends BaseLogin {
     @Override
     protected List<Panel> getSSOLoginFormPanels() {
         List<Panel> ssoLoginFormPanels = new ArrayList<>();
-        
lookup.getClasses(BaseSSOLoginFormPanel.class).forEach(ssoLoginFormPanel -> {
+        
SyncopeWebApplication.get().getLookup().getClasses(BaseSSOLoginFormPanel.class).forEach(ssoLoginFormPanel
 -> {
             try {
                 
ssoLoginFormPanels.add(ssoLoginFormPanel.getConstructor(String.class, 
BaseSession.class).newInstance(
                         "ssoLogin", SyncopeConsoleSession.get()));
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractModalPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractModalPanel.java
index 2d7dee8..9981037 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractModalPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AbstractModalPanel.java
@@ -20,9 +20,9 @@ package org.apache.syncope.client.console.panels;
 
 import org.apache.syncope.client.ui.commons.panels.SubmitableModalPanel;
 import java.io.Serializable;
-import org.apache.syncope.client.console.pages.BasePage;
 import org.apache.syncope.client.console.wicket.markup.head.MetaHeaderItem;
 import 
org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -72,12 +72,12 @@ public class AbstractModalPanel<T extends Serializable> 
extends Panel
 
     @Override
     public void onSubmit(final AjaxRequestTarget target) {
-        ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
     }
 
     @Override
     public void onError(final AjaxRequestTarget target) {
-        ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+        ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
     }
 
     @Override
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AttrListDirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AttrListDirectoryPanel.java
new file mode 100644
index 0000000..dfe285d
--- /dev/null
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AttrListDirectoryPanel.java
@@ -0,0 +1,119 @@
+/*
+ * 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.console.panels;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import 
org.apache.syncope.client.console.panels.AttrListDirectoryPanel.AttrListProvider;
+import org.apache.syncope.client.console.rest.BaseRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.PageReference;
+import 
org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+
+public abstract class AttrListDirectoryPanel
+        extends DirectoryPanel<Attr, Attr, AttrListProvider, BaseRestClient> {
+
+    private static final long serialVersionUID = 1L;
+
+    protected AttrListDirectoryPanel(final String id, final PageReference 
pageRef, final boolean wizardInModal) {
+        super(id, pageRef, wizardInModal);
+
+        itemKeyFieldName = "schema";
+        disableCheckBoxes();
+
+        modal.size(Modal.Size.Default);
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBatches() {
+        return List.of();
+    }
+
+    @Override
+    protected List<IColumn<Attr, String>> getColumns() {
+        final List<IColumn<Attr, String>> columns = new ArrayList<>();
+        columns.add(new PropertyColumn<>(new ResourceModel("schema"), 
"schema"));
+        columns.add(new PropertyColumn<Attr, String>(new 
ResourceModel("values"), "values") {
+
+            private static final long serialVersionUID = -1822504503325964706L;
+
+            @Override
+            public void populateItem(
+                    final Item<ICellPopulator<Attr>> item,
+                    final String componentId,
+                    final IModel<Attr> rowModel) {
+
+                if (rowModel.getObject().getValues().toString().length() > 96) 
{
+                    item.add(new Label(componentId, getString("tooLong")).
+                            add(new AttributeModifier("style", 
"font-style:italic")));
+                } else {
+                    super.populateItem(item, componentId, rowModel);
+                }
+            }
+        });
+        return columns;
+    }
+
+    protected abstract static class AttrListProvider extends 
DirectoryDataProvider<Attr> {
+
+        private static final long serialVersionUID = -185944053385660794L;
+
+        private final SortableDataProviderComparator<Attr> comparator;
+
+        protected AttrListProvider(final int paginatorRows) {
+            super(paginatorRows);
+            setSort("schema", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<>(this);
+        }
+
+        protected abstract List<Attr> list();
+
+        @Override
+        public Iterator<Attr> iterator(final long first, final long count) {
+            List<Attr> result = list();
+            result.sort(comparator);
+            return result.subList((int) first, (int) first + (int) 
count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return list().size();
+        }
+
+        @Override
+        public IModel<Attr> model(final Attr object) {
+            return new CompoundPropertyModel<>(object);
+        }
+    }
+}
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
index 29c8447..fae6887 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/DirectoryPanel.java
@@ -349,7 +349,7 @@ public abstract class DirectoryPanel<
     }
 
     protected ActionsPanel<T> getActions(final IModel<T> model) {
-        return model == null ? new ActionsPanel<>("actions", new Model<>()) : 
new ActionsPanel<>("actions", model);
+        return new ActionsPanel<>("actions", model == null ? new Model<>() : 
model);
     }
 
     protected ActionLinksTogglePanel<T> actionTogglePanel() {
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java
index 92220e3..7545c03 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/ParametersDirectoryPanel.java
@@ -29,7 +29,6 @@ import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
 import org.apache.syncope.client.console.commons.IdRepoConstants;
 import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
-import org.apache.syncope.client.console.pages.BasePage;
 import 
org.apache.syncope.client.console.panels.ParametersDirectoryPanel.ParametersProvider;
 import org.apache.syncope.client.console.rest.SyncopeRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
@@ -37,6 +36,7 @@ import 
org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.ui.commons.wizards.AbstractModalPanelBuilder;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
 import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.ui.commons.pages.BaseWebPage;
 import org.apache.syncope.client.ui.commons.panels.WizardModalPanel;
 import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.wicket.AttributeModifier;
@@ -166,7 +166,7 @@ public class ParametersDirectoryPanel
                     LOG.error("While deleting {}", model.getObject(), e);
                     SyncopeConsoleSession.get().onException(e);
                 }
-                ((BasePage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
+                ((BaseWebPage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
             }
         }, ActionLink.ActionType.DELETE, null, true);
 
diff --git 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
index 099ea90..7c0ca80 100644
--- 
a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
+++ 
b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wizards/WizardMgtPanel.java
@@ -95,7 +95,6 @@ public abstract class WizardMgtPanel<T extends Serializable> 
extends AbstractWiz
             super.onConfigure();
             setFooterVisible(footerVisibility);
         }
-
     };
 
     protected WizardMgtPanel(final String id) {
@@ -160,7 +159,6 @@ public abstract class WizardMgtPanel<T extends 
Serializable> extends AbstractWiz
             protected void populateItem(final ListItem<Component> item) {
                 item.add(item.getModelObject());
             }
-
         });
     }
 
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel.properties
similarity index 100%
rename from 
client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel.properties
rename to 
client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel.properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_fr_CA.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_fr_CA.properties
similarity index 100%
rename from 
client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_fr_CA.properties
rename to 
client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_fr_CA.properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_it.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_it.properties
similarity index 100%
rename from 
client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_it.properties
rename to 
client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_it.properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_ja.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_ja.properties
similarity index 100%
rename from 
client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_ja.properties
rename to 
client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_ja.properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_pt_BR.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_pt_BR.properties
similarity index 100%
rename from 
client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_pt_BR.properties
rename to 
client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_pt_BR.properties
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_ru.properties
 
b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_ru.properties
similarity index 100%
rename from 
client/am/console/src/main/resources/org/apache/syncope/client/console/panels/WAConfigDirectoryPanel_ru.properties
rename to 
client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/AttrListDirectoryPanel_ru.properties
diff --git 
a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
 
b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
index 05deb81..0dca195 100644
--- 
a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
+++ 
b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
@@ -33,6 +33,7 @@ import java.io.InputStream;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Properties;
+import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.cxf.jaxrs.client.Client;
@@ -123,7 +124,7 @@ public abstract class AbstractTest {
 
         @Bean
         public ClassPathScanImplementationLookup 
classPathScanImplementationLookup() {
-            ClassPathScanImplementationLookup lookup = new 
ClassPathScanImplementationLookup();
+            ClassPathScanImplementationLookup lookup = new 
ClassPathScanImplementationLookup(Set.of());
             lookup.load();
             return lookup;
         }
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyTO.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyTO.java
index 093ca7a..4f2026b 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyTO.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyTO.java
@@ -23,6 +23,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 
+@Schema(allOf = { PolicyTO.class })
 public class AccessPolicyTO extends PolicyTO {
 
     private static final long serialVersionUID = -6711411162433533300L;
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyTO.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyTO.java
index 5e43b9a..0f4d68c 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyTO.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyTO.java
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 
+@Schema(allOf = { PolicyTO.class })
 public class AttrReleasePolicyTO extends PolicyTO {
 
     private static final long serialVersionUID = -1432411162433533300L;
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyTO.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyTO.java
index f54e1b2..575b126 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyTO.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyTO.java
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 
+@Schema(allOf = { PolicyTO.class })
 public class AuthPolicyTO extends PolicyTO {
 
     private static final long serialVersionUID = -6711411162433533300L;
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/ClientAppTO.java 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/ClientAppTO.java
index d69826d..f00da29 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/ClientAppTO.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/ClientAppTO.java
@@ -21,11 +21,15 @@ package org.apache.syncope.common.lib.to;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import 
com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
 import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.ArrayList;
+import java.util.List;
 import javax.ws.rs.PathParam;
 import org.apache.commons.lang3.builder.EqualsBuilder;
 import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.Attr;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = 
JsonTypeInfo.As.EXISTING_PROPERTY, property = "_class")
 @JsonPropertyOrder(value = { "_class", "key", "description" })
@@ -54,6 +58,8 @@ public abstract class ClientAppTO implements NamedEntityTO {
 
     private String theme;
 
+    private final List<Attr> properties = new ArrayList<>();
+
     @Schema(name = "_class", required = true)
     public abstract String getDiscriminator();
 
@@ -130,6 +136,12 @@ public abstract class ClientAppTO implements NamedEntityTO 
{
         this.description = description;
     }
 
+    @JacksonXmlElementWrapper(localName = "properties")
+    @JacksonXmlProperty(localName = "property")
+    public List<Attr> getProperties() {
+        return properties;
+    }
+
     @Override
     public int hashCode() {
         return new HashCodeBuilder()
@@ -142,6 +154,7 @@ public abstract class ClientAppTO implements NamedEntityTO {
                 .append(accessPolicy)
                 .append(attrReleasePolicy)
                 .append(theme)
+                .append(properties)
                 .toHashCode();
     }
 
@@ -167,6 +180,7 @@ public abstract class ClientAppTO implements NamedEntityTO {
                 .append(this.accessPolicy, rhs.accessPolicy)
                 .append(this.attrReleasePolicy, rhs.attrReleasePolicy)
                 .append(this.theme, rhs.theme)
+                .append(this.properties, rhs.properties)
                 .isEquals();
     }
 }
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCGrantType.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCGrantType.java
index 2304cbf..c1471b1 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCGrantType.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCGrantType.java
@@ -1,17 +1,20 @@
 /*
- * Copyright 2021 The Apache Software Foundation.
+ * 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
  *
- * 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
  *
- *      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.
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
  */
 package org.apache.syncope.common.lib.types;
 
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCResponseType.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCResponseType.java
index dbf919b..2ccffa9 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCResponseType.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCResponseType.java
@@ -1,17 +1,20 @@
 /*
- * Copyright 2021 The Apache Software Foundation.
+ * 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
  *
- * 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
  *
- *      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.
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
  */
 package org.apache.syncope.common.lib.types;
 
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractCorrelationRuleConf.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractCorrelationRuleConf.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractCorrelationRuleConf.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractCorrelationRuleConf.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPullCorrelationRuleConf.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPullCorrelationRuleConf.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPullCorrelationRuleConf.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPullCorrelationRuleConf.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPushCorrelationRuleConf.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPushCorrelationRuleConf.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPushCorrelationRuleConf.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultPushCorrelationRuleConf.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/ProvisioningPolicyTO.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/ProvisioningPolicyTO.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/ProvisioningPolicyTO.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/ProvisioningPolicyTO.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PullCorrelationRuleConf.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullPolicyTO.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PullPolicyTO.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PullPolicyTO.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PullPolicyTO.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PushCorrelationRuleConf.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PushCorrelationRuleConf.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PushCorrelationRuleConf.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PushCorrelationRuleConf.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PushPolicyTO.java
 
b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PushPolicyTO.java
similarity index 100%
rename from 
common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PushPolicyTO.java
rename to 
common/idm/lib/src/main/java/org/apache/syncope/common/lib/policy/PushPolicyTO.java
diff --git 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PolicyTO.java
 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PolicyTO.java
index 9c7bffe..fe9363d 100644
--- 
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PolicyTO.java
+++ 
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/policy/PolicyTO.java
@@ -33,9 +33,7 @@ import org.apache.syncope.common.lib.to.EntityTO;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = 
JsonTypeInfo.As.EXISTING_PROPERTY, property = "_class")
 @JsonPropertyOrder(value = { "_class", "key", "description" })
-@Schema(
-        subTypes = { AccountPolicyTO.class, PasswordPolicyTO.class, 
ProvisioningPolicyTO.class },
-        discriminatorProperty = "_class")
+@Schema(subTypes = { AccountPolicyTO.class, PasswordPolicyTO.class }, 
discriminatorProperty = "_class")
 public abstract class PolicyTO implements EntityTO {
 
     private static final long serialVersionUID = -2903888572649721035L;
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
index 31d38e7..e313e38 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
@@ -18,6 +18,8 @@
  */
 package org.apache.syncope.core.persistence.api.entity.auth;
 
+import java.util.List;
+import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
@@ -57,4 +59,8 @@ public interface ClientApp extends Entity {
     void setTheme(String name);
 
     String getTheme();
+
+    List<Attr> getProperties();
+
+    void setProperties(List<Attr> properties);
 }
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
index 611f203..b1a9e9e 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
@@ -18,6 +18,9 @@
  */
 package org.apache.syncope.core.persistence.jpa.entity.auth;
 
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.util.ArrayList;
+import java.util.List;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
 import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
@@ -29,9 +32,12 @@ import 
org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccessPolicy;
 import javax.persistence.Column;
 import javax.persistence.FetchType;
+import javax.persistence.Lob;
 import javax.persistence.ManyToOne;
 import javax.persistence.MappedSuperclass;
+import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 
 @MappedSuperclass
 public class AbstractClientApp extends AbstractGeneratedKeyEntity implements 
ClientApp {
@@ -62,6 +68,9 @@ public class AbstractClientApp extends 
AbstractGeneratedKeyEntity implements Cli
     @ManyToOne(fetch = FetchType.EAGER)
     private JPAAttrReleasePolicy attrReleasePolicy;
 
+    @Lob
+    private String properties;
+
     @Override
     public Long getClientAppId() {
         return clientAppId;
@@ -145,4 +154,17 @@ public class AbstractClientApp extends 
AbstractGeneratedKeyEntity implements Cli
     public void setTheme(final String theme) {
         this.theme = theme;
     }
+
+    @Override
+    public List<Attr> getProperties() {
+        return properties == null
+                ? new ArrayList<>(0)
+                : POJOHelper.deserialize(properties, new 
TypeReference<List<Attr>>() {
+                });
+    }
+
+    @Override
+    public void setProperties(final List<Attr> properties) {
+        this.properties = POJOHelper.serialize(properties);
+    }
 }
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
index 4b62c0e..ee766e3 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
@@ -102,7 +102,7 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
     }
 
     private void doUpdate(final SAML2SP clientApp, final SAML2SPTO 
clientAppTO) {
-        doUpdateCommon(clientApp, clientAppTO);
+        copyToEntity(clientApp, clientAppTO);
 
         clientApp.setEntityId(clientAppTO.getEntityId());
         clientApp.setMetadataLocation(clientAppTO.getMetadataLocation());
@@ -140,9 +140,29 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
                 clientAppTO.getEncryptionBlackListedAlgorithms());
     }
 
+    private static void copyToTO(final ClientApp clientApp, final ClientAppTO 
clientAppTO) {
+        clientAppTO.setName(clientApp.getName());
+        clientAppTO.setKey(clientApp.getKey());
+        clientAppTO.setDescription(clientApp.getDescription());
+        clientAppTO.setClientAppId(clientApp.getClientAppId());
+        clientAppTO.setTheme(clientApp.getTheme());
+
+        if (clientApp.getAuthPolicy() != null) {
+            clientAppTO.setAuthPolicy(clientApp.getAuthPolicy().getKey());
+        }
+        if (clientApp.getAccessPolicy() != null) {
+            clientAppTO.setAccessPolicy(clientApp.getAccessPolicy().getKey());
+        }
+        if (clientApp.getAttrReleasePolicy() != null) {
+            
clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy().getKey());
+        }
+
+        clientAppTO.getProperties().addAll(clientApp.getProperties());
+    }
+
     private static SAML2SPTO getSAMLClientAppTO(final SAML2SP clientApp) {
         SAML2SPTO clientAppTO = new SAML2SPTO();
-        updateCommonClientAppTO(clientApp, clientAppTO);
+        copyToTO(clientApp, clientAppTO);
 
         clientAppTO.setEntityId(clientApp.getEntityId());
         clientAppTO.setMetadataLocation(clientApp.getMetadataLocation());
@@ -182,7 +202,7 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
     }
 
     private void doUpdate(final OIDCRP clientApp, final OIDCRPTO clientAppTO) {
-        doUpdateCommon(clientApp, clientAppTO);
+        copyToEntity(clientApp, clientAppTO);
 
         clientApp.setClientSecret(clientAppTO.getClientSecret());
         clientApp.setClientId(clientAppTO.getClientId());
@@ -200,7 +220,7 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
 
     private static OIDCRPTO getOIDCClientAppTO(final OIDCRP clientApp) {
         OIDCRPTO clientAppTO = new OIDCRPTO();
-        updateCommonClientAppTO(clientApp, clientAppTO);
+        copyToTO(clientApp, clientAppTO);
 
         clientAppTO.setClientId(clientApp.getClientId());
         clientAppTO.setClientSecret(clientApp.getClientSecret());
@@ -215,34 +235,19 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
     }
 
     private void doUpdate(final CASSP clientApp, final CASSPTO clientAppTO) {
-        doUpdateCommon(clientApp, clientAppTO);
+        copyToEntity(clientApp, clientAppTO);
 
         clientApp.setServiceId(clientAppTO.getServiceId());
     }
 
     private static CASSPTO getCASClientAppTO(final CASSP clientApp) {
         CASSPTO clientAppTO = new CASSPTO();
-        updateCommonClientAppTO(clientApp, clientAppTO);
+        copyToTO(clientApp, clientAppTO);
         clientAppTO.setServiceId(clientApp.getServiceId());
         return clientAppTO;
     }
 
-    private static void updateCommonClientAppTO(final ClientApp clientApp, 
final ClientAppTO clientAppTO) {
-        clientAppTO.setName(clientApp.getName());
-        clientAppTO.setKey(clientApp.getKey());
-        clientAppTO.setDescription(clientApp.getDescription());
-        clientAppTO.setClientAppId(clientApp.getClientAppId());
-        clientAppTO.setTheme(clientApp.getTheme());
-
-        clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
-                ? null : clientApp.getAuthPolicy().getKey());
-        clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
-                ? null : clientApp.getAccessPolicy().getKey());
-        clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == 
null
-                ? null : clientApp.getAttrReleasePolicy().getKey());
-    }
-
-    private void doUpdateCommon(final ClientApp clientApp, final ClientAppTO 
clientAppTO) {
+    private void copyToEntity(final ClientApp clientApp, final ClientAppTO 
clientAppTO) {
         clientApp.setName(clientAppTO.getName());
         clientApp.setClientAppId(clientAppTO.getClientAppId());
         clientApp.setDescription(clientAppTO.getDescription());
@@ -289,5 +294,7 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
                 throw sce;
             }
         }
+
+        clientApp.setProperties(clientAppTO.getProperties());
     }
 }
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 8e7c2c6..2088d50 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
@@ -25,6 +25,7 @@ import 
com.giffing.wicket.spring.boot.starter.configuration.extensions.core.sett
 import 
com.giffing.wicket.spring.boot.starter.configuration.extensions.external.spring.boot.actuator.WicketEndpointRepositoryDefault;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import org.apache.syncope.client.console.SyncopeAMConsoleContext;
 import org.apache.syncope.client.console.SyncopeIdMConsoleContext;
 import org.apache.syncope.client.console.SyncopeWebApplication;
@@ -82,7 +83,7 @@ public abstract class AbstractConsoleITCase extends 
AbstractUITCase {
 
         @Bean
         public ClassPathScanImplementationLookup 
classPathScanImplementationLookup() {
-            ClassPathScanImplementationLookup lookup = new 
ClassPathScanImplementationLookup();
+            ClassPathScanImplementationLookup lookup = new 
ClassPathScanImplementationLookup(Set.of());
             lookup.load();
             return lookup;
         }
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AbstractClientAppMapper.java
similarity index 59%
copy from 
wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
copy to 
wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AbstractClientAppMapper.java
index 7393a30..4c1064a 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/AbstractClientAppMapper.java
@@ -18,33 +18,40 @@
  */
 package org.apache.syncope.wa.starter.mapping;
 
-import org.apache.syncope.common.lib.to.CASSPTO;
-import org.apache.syncope.common.lib.wa.WAClientApp;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.ClientAppTO;
+import org.apereo.cas.services.DefaultRegisteredServiceProperty;
 import org.apereo.cas.services.RegexRegisteredService;
-import org.apereo.cas.services.RegisteredService;
 import org.apereo.cas.services.RegisteredServiceAccessStrategy;
 import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
 import org.apereo.cas.services.RegisteredServiceAuthenticationPolicy;
+import org.apereo.cas.services.RegisteredServiceProperty;
 
-@ClientAppMapFor(clientAppClass = CASSPTO.class)
-public class CASSPTOMapper implements ClientAppMapper {
+abstract class AbstractClientAppMapper implements ClientAppMapper {
 
-    @Override
-    public RegisteredService build(
-            final WAClientApp clientApp,
+    protected void setCommon(final RegexRegisteredService service, final 
ClientAppTO clientApp) {
+        service.setId(clientApp.getClientAppId());
+        service.setName(clientApp.getName());
+        service.setDescription(clientApp.getDescription());
+
+        if (!clientApp.getProperties().isEmpty()) {
+            Map<String, RegisteredServiceProperty> properties = 
clientApp.getProperties().stream().
+                    collect(Collectors.toMap(
+                            Attr::getSchema,
+                            attr -> new 
DefaultRegisteredServiceProperty(attr.getValues()),
+                            (existing, replacement) -> existing));
+            service.setProperties(properties);
+        }
+    }
+
+    protected void setPolicies(
+            final RegexRegisteredService service,
             final RegisteredServiceAuthenticationPolicy authenticationPolicy,
             final RegisteredServiceAccessStrategy accessStrategy,
             final RegisteredServiceAttributeReleasePolicy 
attributeReleasePolicy) {
 
-        CASSPTO cas = CASSPTO.class.cast(clientApp.getClientAppTO());
-
-        RegexRegisteredService service = new RegexRegisteredService();
-
-        service.setServiceId(cas.getServiceId());
-        service.setId(cas.getClientAppId());
-        service.setName(cas.getName());
-        service.setDescription(cas.getDescription());
-
         if (authenticationPolicy != null) {
             service.setAuthenticationPolicy(authenticationPolicy);
         }
@@ -54,7 +61,5 @@ public class CASSPTOMapper implements ClientAppMapper {
         if (attributeReleasePolicy != null) {
             service.setAttributeReleasePolicy(attributeReleasePolicy);
         }
-
-        return service;
     }
 }
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
index 7393a30..74c41a8 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
@@ -27,10 +27,10 @@ import 
org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
 import org.apereo.cas.services.RegisteredServiceAuthenticationPolicy;
 
 @ClientAppMapFor(clientAppClass = CASSPTO.class)
-public class CASSPTOMapper implements ClientAppMapper {
+public class CASSPTOMapper extends AbstractClientAppMapper {
 
     @Override
-    public RegisteredService build(
+    public RegisteredService map(
             final WAClientApp clientApp,
             final RegisteredServiceAuthenticationPolicy authenticationPolicy,
             final RegisteredServiceAccessStrategy accessStrategy,
@@ -39,21 +39,10 @@ public class CASSPTOMapper implements ClientAppMapper {
         CASSPTO cas = CASSPTO.class.cast(clientApp.getClientAppTO());
 
         RegexRegisteredService service = new RegexRegisteredService();
-
         service.setServiceId(cas.getServiceId());
-        service.setId(cas.getClientAppId());
-        service.setName(cas.getName());
-        service.setDescription(cas.getDescription());
+        setCommon(service, cas);
 
-        if (authenticationPolicy != null) {
-            service.setAuthenticationPolicy(authenticationPolicy);
-        }
-        if (accessStrategy != null) {
-            service.setAccessStrategy(accessStrategy);
-        }
-        if (attributeReleasePolicy != null) {
-            service.setAttributeReleasePolicy(attributeReleasePolicy);
-        }
+        setPolicies(service, authenticationPolicy, accessStrategy, 
attributeReleasePolicy);
 
         return service;
     }
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java
index b0fff5e..4951adb 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/ClientAppMapper.java
@@ -27,7 +27,7 @@ import 
org.apereo.cas.services.RegisteredServiceAuthenticationPolicy;
 @FunctionalInterface
 public interface ClientAppMapper {
 
-    RegisteredService build(
+    RegisteredService map(
             WAClientApp clientApp,
             RegisteredServiceAuthenticationPolicy authPolicy,
             RegisteredServiceAccessStrategy accessStrategy,
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPTOMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPTOMapper.java
index afd9684..1b8a1a4 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPTOMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/OIDCRPTOMapper.java
@@ -47,28 +47,26 @@ import org.slf4j.LoggerFactory;
 import org.springframework.context.ApplicationContext;
 
 @ClientAppMapFor(clientAppClass = OIDCRPTO.class)
-public class OIDCRPTOMapper implements ClientAppMapper {
+public class OIDCRPTOMapper extends AbstractClientAppMapper {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(OIDCRPTOMapper.class);
 
     private static final String CUSTOM_SCOPE = "syncope";
 
     @Override
-    public RegisteredService build(
+    public RegisteredService map(
             final WAClientApp clientApp,
             final RegisteredServiceAuthenticationPolicy authenticationPolicy,
             final RegisteredServiceAccessStrategy accessStrategy,
             final RegisteredServiceAttributeReleasePolicy 
attributeReleasePolicy) {
 
         OIDCRPTO rp = OIDCRPTO.class.cast(clientApp.getClientAppTO());
-
         OidcRegisteredService service = new OidcRegisteredService();
+        setCommon(service, rp);
+
         service.setServiceId(Stream.concat(rp.getRedirectUris().stream(), 
Stream.of(rp.getLogoutUri())).
                 filter(Objects::nonNull).
                 collect(Collectors.joining("|")));
-        service.setId(rp.getClientAppId());
-        service.setName(rp.getName());
-        service.setDescription(rp.getDescription());
         service.setRedirectUrl(service.getServiceId());
         service.setClientId(rp.getClientId());
         service.setClientSecret(rp.getClientSecret());
@@ -86,12 +84,7 @@ public class OIDCRPTOMapper implements ClientAppMapper {
         }
         service.setLogoutUrl(rp.getLogoutUri());
 
-        if (authenticationPolicy != null) {
-            service.setAuthenticationPolicy(authenticationPolicy);
-        }
-        if (accessStrategy != null) {
-            service.setAccessStrategy(accessStrategy);
-        }
+        setPolicies(service, authenticationPolicy, accessStrategy, 
attributeReleasePolicy);
         if (attributeReleasePolicy != null) {
             ChainingAttributeReleasePolicy chain = new 
ChainingAttributeReleasePolicy();
             if (attributeReleasePolicy instanceof 
ReturnMappedAttributeReleasePolicy) {
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
index 5991515..f6f4fd8 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/RegisteredServiceMapper.java
@@ -90,6 +90,6 @@ public class RegisteredServiceMapper {
             }
         }
 
-        return clientAppMapper.build(clientApp, authPolicy, accessStrategy, 
attributeReleasePolicy);
+        return clientAppMapper.map(clientApp, authPolicy, accessStrategy, 
attributeReleasePolicy);
     }
 }
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPTOMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPTOMapper.java
index 704e7a7..610d0e0 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPTOMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPTOMapper.java
@@ -18,6 +18,7 @@
  */
 package org.apache.syncope.wa.starter.mapping;
 
+import java.util.Optional;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.to.SAML2SPTO;
 import org.apache.syncope.common.lib.wa.WAClientApp;
@@ -28,23 +29,20 @@ import 
org.apereo.cas.services.RegisteredServiceAuthenticationPolicy;
 import org.apereo.cas.support.saml.services.SamlRegisteredService;
 
 @ClientAppMapFor(clientAppClass = SAML2SPTO.class)
-public class SAML2SPTOMapper implements ClientAppMapper {
+public class SAML2SPTOMapper extends AbstractClientAppMapper {
 
     @Override
-    public RegisteredService build(
+    public RegisteredService map(
             final WAClientApp clientApp,
             final RegisteredServiceAuthenticationPolicy authenticationPolicy,
             final RegisteredServiceAccessStrategy accessStrategy,
             final RegisteredServiceAttributeReleasePolicy 
attributeReleasePolicy) {
 
         SAML2SPTO sp = SAML2SPTO.class.cast(clientApp.getClientAppTO());
-
         SamlRegisteredService service = new SamlRegisteredService();
+        setCommon(service, sp);
 
         service.setServiceId(sp.getEntityId());
-        service.setId(sp.getClientAppId());
-        service.setName(sp.getName());
-        service.setDescription(sp.getDescription());
 
         service.setMetadataLocation(sp.getMetadataLocation());
         
service.setMetadataSignatureLocation(sp.getMetadataSignatureLocation());
@@ -54,22 +52,14 @@ public class SAML2SPTOMapper implements ClientAppMapper {
         service.setEncryptAssertions(sp.isEncryptAssertions());
         
service.setRequiredAuthenticationContextClass(sp.getRequiredAuthenticationContextClass());
         
service.setRequiredNameIdFormat(sp.getRequiredNameIdFormat().getNameId());
-        service.setSkewAllowance(sp.getSkewAllowance() == null ? 0 : 
sp.getSkewAllowance());
+        
service.setSkewAllowance(Optional.ofNullable(sp.getSkewAllowance()).orElse(0));
         service.setNameIdQualifier(sp.getNameIdQualifier());
         if (!sp.getAssertionAudiences().isEmpty()) {
             
service.setAssertionAudiences(sp.getAssertionAudiences().stream().collect(Collectors.joining(",")));
         }
         
service.setServiceProviderNameIdQualifier(sp.getServiceProviderNameIdQualifier());
 
-        if (authenticationPolicy != null) {
-            service.setAuthenticationPolicy(authenticationPolicy);
-        }
-        if (accessStrategy != null) {
-            service.setAccessStrategy(accessStrategy);
-        }
-        if (attributeReleasePolicy != null) {
-            service.setAttributeReleasePolicy(attributeReleasePolicy);
-        }
+        setPolicies(service, authenticationPolicy, accessStrategy, 
attributeReleasePolicy);
 
         return service;
     }

Reply via email to