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 49fd6223cd [SYNCOPE-1822] added support for SCIM user extension (#773)
49fd6223cd is described below
commit 49fd6223cd6502be8f1c18a5ee41674072e4b29b
Author: Samuel Garofalo <[email protected]>
AuthorDate: Wed Jul 3 15:56:43 2024 +0200
[SYNCOPE-1822] added support for SCIM user extension (#773)
---
.../org/apache/syncope/common/lib/to/Item.java | 18 +-
.../console/panels/SCIMConfExtensionUserPanel.java | 83 ++++++
.../client/console/panels/SCIMConfPanel.java | 28 ++-
.../panels/mapping/SCIMExtentionMappingPanel.java | 279 +++++++++++++++++++++
.../console/panels/SCIMConfExtensionUserPanel.html | 27 ++
.../client/console/panels/SCIMConfPanel.properties | 3 +-
.../console/panels/SCIMConfPanel_it.properties | 3 +-
.../console/panels/SCIMConfPanel_pt_BR.properties | 3 +-
.../console/panels/SCIMConfPanel_ru.properties | 3 +-
.../panels/mapping/SCIMExtentionMappingPanel.html | 105 ++++++++
.../SCIMExtentionMappingPanel.properties} | 15 +-
.../SCIMExtentionMappingPanel_fr.properties} | 15 +-
.../SCIMExtentionMappingPanel_it.properties} | 15 +-
.../SCIMExtentionMappingPanel_ja.properties} | 15 +-
.../SCIMExtentionMappingPanel_pt_BR.properties} | 15 +-
.../SCIMExtentionMappingPanel_ru.properties} | 15 +-
.../apache/syncope/common/lib/scim/SCIMConf.java | 10 +
.../common/lib/scim/SCIMExtensionUserConf.java | 72 ++++++
.../apache/syncope/common/lib/scim/SCIMItem.java | 114 +++++++++
.../syncope/common/lib/scim/SCIMReturned.java | 37 +++
.../apache/syncope/core/logic/SCIMDataBinder.java | 164 ++++++------
.../org/apache/syncope/core/logic/SCIMLogic.java | 61 +++--
.../syncope/core/logic/scim/SCIMConfManager.java | 41 +++
.../syncope/core/logic/scim/SearchCondVisitor.java | 9 +
.../ext/scimv2/api/data/SCIMExtensionInfo.java | 33 +++
.../syncope/ext/scimv2/api/data/SCIMUser.java | 13 +-
.../syncope/ext/scimv2/api/type/Resource.java | 1 +
.../org/apache/syncope/fit/core/SCIMITCase.java | 103 +++++++-
28 files changed, 1138 insertions(+), 162 deletions(-)
diff --git
a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/Item.java
b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/Item.java
index 1cd85207c9..82f147cd02 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/Item.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/Item.java
@@ -35,44 +35,44 @@ public class Item implements Serializable {
* Attribute schema to be mapped. Consider other we can associate tha same
attribute schema more than once, with
* different aliases, to different resource attributes.
*/
- private String intAttrName;
+ protected String intAttrName;
/**
* External resource's field to be mapped.
*/
- private String extAttrName;
+ protected String extAttrName;
/**
* Specify if the mapped target resource's field is the key.
*/
- private boolean connObjectKey;
+ protected boolean connObjectKey;
/**
* Specify if the mapped target resource's field is the password.
*/
- private boolean password;
+ protected boolean password;
/**
* Specify if the mapped target resource's field is nullable.
*/
- private String mandatoryCondition = "false";
+ protected String mandatoryCondition = "false";
/**
* Mapping purposes.
*/
- private MappingPurpose purpose;
+ protected MappingPurpose purpose;
/**
* (Optional) JEXL expression to apply to values before propagation.
*/
- private String propagationJEXLTransformer;
+ protected String propagationJEXLTransformer;
/**
* (Optional) JEXL expression to apply to values before pull.
*/
- private String pullJEXLTransformer;
+ protected String pullJEXLTransformer;
- private final List<String> transformers = new ArrayList<>();
+ protected final List<String> transformers = new ArrayList<>();
public boolean isConnObjectKey() {
return connObjectKey;
diff --git
a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.java
b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.java
new file mode 100644
index 0000000000..b3870d3a23
--- /dev/null
+++
b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.java
@@ -0,0 +1,83 @@
+/*
+ * 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
org.apache.syncope.client.console.panels.mapping.SCIMExtentionMappingPanel;
+import
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.scim.SCIMConf;
+import org.apache.syncope.common.lib.scim.SCIMExtensionUserConf;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.util.ListModel;
+
+public class SCIMConfExtensionUserPanel extends SCIMConfTabPanel {
+
+ private static final long serialVersionUID = 2459231778083046011L;
+
+ private final SCIMExtensionUserConf scimExtensionUserConf;
+
+ public SCIMConfExtensionUserPanel(final String id, final SCIMConf
scimConf) {
+ super(id);
+
+ if (scimConf.getExtensionUserConf() == null) {
+ scimConf.setExtensionUserConf(new SCIMExtensionUserConf());
+ }
+ scimExtensionUserConf = scimConf.getExtensionUserConf();
+
+ AjaxTextFieldPanel namePanel = new AjaxTextFieldPanel("name", "name",
new PropertyModel<>("name", "name") {
+
+ private static final long serialVersionUID = 7389942851813193481L;
+
+ @Override
+ public String getObject() {
+ return scimExtensionUserConf.getName();
+ }
+
+ @Override
+ public void setObject(final String object) {
+ scimExtensionUserConf.setName(object);
+ }
+ });
+ add(namePanel);
+
+ AjaxTextFieldPanel descriptionPanel = new
AjaxTextFieldPanel("description", "description",
+ new PropertyModel<>("description", "description") {
+
+ private static final long serialVersionUID = -5911179251497048661L;
+
+ @Override
+ public String getObject() {
+ return scimExtensionUserConf.getDescription();
+ }
+
+ @Override
+ public void setObject(final String object) {
+ scimExtensionUserConf.setDescription(object);
+ }
+ });
+ add(descriptionPanel);
+
+ SCIMExtentionMappingPanel extentionMappingPanel = new
SCIMExtentionMappingPanel(
+ "mapping", new
ListModel<>(scimExtensionUserConf.getAttributes()));
+ Form<SCIMExtensionUserConf> form = new Form<>("form", new
Model<>(scimExtensionUserConf));
+ form.add(extentionMappingPanel);
+ add(form);
+ }
+}
diff --git
a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfPanel.java
b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfPanel.java
index 160062b962..4ecf6db0db 100644
---
a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfPanel.java
+++
b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfPanel.java
@@ -23,9 +23,12 @@ import
de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbed
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
import org.apache.syncope.client.console.commons.ITabComponent;
+import org.apache.syncope.client.console.pages.BasePage;
import org.apache.syncope.client.console.rest.SCIMConfRestClient;
import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.ui.commons.Constants;
import org.apache.syncope.common.lib.scim.SCIMConf;
import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -72,7 +75,14 @@ public abstract class SCIMConfPanel extends
WizardMgtPanel<SCIMConf> {
@Override
public void onClick(final AjaxRequestTarget target) {
- scimConfRestClient.set(SCIMConfPanel.this.scimConfTO);
+ try {
+ scimConfRestClient.set(SCIMConfPanel.this.scimConfTO);
+
SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+ } catch (Exception e) {
+ LOG.error("While setting SCIM configuration", e);
+ SyncopeConsoleSession.get().onException(e);
+ }
+ ((BasePage)
pageRef.getPage()).getNotificationPanel().refresh(target);
}
};
addInnerObject(saveButton);
@@ -136,6 +146,22 @@ public abstract class SCIMConfPanel extends
WizardMgtPanel<SCIMConf> {
tabs.add(new ITabComponent(
new Model<>(getString("tab4")), getString("tab4")) {
+ private static final long serialVersionUID = 6645614456650987567L;
+
+ @Override
+ public WebMarkupContainer getPanel(final String panelId) {
+ return new SCIMConfExtensionUserPanel(panelId, scimConfTO);
+ }
+
+ @Override
+ public boolean isVisible() {
+ return true;
+ }
+ });
+
+ tabs.add(new ITabComponent(
+ new Model<>(getString("tab5")), getString("tab5")) {
+
private static final long serialVersionUID = 1998052474181916792L;
@Override
diff --git
a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.java
b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.java
new file mode 100644
index 0000000000..892af3e81e
--- /dev/null
+++
b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.java
@@ -0,0 +1,279 @@
+/*
+ * 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.mapping;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import
org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
+import org.apache.syncope.client.console.rest.AnyTypeClassRestClient;
+import org.apache.syncope.client.console.rest.AnyTypeRestClient;
+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.ajax.form.IndicatorAjaxFormComponentUpdatingBehavior;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
+import
org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import
org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.scim.SCIMItem;
+import org.apache.syncope.common.lib.scim.SCIMReturned;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public class SCIMExtentionMappingPanel extends Panel {
+
+ private static final long serialVersionUID = -5268147603868322754L;
+
+ @SpringBean
+ protected AnyTypeRestClient anyTypeRestClient;
+
+ @SpringBean
+ protected AnyTypeClassRestClient anyTypeClassRestClient;
+
+ protected final Label intAttrNameInfo;
+
+ protected final WebMarkupContainer mandatoryHeader;
+
+ protected final Label caseExactLabel;
+
+ protected final Label mutabilityLabel;
+
+ protected final Label returnedLabel;
+
+ protected final Label uniquenessLabel;
+
+ protected final Label multiValuedLabel;
+
+ protected final AjaxButton addMappingBtn;
+
+ protected final ListView<SCIMItem> mappings;
+
+ protected final WebMarkupContainer mappingContainer;
+
+ public SCIMExtentionMappingPanel(
+ final String id,
+ final IModel<List<SCIMItem>> model) {
+
+ super(id);
+ setOutputMarkupId(true);
+
+ mappingContainer = new WebMarkupContainer("mappingContainer");
+ mappingContainer.setOutputMarkupId(true);
+ add(mappingContainer);
+
+ intAttrNameInfo = new Label("intAttrNameInfo", Model.of());
+ mappingContainer.add(intAttrNameInfo);
+
+ mandatoryHeader = new WebMarkupContainer("mandatoryHeader");
+ mandatoryHeader.setOutputMarkupId(true);
+ mappingContainer.add(mandatoryHeader);
+
+ caseExactLabel = new Label("caseExactLabel", Model.of());
+ mappingContainer.add(caseExactLabel);
+ mutabilityLabel = new Label("mutabilityLabel", Model.of());
+ mappingContainer.add(mutabilityLabel);
+ returnedLabel = new Label("returnedLabel", Model.of());
+ mappingContainer.add(returnedLabel);
+ uniquenessLabel = new Label("uniquenessLabel", Model.of());
+ mappingContainer.add(uniquenessLabel);
+ multiValuedLabel = new Label("multiValuedLabel", Model.of());
+ mappingContainer.add(multiValuedLabel);
+
+ mappings = new ListView<>("mappings", model) {
+
+ private static final long serialVersionUID = -8749412138042656239L;
+
+ @Override
+ protected void populateItem(final ListItem<SCIMItem> item) {
+ final SCIMItem itemTO = item.getModelObject();
+
+ //--------------------------------
+ // Internal attribute
+ // -------------------------------
+ AjaxTextFieldPanel intAttrName = new AjaxTextFieldPanel(
+ "intAttrName",
+ "intAttrName",
+ new PropertyModel<>(itemTO, "intAttrName"),
+ false);
+ intAttrName.setChoices(List.of());
+ intAttrName.setRequired(true).hideLabel();
+ item.add(intAttrName);
+ // -------------------------------
+
+ //--------------------------------
+ // External attribute
+ // -------------------------------
+ AjaxTextFieldPanel extAttrName = new AjaxTextFieldPanel(
+ "extAttrName",
+ "extAttrName",
+ new PropertyModel<>(itemTO, "extAttrName"));
+ extAttrName.setChoices(getExtAttrNames().getObject());
+
+ extAttrName.setRequired(true).hideLabel();
+ extAttrName.setEnabled(true);
+ item.add(extAttrName);
+ // -------------------------------
+
+ //--------------------------------
+ // mandatoryCondition
+ // -------------------------------
+ AjaxCheckBoxPanel mandatoryCondition = new AjaxCheckBoxPanel(
+ "mandatoryCondition",
+ "mandatoryCondition",
+ new PropertyModel<>(itemTO, "mandatoryCondition"));
+ mandatoryCondition.hideLabel();
+ mandatoryCondition.setEnabled(true);
+ item.add(mandatoryCondition);
+ // -------------------------------
+
+ //--------------------------------
+ // CaseExact
+ // -------------------------------
+ AjaxCheckBoxPanel caseExact = new AjaxCheckBoxPanel(
+ "caseExact",
+ "caseExact",
+ new PropertyModel<>(itemTO, "caseExact"));
+ caseExact.hideLabel();
+ caseExact.setEnabled(true);
+ item.add(caseExact);
+ // -------------------------------
+
+ //--------------------------------
+ // Mutability
+ // -------------------------------
+ AjaxCheckBoxPanel mutability = new AjaxCheckBoxPanel(
+ "mutability",
+ "mutability",
+ new PropertyModel<>(itemTO, "mutability"));
+ mutability.hideLabel();
+ mutability.setEnabled(true);
+ item.add(mutability);
+ // -------------------------------
+
+ //--------------------------------
+ // Returned
+ // -------------------------------
+ AjaxDropDownChoicePanel<SCIMReturned> returned = new
AjaxDropDownChoicePanel<>(
+ "returned",
+ "returned",
+ new PropertyModel<>(itemTO, "returned"));
+ returned.hideLabel();
+ returned.setChoices(List.of(SCIMReturned.values()));
+ returned.setEnabled(true);
+ item.add(returned);
+ // -------------------------------
+
+ //--------------------------------
+ // Uniqueness
+ // -------------------------------
+ AjaxCheckBoxPanel uniqueness = new AjaxCheckBoxPanel(
+ "uniqueness",
+ "uniqueness",
+ new PropertyModel<>(itemTO, "uniqueness"));
+ uniqueness.hideLabel();
+ uniqueness.setEnabled(true);
+ item.add(uniqueness);
+ // -------------------------------
+
+ //--------------------------------
+ // MultiValued
+ // -------------------------------
+ AjaxCheckBoxPanel multiValued = new AjaxCheckBoxPanel(
+ "multiValued",
+ "multiValued",
+ new PropertyModel<>(itemTO, "multiValued"));
+ multiValued.hideLabel();
+ multiValued.setEnabled(true);
+ item.add(multiValued);
+ // -------------------------------
+
+ //--------------------------------
+ // Remove
+ // -------------------------------
+ ActionsPanel<Serializable> actions = new
ActionsPanel<>("toRemove", null);
+ actions.add(new ActionLink<>() {
+
+ private static final long serialVersionUID =
-4097030429755746419L;
+
+ @Override
+ public void onClick(final AjaxRequestTarget target, final
Serializable ignore) {
+ model.getObject().remove(item.getIndex());
+ item.getParent().removeAll();
+ target.add(SCIMExtentionMappingPanel.this);
+ }
+ }, ActionLink.ActionType.DELETE, StringUtils.EMPTY,
true).hideLabel();
+ item.add(actions);
+ // -------------------------------
+
+ intAttrName.getField().add(new
IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+ private static final long serialVersionUID =
6890150953186587184L;
+
+ @Override
+ protected void onUpdate(final AjaxRequestTarget target) {
+ }
+ });
+ }
+ };
+
+ mappings.setReuseItems(true);
+ mappingContainer.add(mappings);
+
+ addMappingBtn = new IndicatingAjaxButton("addMappingBtn") {
+
+ private static final long serialVersionUID = -971427869417596230L;
+
+ @Override
+ protected void onSubmit(final AjaxRequestTarget target) {
+ model.getObject().add(new SCIMItem());
+ target.add(SCIMExtentionMappingPanel.this);
+ }
+ };
+ addMappingBtn.setDefaultFormProcessing(false);
+ addMappingBtn.setEnabled(true);
+ mappingContainer.add(addMappingBtn);
+ }
+
+ protected IModel<List<String>> getExtAttrNames() {
+ List<String> choices = new
ArrayList<>(ClassPathScanImplementationLookup.USER_FIELD_NAMES);
+
+
anyTypeClassRestClient.list(anyTypeRestClient.read(AnyTypeKind.USER.name()).getClasses()).
+ forEach(anyTypeClassTO -> {
+ choices.addAll(anyTypeClassTO.getPlainSchemas());
+ choices.addAll(anyTypeClassTO.getDerSchemas());
+ choices.addAll(anyTypeClassTO.getVirSchemas());
+ });
+
+ Collections.sort(choices);
+ return Model.ofList(choices);
+ }
+}
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.html
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.html
new file mode 100644
index 0000000000..a14c9a7cf2
--- /dev/null
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.html
@@ -0,0 +1,27 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org">
+ <wicket:panel>
+ <div wicket:id="name"/>
+ <div wicket:id="description"/>
+ <form data-example-id="simple-input-groups" class="bs-example
bs-example-form" wicket:id="form">
+ <span wicket:id="mapping">[modal content]</span>
+ </form>
+ </wicket:panel>
+</html>
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel.properties
index 10d4781a8f..fd36da900c 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel.properties
@@ -19,4 +19,5 @@ tab2=User
tab3=EnterpriseUser
saveButton=Save
-tab4=Group
+tab4=ExtensionUser
+tab5=Group
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_it.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_it.properties
index 10d4781a8f..fd36da900c 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_it.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_it.properties
@@ -19,4 +19,5 @@ tab2=User
tab3=EnterpriseUser
saveButton=Save
-tab4=Group
+tab4=ExtensionUser
+tab5=Group
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
index 10d4781a8f..fd36da900c 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
@@ -19,4 +19,5 @@ tab2=User
tab3=EnterpriseUser
saveButton=Save
-tab4=Group
+tab4=ExtensionUser
+tab5=Group
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_ru.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_ru.properties
index 10d4781a8f..fd36da900c 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_ru.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_ru.properties
@@ -19,4 +19,5 @@ tab2=User
tab3=EnterpriseUser
saveButton=Save
-tab4=Group
+tab4=ExtensionUser
+tab5=Group
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.html
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.html
new file mode 100644
index 0000000000..1849e7e9ae
--- /dev/null
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.html
@@ -0,0 +1,105 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:wicket="http://wicket.apache.org">
+ <wicket:panel>
+ <div class="table-responsive no-padding">
+ <table id="mappings"
+ class="table table-hover"
+ style="font-size: 1em;margin-top:2px;"
+ wicket:id="mappingContainer">
+ <tbody>
+ <tr>
+ <th>
+ <wicket:message key="intAttrName"/>
+ <span id="intAttrNameInfo" wicket:id="intAttrNameInfo"></span>
+ </th>
+ <th>
+ <wicket:message key="extAttrName"/>
+ </th>
+ <th wicket:id="mandatoryHeader">
+ <wicket:message key="mandatoryCondition"/>
+ </th>
+ <th>
+ <wicket:message key="caseExact"/>
+ <span id="caseExactLabel" wicket:id="caseExactLabel"></span>
+ </th>
+ <th>
+ <wicket:message key="mutability"/>
+ <span id="mutabilityLabel" wicket:id="mutabilityLabel"></span>
+ </th>
+ <th>
+ <wicket:message key="returned"/>
+ <span id="returnedLabel" wicket:id="returnedLabel"></span>
+ </th>
+ <th>
+ <wicket:message key="uniqueness"/>
+ <span id="uniquenessLabel" wicket:id="uniquenessLabel"></span>
+ </th>
+ <th>
+ <wicket:message key="multiValued"/>
+ <span id="multiValuedLabel" wicket:id="multiValuedLabel"></span>
+ </th>
+ <th></th>
+ </tr>
+ <tr wicket:id="mappings">
+ <td>
+ <span wicket:id="intAttrName">[intAttrNames]</span>
+ </td>
+ <td>
+ <span wicket:id="extAttrName">[extAttrName]</span>
+ </td>
+ <td>
+ <span wicket:id="mandatoryCondition">[mandatoryCondition]</span>
+ </td>
+ <td>
+ <span wicket:id="caseExact">[caseExact]</span>
+ </td>
+ <td>
+ <span wicket:id="mutability">[mutability]</span>
+ </td>
+ <td>
+ <span wicket:id="returned">[returned]</span>
+ </td>
+ <td>
+ <span wicket:id="uniqueness">[uniqueness]</span>
+ </td>
+ <td>
+ <span wicket:id="multiValued">[multiValued]</span>
+ </td>
+ <td>
+ <div id="inline-actions">
+ <span wicket:id="toRemove"/>
+ </div>
+ </td>
+ </tr>
+ </tbody>
+
+ <tfoot>
+ <tr>
+ <td colspan="10" style="padding: 5px; text-align: right">
+ <button type="submit" class="btn btn-success btn-circle btn-lg"
wicket:id="addMappingBtn">
+ <i class="fa fa-plus"></i>
+ </button>
+ </td>
+ </tr>
+ </tfoot>
+ </table>
+ </div>
+ </wicket:panel>
+</html>
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.properties
similarity index 79%
copy from
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
copy to
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.properties
index 10d4781a8f..75bd842a2f 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel.properties
@@ -14,9 +14,12 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-tab1=General
-tab2=User
-tab3=EnterpriseUser
-
-saveButton=Save
-tab4=Group
+extAttrName=Syncope attribute
+mandatoryCondition=Required
+delete=Delete
+intAttrName=SCIM attribute
+caseExact=Case Exact
+mutability=Mutability
+returned=Returned
+uniqueness=Uniqueness
+multiValued=MultiValued
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_fr.properties
similarity index 79%
copy from
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
copy to
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_fr.properties
index 10d4781a8f..75bd842a2f 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_fr.properties
@@ -14,9 +14,12 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-tab1=General
-tab2=User
-tab3=EnterpriseUser
-
-saveButton=Save
-tab4=Group
+extAttrName=Syncope attribute
+mandatoryCondition=Required
+delete=Delete
+intAttrName=SCIM attribute
+caseExact=Case Exact
+mutability=Mutability
+returned=Returned
+uniqueness=Uniqueness
+multiValued=MultiValued
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_it.properties
similarity index 78%
copy from
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
copy to
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_it.properties
index 10d4781a8f..bbbccc6974 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_it.properties
@@ -14,9 +14,12 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-tab1=General
-tab2=User
-tab3=EnterpriseUser
-
-saveButton=Save
-tab4=Group
+extAttrName=Attributo Syncope
+mandatoryCondition=Obbligatorio
+delete=Rimuovi
+intAttrName=Attributo SCIM
+caseExact=Case Exact
+mutability=Mutability
+returned=Returned
+uniqueness=Unicit\u00E0
+multiValued=Multi Valore
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_ja.properties
similarity index 79%
copy from
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
copy to
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_ja.properties
index 10d4781a8f..75bd842a2f 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_ja.properties
@@ -14,9 +14,12 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-tab1=General
-tab2=User
-tab3=EnterpriseUser
-
-saveButton=Save
-tab4=Group
+extAttrName=Syncope attribute
+mandatoryCondition=Required
+delete=Delete
+intAttrName=SCIM attribute
+caseExact=Case Exact
+mutability=Mutability
+returned=Returned
+uniqueness=Uniqueness
+multiValued=MultiValued
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_pt_BR.properties
similarity index 79%
copy from
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
copy to
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_pt_BR.properties
index 10d4781a8f..75bd842a2f 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_pt_BR.properties
@@ -14,9 +14,12 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-tab1=General
-tab2=User
-tab3=EnterpriseUser
-
-saveButton=Save
-tab4=Group
+extAttrName=Syncope attribute
+mandatoryCondition=Required
+delete=Delete
+intAttrName=SCIM attribute
+caseExact=Case Exact
+mutability=Mutability
+returned=Returned
+uniqueness=Uniqueness
+multiValued=MultiValued
diff --git
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_ru.properties
similarity index 79%
copy from
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
copy to
ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_ru.properties
index 10d4781a8f..75bd842a2f 100644
---
a/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfPanel_pt_BR.properties
+++
b/ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/mapping/SCIMExtentionMappingPanel_ru.properties
@@ -14,9 +14,12 @@
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
-tab1=General
-tab2=User
-tab3=EnterpriseUser
-
-saveButton=Save
-tab4=Group
+extAttrName=Syncope attribute
+mandatoryCondition=Required
+delete=Delete
+intAttrName=SCIM attribute
+caseExact=Case Exact
+mutability=Mutability
+returned=Returned
+uniqueness=Uniqueness
+multiValued=MultiValued
diff --git
a/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMConf.java
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMConf.java
index 98fe8f2b4b..468b5d289e 100644
---
a/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMConf.java
+++
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMConf.java
@@ -32,6 +32,8 @@ public class SCIMConf implements Serializable {
private SCIMEnterpriseUserConf enterpriseUserConf;
+ private SCIMExtensionUserConf extensionUserConf;
+
private SCIMGroupConf groupConf;
public SCIMGeneralConf getGeneralConf() {
@@ -58,6 +60,14 @@ public class SCIMConf implements Serializable {
this.enterpriseUserConf = enterpriseUserConf;
}
+ public SCIMExtensionUserConf getExtensionUserConf() {
+ return extensionUserConf;
+ }
+
+ public void setExtensionUserConf(final SCIMExtensionUserConf
extensionUserConf) {
+ this.extensionUserConf = extensionUserConf;
+ }
+
public SCIMGroupConf getGroupConf() {
return groupConf;
}
diff --git
a/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionUserConf.java
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionUserConf.java
new file mode 100644
index 0000000000..cae6330caf
--- /dev/null
+++
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionUserConf.java
@@ -0,0 +1,72 @@
+/*
+ * 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.common.lib.scim;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+public class SCIMExtensionUserConf implements Serializable {
+
+ private static final long serialVersionUID = -9091596628402547645L;
+
+ private String name;
+
+ private String description;
+
+ private final List<SCIMItem> attributes = new ArrayList<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ public List<SCIMItem> getAttributes() {
+ return attributes;
+ }
+
+ public boolean add(final SCIMItem item) {
+ return Optional.ofNullable(item).
+ filter(itemTO -> attributes.contains(itemTO) ||
attributes.add(itemTO)).
+ isPresent();
+ }
+
+ @JsonIgnore
+ public Map<String, String> asMap() {
+ Map<String, String> map = new HashMap<>();
+ attributes.forEach(item -> map.put(item.getIntAttrName(),
item.getExtAttrName()));
+ return Collections.unmodifiableMap(map);
+ }
+}
diff --git
a/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMItem.java
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMItem.java
new file mode 100644
index 0000000000..3395d1b81b
--- /dev/null
+++
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMItem.java
@@ -0,0 +1,114 @@
+/*
+ * 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.common.lib.scim;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.to.Item;
+
+public class SCIMItem extends Item {
+
+ private boolean caseExact = false;
+
+ private boolean mutability = false;
+
+ private SCIMReturned returned = SCIMReturned.DEFAULT;
+
+ private boolean uniqueness = false;
+
+ private boolean multiValued = false;
+
+ public boolean isCaseExact() {
+ return caseExact;
+ }
+
+ public void setCaseExact(final boolean caseExact) {
+ this.caseExact = caseExact;
+ }
+
+ public boolean isMutability() {
+ return mutability;
+ }
+
+ public void setMutability(final boolean mutability) {
+ this.mutability = mutability;
+ }
+
+ public SCIMReturned getReturned() {
+ return returned;
+ }
+
+ public void setReturned(final SCIMReturned returned) {
+ this.returned = returned;
+ }
+
+ public boolean isUniqueness() {
+ return uniqueness;
+ }
+
+ public void setUniqueness(final boolean uniqueness) {
+ this.uniqueness = uniqueness;
+ }
+
+ public boolean isMultiValued() {
+ return multiValued;
+ }
+
+ public void setMultiValued(final boolean multiValued) {
+ this.multiValued = multiValued;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SCIMItem other = (SCIMItem) obj;
+ return new EqualsBuilder().
+ append(intAttrName, other.intAttrName).
+ append(extAttrName, other.extAttrName).
+ append(mandatoryCondition, other.mandatoryCondition).
+ append(caseExact, other.caseExact).
+ append(mutability, other.mutability).
+ append(returned, other.returned).
+ append(uniqueness, other.uniqueness).
+ append(multiValued, other.multiValued).
+ build();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().
+ append(intAttrName).
+ append(extAttrName).
+ append(mandatoryCondition).
+ append(caseExact).
+ append(mutability).
+ append(returned).
+ append(uniqueness).
+ append(multiValued).
+ build();
+ }
+}
diff --git
a/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMReturned.java
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMReturned.java
new file mode 100644
index 0000000000..d5ba68f2b3
--- /dev/null
+++
b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMReturned.java
@@ -0,0 +1,37 @@
+/*
+ * 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.common.lib.scim;
+
+public enum SCIMReturned {
+
+ ALWAYS("always"),
+ NEVER("never"),
+ DEFAULT("default"),
+ REQUEST("request");
+
+ private final String returned;
+
+ SCIMReturned(final String returned) {
+ this.returned = returned;
+ }
+
+ public String getReturned() {
+ return returned;
+ }
+}
diff --git
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
index f39f839712..354a7ecbfc 100644
---
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
+++
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMDataBinder.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -63,6 +64,7 @@ import org.apache.syncope.ext.scimv2.api.data.Member;
import org.apache.syncope.ext.scimv2.api.data.Meta;
import org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue;
import org.apache.syncope.ext.scimv2.api.data.SCIMEnterpriseInfo;
+import org.apache.syncope.ext.scimv2.api.data.SCIMExtensionInfo;
import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOperation;
import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
@@ -83,11 +85,6 @@ public class SCIMDataBinder {
protected static final Logger LOG =
LoggerFactory.getLogger(SCIMDataBinder.class);
- protected static final List<String> USER_SCHEMAS =
List.of(Resource.User.schema());
-
- protected static final List<String> ENTERPRISE_USER_SCHEMAS =
- List.of(Resource.User.schema(), Resource.EnterpriseUser.schema());
-
protected static final List<String> GROUP_SCHEMAS =
List.of(Resource.Group.schema());
/**
@@ -195,6 +192,9 @@ public class SCIMDataBinder {
if (conf.getEnterpriseUserConf() != null) {
schemas.add(Resource.EnterpriseUser.schema());
}
+ if (conf.getExtensionUserConf() != null) {
+ schemas.add(Resource.ExtensionUser.schema());
+ }
SCIMUser user = new SCIMUser(
userTO.getKey(),
@@ -444,6 +444,13 @@ public class SCIMDataBinder {
}
}
+ if (conf.getExtensionUserConf() != null) {
+ SCIMExtensionInfo extensionInfo = new SCIMExtensionInfo();
+ conf.getExtensionUserConf().asMap().forEach((scimAttr,
syncopeAttr) -> extensionInfo.getAttributes().put(
+ scimAttr, attrs.get(syncopeAttr).getValues().get(0)));
+ user.setExtensionInfo(extensionInfo);
+ }
+
if (output(attributes, excludedAttributes, "groups")) {
userTO.getMemberships().forEach(membership ->
user.getGroups().add(new Group(
membership.getGroupKey(),
@@ -499,9 +506,19 @@ public class SCIMDataBinder {
}
public UserTO toUserTO(final SCIMUser user, final boolean checkSchemas) {
+ SCIMConf conf = confManager.get();
+
+ Set<String> expectedSchemas = new HashSet<>();
+ expectedSchemas.add(Resource.User.schema());
+ if (conf.getEnterpriseUserConf() != null) {
+ expectedSchemas.add(Resource.EnterpriseUser.schema());
+ }
+ if (conf.getExtensionUserConf() != null) {
+ expectedSchemas.add(Resource.ExtensionUser.schema());
+ }
if (checkSchemas
- && !USER_SCHEMAS.equals(user.getSchemas())
- && !ENTERPRISE_USER_SCHEMAS.equals(user.getSchemas())) {
+ && (!user.getSchemas().containsAll(expectedSchemas)
+ || !expectedSchemas.containsAll(user.getSchemas()))) {
throw new BadRequestException(ErrorType.invalidValue);
}
@@ -512,8 +529,6 @@ public class SCIMDataBinder {
userTO.setPassword(user.getPassword());
userTO.setUsername(user.getUserName());
- SCIMConf conf = confManager.get();
-
if (conf.getUserConf() != null) {
setAttribute(
userTO,
@@ -677,6 +692,11 @@ public class SCIMDataBinder {
map(SCIMUserManager::getValue).orElse(null));
}
+ if (conf.getExtensionUserConf() != null && user.getExtensionInfo() !=
null) {
+ conf.getExtensionUserConf().asMap().forEach((scimAttr,
syncopeAttr) -> setAttribute(
+ userTO, syncopeAttr,
user.getExtensionInfo().getAttributes().get(scimAttr)));
+ }
+
userTO.getMemberships().addAll(user.getGroups().stream().
map(group -> new
MembershipTO.Builder(group.getValue()).build()).
toList());
@@ -799,20 +819,18 @@ public class SCIMDataBinder {
}
switch (op.getPath().getAttribute()) {
- case "externalId" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getExternalId(), op);
+ case "externalId" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getExternalId(), op);
case "userName" -> {
if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
- userUR.setUsername(new StringReplacePatchItem.Builder().
- value(op.getValue().get(0).toString()).build());
+ userUR.setUsername(
+ new
StringReplacePatchItem.Builder().value(op.getValue().get(0).toString()).build());
}
}
case "password" -> {
if (op.getOp() != PatchOp.remove &&
!CollectionUtils.isEmpty(op.getValue())) {
- userUR.setPassword(new PasswordPatch.Builder().
- value(op.getValue().get(0).toString()).build());
+ userUR.setPassword(new
PasswordPatch.Builder().value(op.getValue().get(0).toString()).build());
}
}
@@ -824,11 +842,9 @@ public class SCIMDataBinder {
op.setValue(List.of(BooleanUtils.toBoolean(a)));
}
- statusR = new StatusR.Builder(
- before.getKey(),
- (boolean) op.getValue().get(0) ?
StatusRType.REACTIVATE : StatusRType.SUSPEND).
- resources(resources).
- build();
+ statusR = new StatusR.Builder(before.getKey(),
+ (boolean) op.getValue().get(0) ?
StatusRType.REACTIVATE : StatusRType.SUSPEND).resources(
+ resources).build();
}
}
@@ -855,37 +871,27 @@ public class SCIMDataBinder {
}
}
- case "displayName" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getDisplayName(), op);
+ case "displayName" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getDisplayName(), op);
- case "nickName" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getNickName(), op);
+ case "nickName" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getNickName(), op);
- case "profileUrl" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getProfileUrl(), op);
+ case "profileUrl" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getProfileUrl(), op);
- case "title" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTitle(), op);
+ case "title" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTitle(), op);
- case "userType" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getUserType(), op);
+ case "userType" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getUserType(), op);
case "preferredLanguage" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPreferredLanguage(), op);
+ setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPreferredLanguage(), op);
- case "locale" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getLocale(), op);
+ case "locale" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getLocale(), op);
- case "timezone" ->
- setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTimezone(), op);
+ case "timezone" -> setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getTimezone(), op);
case "emails" -> {
if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getEmails(),
- ((SCIMUser) op.getValue().get(0)).getEmails(),
- op.getOp());
+ setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getEmails(),
+ ((SCIMUser) op.getValue().get(0)).getEmails(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getEmails(), op);
}
@@ -893,11 +899,8 @@ public class SCIMDataBinder {
case "phoneNumbers" -> {
if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getPhoneNumbers(),
- ((SCIMUser)
op.getValue().get(0)).getPhoneNumbers(),
- op.getOp());
+ setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(),
+ ((SCIMUser)
op.getValue().get(0)).getPhoneNumbers(), op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhoneNumbers(), op);
}
@@ -905,11 +908,8 @@ public class SCIMDataBinder {
case "ims" -> {
if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getIms(),
- ((SCIMUser) op.getValue().get(0)).getIms(),
- op.getOp());
+ setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getIms(),
+ ((SCIMUser) op.getValue().get(0)).getIms(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getIms(), op);
}
@@ -917,11 +917,8 @@ public class SCIMDataBinder {
case "photos" -> {
if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
- setAttribute(
- userUR.getPlainAttrs(),
- conf.getUserConf().getPhotos(),
- ((SCIMUser) op.getValue().get(0)).getPhotos(),
- op.getOp());
+ setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(),
+ ((SCIMUser) op.getValue().get(0)).getPhotos(),
op.getOp());
} else if (op.getPath().getFilter() != null) {
setAttribute(userUR.getPlainAttrs(),
conf.getUserConf().getPhotos(), op);
}
@@ -930,44 +927,47 @@ public class SCIMDataBinder {
case "addresses" -> {
if (!CollectionUtils.isEmpty(op.getValue()) &&
op.getValue().get(0) instanceof SCIMUser) {
SCIMUser after = (SCIMUser) op.getValue().get(0);
- after.getAddresses().stream().filter(address ->
address.getType() != null).
- forEach(address ->
conf.getUserConf().getAddresses().stream().
- filter(object ->
address.getType().equals(object.getType().name())).findFirst().
- ifPresent(addressConf ->
setAttribute(userUR.getPlainAttrs(), addressConf, op)));
+ after.getAddresses().stream().filter(address ->
address.getType() != null).forEach(
+ address ->
conf.getUserConf().getAddresses().stream()
+ .filter(object ->
address.getType().equals(object.getType().name())).findFirst()
+ .ifPresent(addressConf ->
setAttribute(userUR.getPlainAttrs(), addressConf, op)));
} else if (op.getPath().getFilter() != null) {
- conf.getUserConf().getAddresses().stream().
- filter(addressConf ->
BooleanUtils.toBoolean(JexlUtils.evaluateExpr(
- filter2JexlExpression(op.getPath().getFilter()),
- new MapContext(Map.of("type",
addressConf.getType().name()))).toString())).findFirst().
- ifPresent(addressConf ->
setAttribute(userUR.getPlainAttrs(), addressConf, op));
+
conf.getUserConf().getAddresses().stream().filter(addressConf ->
BooleanUtils.toBoolean(
+
JexlUtils.evaluateExpr(filter2JexlExpression(op.getPath().getFilter()),
+ new MapContext(Map.of("type",
addressConf.getType().name()))).toString()))
+ .findFirst()
+ .ifPresent(addressConf ->
setAttribute(userUR.getPlainAttrs(), addressConf, op));
}
}
- case "employeeNumber" ->
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getEmployeeNumber).orElse(null), op);
+ case "employeeNumber" -> setAttribute(userUR.getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getEmployeeNumber)
+ .orElse(null), op);
- case "costCenter" ->
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getCostCenter).orElse(null), op);
+ case "costCenter" -> setAttribute(userUR.getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getCostCenter)
+ .orElse(null), op);
- case "organization" ->
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getOrganization).orElse(null), op);
+ case "organization" -> setAttribute(userUR.getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getOrganization)
+ .orElse(null), op);
- case "division" ->
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
- map(SCIMEnterpriseUserConf::getDivision).orElse(null),
op);
+ case "division" -> setAttribute(userUR.getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getDivision)
+ .orElse(null), op);
- case "department" ->
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getDepartment).orElse(null), op);
+ case "department" -> setAttribute(userUR.getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getDepartment)
+ .orElse(null), op);
- case "manager" ->
- setAttribute(userUR.getPlainAttrs(),
Optional.ofNullable(conf.getEnterpriseUserConf()).
-
map(SCIMEnterpriseUserConf::getManager).map(SCIMManagerConf::getKey).orElse(null),
op);
+ case "manager" -> setAttribute(userUR.getPlainAttrs(),
+
Optional.ofNullable(conf.getEnterpriseUserConf()).map(SCIMEnterpriseUserConf::getManager)
+ .map(SCIMManagerConf::getKey).orElse(null), op);
default -> {
+ Optional.ofNullable(conf.getExtensionUserConf())
+ .flatMap(schema ->
Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute())))
+ .ifPresent(schema ->
setAttribute(userUR.getPlainAttrs(), schema, op));
}
}
diff --git
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogic.java
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogic.java
index 130c9ec123..9c9d8c558a 100644
---
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogic.java
+++
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/SCIMLogic.java
@@ -21,6 +21,7 @@ package org.apache.syncope.core.logic;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.lang.reflect.Method;
@@ -49,15 +50,17 @@ public class SCIMLogic extends AbstractLogic<EntityTO> {
protected static final Object MONITOR = new Object();
+ protected static final JsonMapper MAPPER =
JsonMapper.builder().findAndAddModules().build();
+
protected static ServiceProviderConfig SERVICE_PROVIDER_CONFIG;
protected static ResourceType USER;
protected static ResourceType GROUP;
- protected static String SCHEMAS;
+ protected String schemas;
- protected static final Map<String, String> SCHEMA_MAP = new HashMap<>();
+ protected final Map<String, String> schemaMap = new HashMap<>();
protected final SCIMConfManager confManager;
@@ -67,17 +70,41 @@ public class SCIMLogic extends AbstractLogic<EntityTO> {
protected void init() {
try {
- JsonMapper mapper =
JsonMapper.builder().findAndAddModules().build();
- JsonNode tree =
mapper.readTree(SCIMLogic.class.getResourceAsStream('/' + SCHEMAS_JSON));
+ JsonNode tree =
MAPPER.readTree(SCIMLogic.class.getResourceAsStream('/' + SCHEMAS_JSON));
if (!tree.isArray()) {
throw new IOException("JSON node is not a tree");
}
ArrayNode schemaArray = (ArrayNode) tree;
- SCHEMAS =
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(tree);
+ SCIMConf conf = confManager.get();
+ if (conf.getExtensionUserConf() != null) {
+ ObjectNode extensionObject = MAPPER.createObjectNode();
+ extensionObject.put("id", Resource.ExtensionUser.schema());
+ extensionObject.put("name",
conf.getExtensionUserConf().getName());
+ extensionObject.put("description",
conf.getExtensionUserConf().getDescription());
+ ArrayNode attributes = MAPPER.createArrayNode();
+ conf.getExtensionUserConf().getAttributes().forEach(scimItem
-> {
+ ObjectNode attribute = MAPPER.createObjectNode();
+ attribute.put("name", scimItem.getIntAttrName());
+ attribute.put("type", "string");
+ attribute.put("multiValued", scimItem.isMultiValued());
+ attribute.put("required",
scimItem.getMandatoryCondition());
+ attribute.put("caseExact", scimItem.isCaseExact());
+ attribute.put("mutability", scimItem.isMutability());
+ attribute.put("returned",
scimItem.getReturned().getReturned());
+ attribute.put("uniqueness", scimItem.isUniqueness());
+ attributes.add(attribute);
+ });
+ extensionObject.putIfAbsent("attributes", attributes);
+ extensionObject.putIfAbsent("meta",
MAPPER.readTree("{\"resourceType\": \"Schema\","
+ + "\"location\":
\"/v2/Schemas/urn:ietf:params:scim:schemas:extension:syncope:2.0:User\"}"));
+ schemaArray.add(extensionObject);
+ }
+ schemas =
MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(tree);
+ schemaMap.clear();
for (JsonNode schema : schemaArray) {
- SCHEMA_MAP.put(schema.get("id").asText(),
mapper.writeValueAsString(schema));
+ schemaMap.put(schema.get("id").asText(),
MAPPER.writeValueAsString(schema));
}
} catch (IOException e) {
LOG.error("Could not parse the default schema definitions", e);
@@ -86,11 +113,9 @@ public class SCIMLogic extends AbstractLogic<EntityTO> {
@PreAuthorize("isAuthenticated()")
public ServiceProviderConfig serviceProviderConfig(final UriBuilder
uriBuilder) {
- synchronized (MONITOR) {
- if (SCHEMAS == null) {
- init();
- }
+ init();
+ synchronized (MONITOR) {
if (SERVICE_PROVIDER_CONFIG == null) {
SCIMConf conf = confManager.get();
@@ -164,24 +189,16 @@ public class SCIMLogic extends AbstractLogic<EntityTO> {
@PreAuthorize("isAuthenticated()")
public String schemas() {
- synchronized (MONITOR) {
- if (SCHEMAS == null) {
- init();
- }
- }
+ init();
- return SCHEMAS;
+ return schemas;
}
@PreAuthorize("isAuthenticated()")
public String schema(final String schema) {
- synchronized (MONITOR) {
- if (SCHEMAS == null) {
- init();
- }
- }
+ init();
- String found = SCHEMA_MAP.get(schema);
+ String found = schemaMap.get(schema);
if (found == null) {
throw new NotFoundException("Schema " + schema + " not found");
}
diff --git
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMConfManager.java
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMConfManager.java
index c964c9e80a..8ee044a08b 100644
---
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMConfManager.java
+++
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SCIMConfManager.java
@@ -22,11 +22,13 @@ import jakarta.ws.rs.core.MediaType;
import java.time.OffsetDateTime;
import java.util.Base64;
import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
+import org.apache.syncope.common.lib.SyncopeClientException;
import org.apache.syncope.common.lib.scim.SCIMConf;
import org.apache.syncope.common.lib.scim.SCIMGeneralConf;
import org.apache.syncope.common.lib.scim.types.SCIMEntitlement;
import org.apache.syncope.common.lib.to.PlainSchemaTO;
import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.SchemaType;
import org.apache.syncope.core.logic.SchemaLogic;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
@@ -82,6 +84,45 @@ public class SCIMConfManager {
conf.setGeneralConf(new SCIMGeneralConf());
conf.getGeneralConf().setLastChangeDate(OffsetDateTime.now());
+ if (conf.getExtensionUserConf() != null) {
+ conf.getExtensionUserConf().getAttributes().forEach(scimItem -> {
+ try {
+ PlainSchemaTO schema = schemaLogic.read(SchemaType.PLAIN,
scimItem.getExtAttrName());
+ SyncopeClientException invalidMapping =
+
SyncopeClientException.build(ClientExceptionType.InvalidMapping);
+ if
(!scimItem.getMandatoryCondition().equals(schema.getMandatoryCondition())) {
+ invalidMapping.getElements().add('\'' +
scimItem.getIntAttrName()
+ + "' should " +
(Boolean.parseBoolean(schema.getMandatoryCondition()) ? "" : "not")
+ + " be required");
+ }
+ if (scimItem.isMultiValued() != schema.isMultivalue()) {
+ invalidMapping.getElements().add('\'' +
scimItem.getIntAttrName()
+ + "' should " + (schema.isMultivalue() ? "" :
"not") + " be multi-value");
+ }
+ if (scimItem.isMutability() != schema.isReadonly()) {
+ invalidMapping.getElements().add('\'' +
scimItem.getIntAttrName()
+ + "' should " + (schema.isReadonly() ? "" :
"not") + " be readonly");
+ }
+ if (scimItem.isUniqueness() !=
schema.isUniqueConstraint()) {
+ invalidMapping.getElements().add('\'' +
scimItem.getIntAttrName()
+ + "' should " + (schema.isUniqueConstraint() ?
"" : "not") + " be unique");
+ }
+ if (!invalidMapping.getElements().isEmpty()) {
+ throw invalidMapping;
+ }
+ } catch (NotFoundException e) {
+ PlainSchemaTO schema =
schemaLogic.read(SchemaType.VIRTUAL, scimItem.getExtAttrName());
+ if (scimItem.isMutability() != schema.isReadonly()) {
+ SyncopeClientException invalidMapping =
+
SyncopeClientException.build(ClientExceptionType.InvalidMapping);
+ invalidMapping.getElements().add('\'' +
scimItem.getIntAttrName()
+ + "' should " + (schema.isReadonly() ? "" :
"not") + " be readonly");
+ throw invalidMapping;
+ }
+ }
+ });
+ }
+
confParamOps.set(AuthContextUtils.getDomain(),
SCIMConf.KEY,
Base64.getEncoder().encodeToString(POJOHelper.serialize(conf).getBytes()));
}
diff --git
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
index 44c3b0a1e1..ad8cc743fc 100644
---
a/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
+++
b/ext/scimv2/logic/src/main/java/org/apache/syncope/core/logic/scim/SearchCondVisitor.java
@@ -123,6 +123,15 @@ public class SearchCondVisitor extends
SCIMFilterBaseVisitor<SearchCond> {
attrCond.setSchema(conf.getEnterpriseUserConf().getManager().getKey());
}
}
+
+ if (conf.getExtensionUserConf() != null) {
+ for (Map.Entry<String, String> entry :
conf.getExtensionUserConf().asMap().entrySet()) {
+ if (schemaEquals(Resource.ExtensionUser,
entry.getKey(), schema)) {
+ attrCond = new AttrCond();
+ attrCond.setSchema(entry.getValue());
+ }
+ }
+ }
break;
case Group:
diff --git
a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMExtensionInfo.java
b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMExtensionInfo.java
new file mode 100644
index 0000000000..a23ee4ef58
--- /dev/null
+++
b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMExtensionInfo.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.ext.scimv2.api.data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class SCIMExtensionInfo extends SCIMBean {
+
+ private static final long serialVersionUID = 1310985252565467391L;
+
+ private final Map<String, String> attributes = new HashMap<>();
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+}
diff --git
a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMUser.java
b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMUser.java
index 08120534e9..ccd34ed98c 100644
---
a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMUser.java
+++
b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMUser.java
@@ -31,7 +31,7 @@ import java.util.Optional;
"name", "displayName", "nickName", "profileUrl", "title", "userType",
"preferredLanguage", "locale", "timezone",
"emails", "phoneNumbers", "ims", "photos", "addresses", "x509Certificates",
"groups", "entitlements", "roles",
- "enterpriseInfo",
+ "enterpriseInfo", "extensionInfo",
"meta" })
public class SCIMUser extends SCIMResource {
@@ -80,6 +80,9 @@ public class SCIMUser extends SCIMResource {
@JsonProperty("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User")
private SCIMEnterpriseInfo enterpriseInfo;
+ @JsonProperty("urn:ietf:params:scim:schemas:extension:syncope:2.0:User")
+ private SCIMExtensionInfo extensionInfo;
+
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public SCIMUser(
@JsonProperty("id") final String id,
@@ -221,4 +224,12 @@ public class SCIMUser extends SCIMResource {
public void setEnterpriseInfo(final SCIMEnterpriseInfo enterpriseInfo) {
this.enterpriseInfo = enterpriseInfo;
}
+
+ public SCIMExtensionInfo getExtensionInfo() {
+ return extensionInfo;
+ }
+
+ public void setExtensionInfo(final SCIMExtensionInfo extensionInfo) {
+ this.extensionInfo = extensionInfo;
+ }
}
diff --git
a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/type/Resource.java
b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/type/Resource.java
index b26382383f..0cbd6d2828 100644
---
a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/type/Resource.java
+++
b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/type/Resource.java
@@ -25,6 +25,7 @@ public enum Resource {
Schema("urn:ietf:params:scim:schemas:core:2.0:Schema"),
User("urn:ietf:params:scim:schemas:core:2.0:User"),
EnterpriseUser("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"),
+ ExtensionUser("urn:ietf:params:scim:schemas:extension:syncope:2.0:User"),
Group("urn:ietf:params:scim:schemas:core:2.0:Group"),
SearchRequest("urn:ietf:params:scim:api:messages:2.0:SearchRequest"),
ListResponse("urn:ietf:params:scim:api:messages:2.0:ListResponse"),
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
index ebbb8530cc..5bfc635687 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SCIMITCase.java
@@ -24,6 +24,7 @@ import static
org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -46,7 +47,9 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.syncope.common.lib.scim.SCIMComplexConf;
import org.apache.syncope.common.lib.scim.SCIMConf;
+import org.apache.syncope.common.lib.scim.SCIMExtensionUserConf;
import org.apache.syncope.common.lib.scim.SCIMGroupConf;
+import org.apache.syncope.common.lib.scim.SCIMItem;
import org.apache.syncope.common.lib.scim.SCIMUserConf;
import org.apache.syncope.common.lib.scim.SCIMUserNameConf;
import org.apache.syncope.common.lib.scim.types.EmailCanonicalType;
@@ -59,6 +62,7 @@ import org.apache.syncope.ext.scimv2.api.data.Member;
import org.apache.syncope.ext.scimv2.api.data.ResourceType;
import org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue;
import org.apache.syncope.ext.scimv2.api.data.SCIMError;
+import org.apache.syncope.ext.scimv2.api.data.SCIMExtensionInfo;
import org.apache.syncope.ext.scimv2.api.data.SCIMGroup;
import org.apache.syncope.ext.scimv2.api.data.SCIMSearchRequest;
import org.apache.syncope.ext.scimv2.api.data.SCIMUser;
@@ -106,8 +110,8 @@ public class SCIMITCase extends AbstractITCase {
CONF.getUserConf().getEmails().add(email);
}
- private static SCIMUser getSampleUser(final String username) {
- SCIMUser user = new SCIMUser(null, List.of(Resource.User.schema()),
null, username, true);
+ private static SCIMUser getSampleUser(final String username, final
List<String> schemas) {
+ SCIMUser user = new SCIMUser(null, schemas, null, username, true);
user.setPassword("password123");
SCIMUserName name = new SCIMUserName();
@@ -195,6 +199,16 @@ public class SCIMITCase extends AbstractITCase {
@Test
public void schemas() {
+ SCIMExtensionUserConf extensionUserConf = new SCIMExtensionUserConf();
+ extensionUserConf.setName("syncope");
+ extensionUserConf.setDescription("syncope user");
+ SCIMItem scimItem = new SCIMItem();
+ scimItem.setIntAttrName("gender");
+ scimItem.setExtAttrName("gender");
+ extensionUserConf.add(scimItem);
+ CONF.setExtensionUserConf(extensionUserConf);
+ SCIM_CONF_SERVICE.set(CONF);
+
Response response = webClient().path("Schemas").get();
assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
assertEquals(
@@ -203,7 +217,7 @@ public class SCIMITCase extends AbstractITCase {
ArrayNode schemas = response.readEntity(ArrayNode.class);
assertNotNull(schemas);
- assertEquals(3, schemas.size());
+ assertEquals(4, schemas.size());
response = webClient().path("Schemas").path("none").get();
assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
response.getStatus());
@@ -214,6 +228,16 @@ public class SCIMITCase extends AbstractITCase {
ObjectNode enterpriseUser = response.readEntity(ObjectNode.class);
assertNotNull(enterpriseUser);
assertEquals(Resource.EnterpriseUser.schema(),
enterpriseUser.get("id").textValue());
+
+ response =
webClient().path("Schemas").path(Resource.ExtensionUser.schema()).get();
+ assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
+
+ ObjectNode extensionUser = response.readEntity(ObjectNode.class);
+ assertNotNull(extensionUser);
+ assertEquals(Resource.ExtensionUser.schema(),
extensionUser.get("id").textValue());
+
+ CONF.setExtensionUserConf(null);
+ SCIM_CONF_SERVICE.set(CONF);
}
@Test
@@ -270,6 +294,25 @@ public class SCIMITCase extends AbstractITCase {
assertEquals("Rossini, Gioacchino", user.getDisplayName());
}
+ @Test
+ void invalidConf() {
+ SCIMExtensionUserConf extensionUserConf = new SCIMExtensionUserConf();
+ extensionUserConf.setName("syncope");
+ extensionUserConf.setDescription("syncope user");
+ SCIMItem scimItem = new SCIMItem();
+ scimItem.setIntAttrName("gender");
+ scimItem.setExtAttrName("gender");
+ scimItem.setMultiValued(true);
+ extensionUserConf.add(scimItem);
+ CONF.setExtensionUserConf(extensionUserConf);
+ try {
+ SCIM_CONF_SERVICE.set(CONF);
+ fail();
+ } catch (Exception ignored) {
+ CONF.setExtensionUserConf(null);
+ }
+ }
+
@Test
public void list() throws IOException {
Response response = webClient().path("Groups").query("count",
1100000).get();
@@ -366,7 +409,7 @@ public class SCIMITCase extends AbstractITCase {
public void createUser() throws JsonProcessingException {
SCIM_CONF_SERVICE.set(CONF);
- SCIMUser user = getSampleUser(UUID.randomUUID().toString());
+ SCIMUser user = getSampleUser(UUID.randomUUID().toString(),
List.of(Resource.User.schema()));
user.getRoles().add(new Value("User reviewer"));
user.getGroups().add(new Group("37d15e4c-cdc1-460b-a591-8505c8133806",
null, null, null));
@@ -390,11 +433,57 @@ public class SCIMITCase extends AbstractITCase {
assertEquals(user.getGroups().get(0).getValue(),
userTO.getMemberships().get(0).getGroupKey());
}
+ @Test
+ void crudExtensionUser() {
+ SCIMExtensionUserConf extensionUserConf = new SCIMExtensionUserConf();
+ extensionUserConf.setName("syncope");
+ extensionUserConf.setDescription("syncope user");
+ SCIMItem scimItem = new SCIMItem();
+ scimItem.setIntAttrName("gender");
+ scimItem.setExtAttrName("gender");
+ extensionUserConf.add(scimItem);
+ CONF.setExtensionUserConf(extensionUserConf);
+ SCIM_CONF_SERVICE.set(CONF);
+
+ SCIMUser user = getSampleUser(
+ UUID.randomUUID().toString(), List.of(Resource.User.schema(),
Resource.ExtensionUser.schema()));
+ SCIMExtensionInfo scimExtensionInfo = new SCIMExtensionInfo();
+ scimExtensionInfo.getAttributes().put("gender", "M");
+ user.setExtensionInfo(scimExtensionInfo);
+
+ Response response = webClient().path("Users").post(user);
+ assertEquals(Response.Status.CREATED.getStatusCode(),
response.getStatus());
+
+ user = response.readEntity(SCIMUser.class);
+ assertNotNull(user.getId());
+
assertTrue(response.getLocation().toASCIIString().endsWith(user.getId()));
+
+ UserTO userTO = USER_SERVICE.read(user.getId());
+ assertEquals(user.getUserName(), userTO.getUsername());
+ assertTrue(user.isActive());
+ assertEquals(user.getDisplayName(),
userTO.getDerAttr("cn").get().getValues().get(0));
+ assertEquals(user.getName().getGivenName(),
userTO.getPlainAttr("firstname").get().getValues().get(0));
+ assertEquals(user.getName().getFamilyName(),
userTO.getPlainAttr("surname").get().getValues().get(0));
+ assertEquals(user.getName().getFormatted(),
userTO.getPlainAttr("fullname").get().getValues().get(0));
+ assertEquals(user.getEmails().get(0).getValue(),
userTO.getPlainAttr("userId").get().getValues().get(0));
+ assertEquals(user.getEmails().get(1).getValue(),
userTO.getPlainAttr("email").get().getValues().get(0));
+ assertEquals(user.getExtensionInfo().getAttributes().get("gender"),
+ userTO.getPlainAttr("gender").get().getValues().get(0));
+
+ response = webClient().path("Users").path(user.getId()).delete();
+ assertEquals(Response.Status.NO_CONTENT.getStatusCode(),
response.getStatus());
+
+ response = webClient().path("Users").path(user.getId()).get();
+ assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
response.getStatus());
+ CONF.setExtensionUserConf(null);
+ SCIM_CONF_SERVICE.set(CONF);
+ }
+
@Test
public void updateUser() {
SCIM_CONF_SERVICE.set(CONF);
- SCIMUser user = getSampleUser(UUID.randomUUID().toString());
+ SCIMUser user = getSampleUser(UUID.randomUUID().toString(),
List.of(Resource.User.schema()));
Response response = webClient().path("Users").post(user);
assertEquals(Response.Status.CREATED.getStatusCode(),
response.getStatus());
@@ -531,7 +620,7 @@ public class SCIMITCase extends AbstractITCase {
public void replaceUser() {
SCIM_CONF_SERVICE.set(CONF);
- SCIMUser user = getSampleUser(UUID.randomUUID().toString());
+ SCIMUser user = getSampleUser(UUID.randomUUID().toString(),
List.of(Resource.User.schema()));
Response response = webClient().path("Users").post(user);
assertEquals(Response.Status.CREATED.getStatusCode(),
response.getStatus());
@@ -552,7 +641,7 @@ public class SCIMITCase extends AbstractITCase {
public void deleteUser() {
SCIM_CONF_SERVICE.set(CONF);
- SCIMUser user = getSampleUser(UUID.randomUUID().toString());
+ SCIMUser user = getSampleUser(UUID.randomUUID().toString(),
List.of(Resource.User.schema()));
Response response = webClient().path("Users").post(user);
assertEquals(Response.Status.CREATED.getStatusCode(),
response.getStatus());