This is an automated email from the ASF dual-hosted git repository. ilgrosso pushed a commit to branch 3_0_X in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/3_0_X by this push: new 38eedf2c4b [SYNCOPE-1893] SCIM Group extension + SCIM AnyObject extension (#1145) 38eedf2c4b is described below commit 38eedf2c4baa1e9bac5fc9fa8ae063bbd4efa361 Author: Samuel Garofalo <72073457+samuelg...@users.noreply.github.com> AuthorDate: Mon Jul 28 11:25:28 2025 +0200 [SYNCOPE-1893] SCIM Group extension + SCIM AnyObject extension (#1145) --- .../panels/SCIMConfExtensionAnyObjectPanel.java | 48 ++++ ...erPanel.java => SCIMConfExtensionAnyPanel.java} | 28 ++- .../panels/SCIMConfExtensionGroupPanel.java | 39 +++ .../console/panels/SCIMConfExtensionUserPanel.java | 62 +---- .../client/console/panels/SCIMConfPanel.java | 32 ++- .../panels/mapping/SCIMExtensionMappingPanel.java | 22 +- ...erPanel.html => SCIMConfExtensionAnyPanel.html} | 0 .../client/console/panels/SCIMConfPanel.properties | 1 + .../console/panels/SCIMConfPanel_it.properties | 1 + .../console/panels/SCIMConfPanel_pt_BR.properties | 1 + .../console/panels/SCIMConfPanel_ru.properties | 1 + .../apache/syncope/common/lib/scim/SCIMConf.java | 24 +- ...sionUserConf.java => SCIMExtensionAnyConf.java} | 2 +- .../lib/scim/SCIMExtensionAnyObjectConf.java | 44 ++++ .../apache/syncope/core/logic/SCIMDataBinder.java | 224 ++++++++++++++++- .../org/apache/syncope/core/logic/SCIMLogic.java | 60 ++++- .../syncope/core/logic/scim/SCIMConfManager.java | 81 ++++--- .../syncope/core/logic/scim/SearchCondVisitor.java | 55 +++-- .../syncope/core/logic/scim/SCIMFilterTest.java | 6 +- .../apache/syncope/ext/scimv2/api/data/Meta.java | 7 +- .../syncope/ext/scimv2/api/data/SCIMAnyObject.java | 90 +++++++ .../syncope/ext/scimv2/api/data/SCIMGroup.java | 17 +- .../scimv2/api/service/SCIMAnyObjectService.java | 26 ++ .../syncope/ext/scimv2/api/type/Resource.java | 1 + .../ext/scimv2/cxf/SCIMv2RESTCXFContext.java | 39 ++- .../scimv2/cxf/service/AbstractSCIMService.java | 61 +++-- ...viceImpl.java => SCIMAnyObjectServiceImpl.java} | 172 +++++++------- .../scimv2/cxf/service/SCIMGroupServiceImpl.java | 18 +- .../ext/scimv2/cxf/service/SCIMServiceImpl.java | 6 +- .../scimv2/cxf/service/SCIMUserServiceImpl.java | 18 +- .../org/apache/syncope/fit/core/SCIMITCase.java | 264 ++++++++++++++++++++- 31 files changed, 1172 insertions(+), 278 deletions(-) diff --git a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionAnyObjectPanel.java b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionAnyObjectPanel.java new file mode 100644 index 0000000000..562d8e7ab8 --- /dev/null +++ b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionAnyObjectPanel.java @@ -0,0 +1,48 @@ +/* + * 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 java.util.Optional; +import org.apache.syncope.common.lib.scim.SCIMConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyObjectConf; + +public class SCIMConfExtensionAnyObjectPanel extends SCIMConfExtensionAnyPanel { + + private static final long serialVersionUID = -1540432800132655369L; + + public SCIMConfExtensionAnyObjectPanel(final String id, final SCIMConf scimConf, final String anyTypeKey) { + super(id, scimConf, anyTypeKey); + } + + @Override + public SCIMExtensionAnyConf getExtensionAnyConf(final SCIMConf scimConf) { + Optional<SCIMExtensionAnyObjectConf> scimExtAnyObjectConf = + scimConf.getExtensionAnyObjectsConf().stream() + .filter(scimExtensionAnyObjectConf -> scimExtensionAnyObjectConf.getType().equals(anyTypeKey)) + .findFirst(); + if (scimExtAnyObjectConf.isPresent()) { + return scimExtAnyObjectConf.get(); + } + SCIMExtensionAnyObjectConf scimExtensionAnyObjectConf = new SCIMExtensionAnyObjectConf(); + scimExtensionAnyObjectConf.setType(anyTypeKey); + scimConf.getExtensionAnyObjectsConf().add(scimExtensionAnyObjectConf); + return scimExtensionAnyObjectConf; + } +} 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/SCIMConfExtensionAnyPanel.java similarity index 72% copy from ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.java copy to ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionAnyPanel.java index b8dbb6a377..d328b80cc9 100644 --- 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/SCIMConfExtensionAnyPanel.java @@ -21,23 +21,23 @@ package org.apache.syncope.client.console.panels; import org.apache.syncope.client.console.panels.mapping.SCIMExtensionMappingPanel; 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.syncope.common.lib.scim.SCIMExtensionAnyConf; 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 { +public abstract class SCIMConfExtensionAnyPanel extends SCIMConfTabPanel { private static final long serialVersionUID = 2459231778083046011L; - public SCIMConfExtensionUserPanel(final String id, final SCIMConf scimConf) { + protected final String anyTypeKey; + + protected SCIMConfExtensionAnyPanel(final String id, final SCIMConf scimConf, final String anyTypeKey) { super(id); + this.anyTypeKey = anyTypeKey; - if (scimConf.getExtensionUserConf() == null) { - scimConf.setExtensionUserConf(new SCIMExtensionUserConf()); - } - SCIMExtensionUserConf scimExtensionUserConf = scimConf.getExtensionUserConf(); + SCIMExtensionAnyConf scimExtensionAnyConf = getExtensionAnyConf(scimConf); AjaxTextFieldPanel namePanel = new AjaxTextFieldPanel("name", "name", new PropertyModel<>("name", "name") { @@ -45,12 +45,12 @@ public class SCIMConfExtensionUserPanel extends SCIMConfTabPanel { @Override public String getObject() { - return scimExtensionUserConf.getName(); + return scimExtensionAnyConf.getName(); } @Override public void setObject(final String object) { - scimExtensionUserConf.setName(object); + scimExtensionAnyConf.setName(object); } }); add(namePanel); @@ -62,20 +62,22 @@ public class SCIMConfExtensionUserPanel extends SCIMConfTabPanel { @Override public String getObject() { - return scimExtensionUserConf.getDescription(); + return scimExtensionAnyConf.getDescription(); } @Override public void setObject(final String object) { - scimExtensionUserConf.setDescription(object); + scimExtensionAnyConf.setDescription(object); } }); add(descriptionPanel); SCIMExtensionMappingPanel extensionMappingPanel = new SCIMExtensionMappingPanel( - "mapping", new ListModel<>(scimExtensionUserConf.getAttributes())); - Form<SCIMExtensionUserConf> form = new Form<>("form", new Model<>(scimExtensionUserConf)); + "mapping", new ListModel<>(scimExtensionAnyConf.getAttributes()), anyTypeKey); + Form<SCIMExtensionAnyConf> form = new Form<>("form", new Model<>(scimExtensionAnyConf)); form.add(extensionMappingPanel); add(form); } + + abstract SCIMExtensionAnyConf getExtensionAnyConf(SCIMConf scimConf); } diff --git a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionGroupPanel.java b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionGroupPanel.java new file mode 100644 index 0000000000..3298a2fa8f --- /dev/null +++ b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/SCIMConfExtensionGroupPanel.java @@ -0,0 +1,39 @@ +/* + * 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.common.lib.scim.SCIMConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyConf; + +public class SCIMConfExtensionGroupPanel extends SCIMConfExtensionAnyPanel { + + private static final long serialVersionUID = -3719006384765921047L; + + public SCIMConfExtensionGroupPanel(final String id, final SCIMConf scimConf, final String anyTypeKey) { + super(id, scimConf, anyTypeKey); + } + + @Override + public SCIMExtensionAnyConf getExtensionAnyConf(final SCIMConf scimConf) { + if (scimConf.getExtensionGroupConf() == null) { + scimConf.setExtensionGroupConf(new SCIMExtensionAnyConf()); + } + return scimConf.getExtensionGroupConf(); + } +} 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 index b8dbb6a377..beb1f0684b 100644 --- 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 @@ -18,64 +18,22 @@ */ package org.apache.syncope.client.console.panels; -import org.apache.syncope.client.console.panels.mapping.SCIMExtensionMappingPanel; -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; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyConf; -public class SCIMConfExtensionUserPanel extends SCIMConfTabPanel { +public class SCIMConfExtensionUserPanel extends SCIMConfExtensionAnyPanel { - private static final long serialVersionUID = 2459231778083046011L; + private static final long serialVersionUID = -94504185795020353L; - public SCIMConfExtensionUserPanel(final String id, final SCIMConf scimConf) { - super(id); + public SCIMConfExtensionUserPanel(final String id, final SCIMConf scimConf, final String anyTypeKey) { + super(id, scimConf, anyTypeKey); + } + @Override + public SCIMExtensionAnyConf getExtensionAnyConf(final SCIMConf scimConf) { if (scimConf.getExtensionUserConf() == null) { - scimConf.setExtensionUserConf(new SCIMExtensionUserConf()); + scimConf.setExtensionUserConf(new SCIMExtensionAnyConf()); } - 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); - - SCIMExtensionMappingPanel extensionMappingPanel = new SCIMExtensionMappingPanel( - "mapping", new ListModel<>(scimExtensionUserConf.getAttributes())); - Form<SCIMExtensionUserConf> form = new Form<>("form", new Model<>(scimExtensionUserConf)); - form.add(extensionMappingPanel); - add(form); + return scimConf.getExtensionUserConf(); } } 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 a36c2f8d15..df886c401a 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 @@ -24,10 +24,12 @@ 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.AnyTypeRestClient; 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.syncope.common.lib.types.AnyTypeKind; import org.apache.wicket.PageReference; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; @@ -35,6 +37,7 @@ import org.apache.wicket.extensions.markup.html.tabs.ITab; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.Model; +import org.apache.wicket.model.ResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,6 +51,9 @@ public class SCIMConfPanel extends WizardMgtPanel<SCIMConf> { @SpringBean protected SCIMConfRestClient scimConfRestClient; + @SpringBean + protected AnyTypeRestClient anyTypeRestClient; + protected final SCIMConf scimConf; public SCIMConfPanel( @@ -124,7 +130,7 @@ public class SCIMConfPanel extends WizardMgtPanel<SCIMConf> { @Override public WebMarkupContainer getPanel(final String panelId) { - return new SCIMConfExtensionUserPanel(panelId, scimConf); + return new SCIMConfExtensionUserPanel(panelId, scimConf, AnyTypeKind.USER.name()); } }); @@ -138,6 +144,30 @@ public class SCIMConfPanel extends WizardMgtPanel<SCIMConf> { } }); + tabs.add(new ITabComponent(new Model<>(getString("tab6")), getString("tab6")) { + + private static final long serialVersionUID = -7858187494595192532L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new SCIMConfExtensionGroupPanel(panelId, scimConf, AnyTypeKind.GROUP.name()); + } + }); + + anyTypeRestClient.listAnyTypes().stream() + .filter(anyTypeTO -> AnyTypeKind.ANY_OBJECT.equals(anyTypeTO.getKind())) + .forEach(anyTypeTO -> + tabs.add(new ITabComponent( + new ResourceModel("anyType." + anyTypeTO.getKey(), anyTypeTO.getKey())) { + + private static final long serialVersionUID = 6429988338658964324L; + + @Override + public WebMarkupContainer getPanel(final String panelId) { + return new SCIMConfExtensionAnyObjectPanel(panelId, scimConf, anyTypeTO.getKey()); + } + })); + return tabs; } } diff --git a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtensionMappingPanel.java b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtensionMappingPanel.java index 683794130c..07b43469f8 100644 --- a/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtensionMappingPanel.java +++ b/ext/scimv2/client-console/src/main/java/org/apache/syncope/client/console/panels/mapping/SCIMExtensionMappingPanel.java @@ -35,7 +35,6 @@ import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoiceP 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; @@ -79,11 +78,15 @@ public class SCIMExtensionMappingPanel extends Panel { protected final WebMarkupContainer mappingContainer; + protected final String anyTypeKey; + public SCIMExtensionMappingPanel( final String id, - final IModel<List<SCIMItem>> model) { + final IModel<List<SCIMItem>> model, + final String anyTypeKey) { super(id); + this.anyTypeKey = anyTypeKey; setOutputMarkupId(true); mappingContainer = new WebMarkupContainer("mappingContainer"); @@ -264,9 +267,18 @@ public class SCIMExtensionMappingPanel extends Panel { } protected IModel<List<String>> getExtAttrNames() { - List<String> choices = new ArrayList<>(ClassPathScanImplementationLookup.USER_FIELD_NAMES); - - anyTypeClassRestClient.list(anyTypeRestClient.read(AnyTypeKind.USER.name()).getClasses()). + List<String> choices = new ArrayList<>(); + switch (anyTypeKey) { + case "USER": + choices.addAll(ClassPathScanImplementationLookup.USER_FIELD_NAMES); + break; + case "GROUP": + choices.addAll(ClassPathScanImplementationLookup.GROUP_FIELD_NAMES); + break; + default: + choices.addAll(ClassPathScanImplementationLookup.ANY_OBJECT_FIELD_NAMES); + } + anyTypeClassRestClient.list(anyTypeRestClient.read(anyTypeKey).getClasses()). forEach(anyTypeClassTO -> { choices.addAll(anyTypeClassTO.getPlainSchemas()); choices.addAll(anyTypeClassTO.getDerSchemas()); 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/SCIMConfExtensionAnyPanel.html similarity index 100% rename from ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfExtensionUserPanel.html rename to ext/scimv2/client-console/src/main/resources/org/apache/syncope/client/console/panels/SCIMConfExtensionAnyPanel.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 fd36da900c..954ee7de18 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 @@ -21,3 +21,4 @@ tab3=EnterpriseUser saveButton=Save tab4=ExtensionUser tab5=Group +tab6=ExtensionGroup 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 fd36da900c..954ee7de18 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 @@ -21,3 +21,4 @@ tab3=EnterpriseUser saveButton=Save tab4=ExtensionUser tab5=Group +tab6=ExtensionGroup 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 fd36da900c..954ee7de18 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 @@ -21,3 +21,4 @@ tab3=EnterpriseUser saveButton=Save tab4=ExtensionUser tab5=Group +tab6=ExtensionGroup 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 fd36da900c..954ee7de18 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 @@ -21,3 +21,4 @@ tab3=EnterpriseUser saveButton=Save tab4=ExtensionUser tab5=Group +tab6=ExtensionGroup 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 468b5d289e..d90ef49b72 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 @@ -19,6 +19,8 @@ package org.apache.syncope.common.lib.scim; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; public class SCIMConf implements Serializable { @@ -32,10 +34,14 @@ public class SCIMConf implements Serializable { private SCIMEnterpriseUserConf enterpriseUserConf; - private SCIMExtensionUserConf extensionUserConf; + private SCIMExtensionAnyConf extensionUserConf; private SCIMGroupConf groupConf; + private SCIMExtensionAnyConf extensionGroupConf; + + private final List<SCIMExtensionAnyObjectConf> extensionAnyObjectsConf = new ArrayList<>(); + public SCIMGeneralConf getGeneralConf() { return generalConf; } @@ -60,14 +66,26 @@ public class SCIMConf implements Serializable { this.enterpriseUserConf = enterpriseUserConf; } - public SCIMExtensionUserConf getExtensionUserConf() { + public SCIMExtensionAnyConf getExtensionUserConf() { return extensionUserConf; } - public void setExtensionUserConf(final SCIMExtensionUserConf extensionUserConf) { + public void setExtensionUserConf(final SCIMExtensionAnyConf extensionUserConf) { this.extensionUserConf = extensionUserConf; } + public SCIMExtensionAnyConf getExtensionGroupConf() { + return extensionGroupConf; + } + + public void setExtensionGroupConf(final SCIMExtensionAnyConf extensionGroupConf) { + this.extensionGroupConf = extensionGroupConf; + } + + public List<SCIMExtensionAnyObjectConf> getExtensionAnyObjectsConf() { + return extensionAnyObjectsConf; + } + 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/SCIMExtensionAnyConf.java similarity index 97% rename from ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionUserConf.java rename to ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionAnyConf.java index cae6330caf..74c81e96a5 100644 --- 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/SCIMExtensionAnyConf.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -public class SCIMExtensionUserConf implements Serializable { +public class SCIMExtensionAnyConf implements Serializable { private static final long serialVersionUID = -9091596628402547645L; diff --git a/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionAnyObjectConf.java b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionAnyObjectConf.java new file mode 100644 index 0000000000..7872e44511 --- /dev/null +++ b/ext/scimv2/common-lib/src/main/java/org/apache/syncope/common/lib/scim/SCIMExtensionAnyObjectConf.java @@ -0,0 +1,44 @@ +/* + * 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 class SCIMExtensionAnyObjectConf extends SCIMExtensionAnyConf { + + private static final long serialVersionUID = 6600395333691186244L; + + private String type; + + private String externalId; + + public String getType() { + return type; + } + + public void setType(final String type) { + this.type = type; + } + + public String getExternalId() { + return externalId; + } + + public void setExternalId(final String externalId) { + this.externalId = externalId; + } +} 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 7c37d03a3c..1518675eb0 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 @@ -35,6 +35,8 @@ import org.apache.syncope.common.lib.AnyOperations; import org.apache.syncope.common.lib.Attr; import org.apache.syncope.common.lib.EntityTOUtils; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.request.AnyObjectCR; +import org.apache.syncope.common.lib.request.AnyObjectUR; import org.apache.syncope.common.lib.request.AttrPatch; import org.apache.syncope.common.lib.request.GroupCR; import org.apache.syncope.common.lib.request.GroupUR; @@ -46,8 +48,10 @@ import org.apache.syncope.common.lib.request.UserUR; import org.apache.syncope.common.lib.scim.SCIMComplexConf; import org.apache.syncope.common.lib.scim.SCIMConf; import org.apache.syncope.common.lib.scim.SCIMEnterpriseUserConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyObjectConf; import org.apache.syncope.common.lib.scim.SCIMManagerConf; import org.apache.syncope.common.lib.scim.SCIMUserAddressConf; +import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.UserTO; @@ -66,6 +70,7 @@ import org.apache.syncope.ext.scimv2.api.BadRequestException; import org.apache.syncope.ext.scimv2.api.data.Group; 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.SCIMAnyObject; 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; @@ -89,8 +94,6 @@ public class SCIMDataBinder { protected static final Logger LOG = LoggerFactory.getLogger(SCIMDataBinder.class); - protected static final List<String> GROUP_SCHEMAS = List.of(Resource.Group.schema()); - /** * Translates the given SCIM filter into the equivalent JEXL expression. * @@ -208,7 +211,7 @@ public class SCIMDataBinder { userTO.getKey(), schemas, new Meta( - Resource.User, + Resource.User.name(), userTO.getCreationDate(), Optional.ofNullable(userTO.getLastChangeDate()).orElse(userTO.getCreationDate()), userTO.getETagValue(), @@ -509,6 +512,41 @@ public class SCIMDataBinder { } } + protected void setAttribute( + final GroupTO groupTO, + final String schema, + final String value) { + + if (schema == null || value == null) { + return; + } + + switch (schema) { + case "name": + groupTO.setName(value); + break; + + default: + groupTO.getPlainAttrs().add(new Attr.Builder(schema).value(value).build()); + } + } + + protected void setAttribute( + final AnyObjectTO anyObjectTO, + final String schema, + final String value) { + + if (schema == null || value == null) { + return; + } + + if ("name".equals(schema)) { + anyObjectTO.setName(value); + } else { + anyObjectTO.getPlainAttrs().add(new Attr.Builder(schema).value(value).build()); + } + } + protected <E extends Enum<?>> void setAttribute( final Set<Attr> attrs, final List<SCIMComplexConf<E>> confs, @@ -1034,18 +1072,24 @@ public class SCIMDataBinder { final List<String> attributes, final List<String> excludedAttributes) { + SCIMConf conf = confManager.get(); + List<String> schemas = new ArrayList<>(); + schemas.add(Resource.Group.schema()); + if (conf.getExtensionGroupConf() != null) { + schemas.add(Resource.ExtensionGroup.schema()); + } + SCIMGroup group = new SCIMGroup( groupTO.getKey(), + schemas, new Meta( - Resource.Group, + Resource.Group.name(), groupTO.getCreationDate(), Optional.ofNullable(groupTO.getLastChangeDate()).orElse(groupTO.getCreationDate()), groupTO.getETagValue(), location), output(attributes, excludedAttributes, "displayName", groupTO.getName())); - SCIMConf conf = confManager.get(); - Map<String, Attr> attrs = new HashMap<>(); attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getPlainAttrs())); attrs.putAll(EntityTOUtils.buildAttrMap(groupTO.getDerAttrs())); @@ -1063,6 +1107,19 @@ public class SCIMDataBinder { membCond.setGroup(groupTO.getKey()); SearchCond searchCond = SearchCond.getLeaf(membCond); + if (conf.getExtensionGroupConf() != null) { + SCIMExtensionInfo extensionInfo = new SCIMExtensionInfo(); + conf.getExtensionGroupConf().asMap().forEach((scimAttr, syncopeAttr) -> { + if (output(attributes, excludedAttributes, scimAttr) && attrs.containsKey(syncopeAttr)) { + extensionInfo.getAttributes().put(scimAttr, attrs.get(syncopeAttr).getValues().get(0)); + } + }); + + if (!extensionInfo.isEmpty()) { + group.setExtensionInfo(extensionInfo); + } + } + if (output(attributes, excludedAttributes, "members")) { int count = userLogic.search( searchCond, 1, 1, List.of(), SyncopeConstants.ROOT_REALM, true, false).getLeft(); @@ -1084,7 +1141,16 @@ public class SCIMDataBinder { } public GroupTO toGroupTO(final SCIMGroup group, final boolean checkSchemas) { - if (checkSchemas && !GROUP_SCHEMAS.equals(group.getSchemas())) { + SCIMConf conf = confManager.get(); + Set<String> expectedSchemas = new HashSet<>(); + expectedSchemas.add(Resource.Group.schema()); + if (conf.getExtensionGroupConf() != null) { + expectedSchemas.add(Resource.ExtensionGroup.schema()); + } + if (checkSchemas + && (!group.getSchemas().containsAll(expectedSchemas) + || !expectedSchemas.containsAll(group.getSchemas()))) { + throw new BadRequestException(ErrorType.invalidValue); } @@ -1093,7 +1159,6 @@ public class SCIMDataBinder { groupTO.setKey(group.getId()); groupTO.setName(group.getDisplayName()); - SCIMConf conf = confManager.get(); if (conf.getGroupConf() != null && conf.getGroupConf().getExternalId() != null && group.getExternalId() != null) { @@ -1102,6 +1167,11 @@ public class SCIMDataBinder { value(group.getExternalId()).build()); } + if (conf.getExtensionGroupConf() != null && group.getExtensionInfo() != null) { + conf.getExtensionGroupConf().asMap().forEach((scimAttr, syncopeAttr) -> setAttribute( + groupTO, syncopeAttr, group.getExtensionInfo().getAttributes().get(scimAttr))); + } + return groupTO; } @@ -1128,11 +1198,147 @@ public class SCIMDataBinder { groupUR.setName(name.build()); } else { SCIMConf conf = confManager.get(); - if (conf.getGroupConf() != null) { + if (conf.getGroupConf() != null && "externalId".equals(op.getPath().getAttribute())) { setAttribute(groupUR.getPlainAttrs(), conf.getGroupConf().getExternalId(), op); } + if (conf.getExtensionGroupConf() != null) { + Optional.ofNullable(conf.getExtensionGroupConf()). + flatMap(schema -> Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute()))). + ifPresent(schema -> setAttribute(groupUR.getPlainAttrs(), schema, op)); + } } return groupUR; } + + public SCIMAnyObject toSCIMAnyObject( + final AnyObjectTO anyObjectTO, + final String location, + final List<String> attributes, + final List<String> excludedAttributes) { + SCIMConf conf = confManager.get(); + List<String> schemas = new ArrayList<>(); + SCIMExtensionAnyObjectConf scimExtensionAnyObjectConf = + conf.getExtensionAnyObjectsConf().stream() + .filter(scimExtAnyObjectConf -> scimExtAnyObjectConf.getType().equals(anyObjectTO.getType())) + .findFirst() + .orElseThrow(() -> new NotFoundException("SCIMExtensionAnyObjectConf not found")); + schemas.add("urn:ietf:params:scim:schemas:extension:syncope:2.0:" + scimExtensionAnyObjectConf.getType()); + + SCIMAnyObject anyObject = new SCIMAnyObject( + anyObjectTO.getKey(), + schemas, + new Meta( + "urn:ietf:params:scim:schemas:extension:syncope:2.0:" + + scimExtensionAnyObjectConf.getType(), + anyObjectTO.getCreationDate(), + Optional.ofNullable(anyObjectTO.getLastChangeDate()).orElse(anyObjectTO.getCreationDate()), + anyObjectTO.getETagValue(), + location), + output(attributes, excludedAttributes, "displayName", anyObjectTO.getName())); + + Map<String, Attr> attrs = new HashMap<>(); + attrs.putAll(EntityTOUtils.buildAttrMap(anyObjectTO.getPlainAttrs())); + attrs.putAll(EntityTOUtils.buildAttrMap(anyObjectTO.getDerAttrs())); + attrs.putAll(EntityTOUtils.buildAttrMap(anyObjectTO.getVirAttrs())); + + if (output(attributes, excludedAttributes, "externalId") + && scimExtensionAnyObjectConf.getExternalId() != null + && attrs.containsKey(scimExtensionAnyObjectConf.getExternalId())) { + + anyObject.setExternalId(attrs.get(scimExtensionAnyObjectConf.getExternalId()).getValues().get(0)); + } + + SCIMExtensionInfo extensionInfo = new SCIMExtensionInfo(); + scimExtensionAnyObjectConf.asMap().forEach((scimAttr, syncopeAttr) -> { + if (output(attributes, excludedAttributes, scimAttr) && attrs.containsKey(syncopeAttr)) { + extensionInfo.getAttributes().put(scimAttr, attrs.get(syncopeAttr).getValues().get(0)); + } + }); + + if (!extensionInfo.isEmpty()) { + anyObject.setExtensionInfo(extensionInfo); + } + + return anyObject; + } + + public AnyObjectTO toAnyObjectTO(final SCIMAnyObject anyObject, final boolean checkSchemas) { + SCIMConf conf = confManager.get(); + Set<String> expectedSchemas = new HashSet<>(); + Optional<SCIMExtensionAnyObjectConf> scimExtensionAnyObjectConf = + conf.getExtensionAnyObjectsConf().stream() + .filter(scimExtAnyObjectConf -> + scimExtAnyObjectConf.getType().equals(anyObject.getExtensionUrn() + .substring(anyObject.getExtensionUrn().lastIndexOf(':') + 1))) + .findFirst(); + scimExtensionAnyObjectConf.ifPresent(scimExtAnyObjectConf -> + expectedSchemas.add("urn:ietf:params:scim:schemas:extension:syncope:2.0:" + + scimExtAnyObjectConf.getType())); + if (checkSchemas + && (!anyObject.getSchemas().containsAll(expectedSchemas) + || !expectedSchemas.containsAll(anyObject.getSchemas()))) { + + throw new BadRequestException(ErrorType.invalidValue); + } + + AnyObjectTO anyObjectTO = new AnyObjectTO(); + anyObjectTO.setRealm(SyncopeConstants.ROOT_REALM); + anyObjectTO.setKey(anyObject.getId()); + anyObjectTO.setName(anyObject.getDisplayName()); + anyObjectTO.setType(anyObject.getExtensionUrn().substring(anyObject.getExtensionUrn().lastIndexOf(':') + 1)); + + if (scimExtensionAnyObjectConf.isPresent() + && scimExtensionAnyObjectConf.get().getExternalId() != null && anyObject.getExternalId() != null) { + + anyObjectTO.getPlainAttrs().add( + new Attr.Builder(scimExtensionAnyObjectConf.get().getExternalId()). + value(anyObject.getExternalId()).build()); + } + + if (scimExtensionAnyObjectConf.isPresent() && anyObject.getExtensionInfo() != null) { + scimExtensionAnyObjectConf.get().asMap().forEach((scimAttr, syncopeAttr) -> setAttribute( + anyObjectTO, syncopeAttr, anyObject.getExtensionInfo().getAttributes().get(scimAttr))); + } + + return anyObjectTO; + } + + public AnyObjectCR toAnyObjectCR(final SCIMAnyObject anyObject) { + AnyObjectTO anyObjectTO = toAnyObjectTO(anyObject, true); + AnyObjectCR anyObjectCR = new AnyObjectCR(); + EntityTOUtils.toAnyCR(anyObjectTO, anyObjectCR); + return anyObjectCR; + } + + public AnyObjectUR toAnyObjectUR(final AnyObjectTO before, final SCIMPatchOperation op) { + if (op.getPath() == null) { + throw new UnsupportedOperationException("Empty path not supported for AnyObjects"); + } + + AnyObjectUR anyObjectUR = new AnyObjectUR.Builder(before.getKey()).build(); + + if ("displayName".equals(op.getPath().getAttribute())) { + StringReplacePatchItem.Builder name = new StringReplacePatchItem.Builder(). + operation(op.getOp() == PatchOp.remove ? PatchOperation.DELETE : PatchOperation.ADD_REPLACE); + if (!CollectionUtils.isEmpty(op.getValue())) { + name.value(op.getValue().get(0).toString()); + } + anyObjectUR.setName(name.build()); + } else { + SCIMConf conf = confManager.get(); + Optional<SCIMExtensionAnyObjectConf> scimExtensionAnyObjectConf = + conf.getExtensionAnyObjectsConf().stream() + .filter(scimExtAnyObjectConf -> scimExtAnyObjectConf.getType().equals(before.getType())) + .findFirst(); + if (scimExtensionAnyObjectConf.isPresent() && "externalId".equals(op.getPath().getAttribute())) { + setAttribute(anyObjectUR.getPlainAttrs(), scimExtensionAnyObjectConf.get().getExternalId(), op); + } + scimExtensionAnyObjectConf.flatMap(extensionAnyObjectConf -> Optional.of(extensionAnyObjectConf) + .flatMap(schema -> Optional.ofNullable(schema.asMap().get(op.getPath().getAttribute())))) + .ifPresent(schema -> setAttribute(anyObjectUR.getPlainAttrs(), schema, op)); + } + + return anyObjectUR; + } } 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 610e11795a..4a5bbfc844 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 @@ -100,6 +100,60 @@ public class SCIMLogic extends AbstractLogic<EntityTO> { + "\"location\": \"/v2/Schemas/urn:ietf:params:scim:schemas:extension:syncope:2.0:User\"}")); schemaArray.add(extensionObject); } + if (conf.getExtensionGroupConf() != null) { + ObjectNode extensionObject = MAPPER.createObjectNode(); + extensionObject.put("id", Resource.ExtensionGroup.schema()); + extensionObject.put("name", conf.getExtensionGroupConf().getName()); + extensionObject.put("description", conf.getExtensionGroupConf().getDescription()); + ArrayNode attributes = MAPPER.createArrayNode(); + conf.getExtensionGroupConf().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:Group\"}")); + schemaArray.add(extensionObject); + } + if (!conf.getExtensionAnyObjectsConf().isEmpty()) { + conf.getExtensionAnyObjectsConf().forEach(confItem -> { + ObjectNode extensionObject = MAPPER.createObjectNode(); + extensionObject.put("id", + "urn:ietf:params:scim:schemas:extension:syncope:2.0:" + confItem.getType()); + extensionObject.put("name", confItem.getName()); + extensionObject.put("description", confItem.getDescription()); + ArrayNode attributes = MAPPER.createArrayNode(); + confItem.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); + try { + extensionObject.putIfAbsent("meta", MAPPER.readTree("{\"resourceType\": \"Schema\"," + + "\"location\": \"/v2/Schemas/urn:ietf:params:scim:schemas:extension:syncope:2.0:" + + confItem.getType() + "}\"")); + } catch (IOException e) { + LOG.error("Could not parse the default schema definitions", e); + } + schemaArray.add(extensionObject); + }); + } schemas = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(tree); schemaMap.clear(); @@ -121,7 +175,7 @@ public class SCIMLogic extends AbstractLogic<EntityTO> { SERVICE_PROVIDER_CONFIG = new ServiceProviderConfig( new Meta( - Resource.ServiceProviderConfig, + Resource.ServiceProviderConfig.name(), conf.getGeneralConf().getCreationDate(), conf.getGeneralConf().getLastChangeDate(), conf.getGeneralConf().getETagValue(), @@ -162,12 +216,12 @@ public class SCIMLogic extends AbstractLogic<EntityTO> { String uri = uriBuilder.build().toASCIIString(); if (USER == null) { USER = new ResourceType("User", "User", "/Users", "User Account", Resource.User.schema(), - new Meta(Resource.ResourceType, null, null, null, uri + "User")); + new Meta(Resource.ResourceType.name(), null, null, null, uri + "User")); USER.getSchemaExtensions().add(new SchemaExtension(Resource.EnterpriseUser.schema(), true)); } if (GROUP == null) { GROUP = new ResourceType("Group", "Group", "/Groups", "Group", Resource.Group.schema(), - new Meta(Resource.ResourceType, null, null, null, uri + "Group")); + new Meta(Resource.ResourceType.name(), null, null, null, uri + "Group")); } } 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 7fab5c0e1e..7cef51a222 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 @@ -26,6 +26,7 @@ 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.SCIMItem; 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; @@ -88,40 +89,15 @@ public class SCIMConfManager { 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; - } + conf.getExtensionUserConf().getAttributes().forEach(this::checkSCIMItem); + } + if (conf.getExtensionGroupConf() != null) { + conf.getExtensionGroupConf().getAttributes().forEach(this::checkSCIMItem); + } + if (!conf.getExtensionAnyObjectsConf().isEmpty()) { + conf.getExtensionAnyObjectsConf().forEach(confItem -> { + if (confItem != null) { + confItem.getAttributes().forEach(this::checkSCIMItem); } }); } @@ -129,4 +105,41 @@ public class SCIMConfManager { confParamOps.set(AuthContextUtils.getDomain(), SCIMConf.KEY, Base64.getEncoder().encodeToString(POJOHelper.serialize(conf).getBytes())); } + + private void checkSCIMItem(final SCIMItem 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; + } + } + } } 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 fba72c8bef..06647905cc 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 @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.syncope.common.lib.scim.SCIMComplexConf; import org.apache.syncope.common.lib.scim.SCIMConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyObjectConf; import org.apache.syncope.common.lib.scim.SCIMUserAddressConf; import org.apache.syncope.common.lib.scim.SCIMUserConf; import org.apache.syncope.core.persistence.api.dao.search.AnyCond; @@ -40,12 +41,12 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { private static final List<String> MULTIVALUE = List.of( "emails", "phoneNumbers", "ims", "photos", "addresses"); - private static boolean schemaEquals(final Resource resource, final String value, final String schema) { + private static boolean schemaEquals(final String resource, final String value, final String schema) { return resource == null ? value.contains(":") ? StringUtils.substringAfterLast(value, ":").equalsIgnoreCase(schema) : value.equalsIgnoreCase(schema) - : value.equalsIgnoreCase(schema) || (resource.schema() + ":" + value).equalsIgnoreCase(schema); + : value.equalsIgnoreCase(schema) || (resource + ":" + value).equalsIgnoreCase(schema); } private static SearchCond setOperator(final AttrCond attrCond, final String operator) { @@ -154,11 +155,11 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { return null; } - private final Resource resource; + private final String resource; private final SCIMConf conf; - public SearchCondVisitor(final Resource resource, final SCIMConf conf) { + public SearchCondVisitor(final String resource, final SCIMConf conf) { this.resource = resource; this.conf = conf; } @@ -171,10 +172,13 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { public AttrCond createAttrCond(final String schema) { AttrCond attrCond = null; - if (schemaEquals(Resource.User, "userName", schema)) { + if (schemaEquals(Resource.User.schema(), "userName", schema)) { attrCond = new AnyCond(); attrCond.setSchema("username"); - } else if (resource == Resource.Group && schemaEquals(Resource.Group, "displayName", schema)) { + } else if ((resource.equals(Resource.Group.name()) + && schemaEquals(Resource.Group.schema(), "displayName", schema)) + || (resource.contains("urn:ietf:params:scim:schemas:extension:syncope:2.0:") + && schemaEquals(resource, "displayName", schema))) { attrCond = new AnyCond(); attrCond.setSchema("name"); } else if (schemaEquals(null, "meta.created", schema)) { @@ -186,11 +190,11 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { } switch (resource) { - case User: + case "User": if (conf.getUserConf() != null) { if (conf.getUserConf().getName() != null) { for (Map.Entry<String, String> entry : conf.getUserConf().getName().asMap().entrySet()) { - if (schemaEquals(Resource.User, "name." + entry.getKey(), schema)) { + if (schemaEquals(Resource.User.schema(), "name." + entry.getKey(), schema)) { attrCond = new AttrCond(); attrCond.setSchema(entry.getValue()); } @@ -198,7 +202,7 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { } for (Map.Entry<String, String> entry : conf.getUserConf().asMap().entrySet()) { - if (schemaEquals(Resource.User, entry.getKey(), schema)) { + if (schemaEquals(Resource.User.schema(), entry.getKey(), schema)) { attrCond = new AttrCond(); attrCond.setSchema(entry.getValue()); } @@ -206,7 +210,7 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { for (SCIMUserAddressConf address : conf.getUserConf().getAddresses()) { for (Map.Entry<String, String> entry : address.asMap().entrySet()) { - if (schemaEquals(Resource.User, "addresses." + entry.getKey(), schema)) { + if (schemaEquals(Resource.User.schema(), "addresses." + entry.getKey(), schema)) { attrCond = new AttrCond(); attrCond.setSchema(entry.getValue()); } @@ -216,7 +220,7 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { if (conf.getEnterpriseUserConf() != null) { for (Map.Entry<String, String> entry : conf.getEnterpriseUserConf().asMap().entrySet()) { - if (schemaEquals(Resource.EnterpriseUser, entry.getKey(), schema)) { + if (schemaEquals(Resource.EnterpriseUser.schema(), entry.getKey(), schema)) { attrCond = new AttrCond(); attrCond.setSchema(entry.getValue()); } @@ -232,7 +236,7 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { if (conf.getExtensionUserConf() != null) { for (Map.Entry<String, String> entry : conf.getExtensionUserConf().asMap().entrySet()) { - if (schemaEquals(Resource.ExtensionUser, entry.getKey(), schema)) { + if (schemaEquals(Resource.ExtensionUser.schema(), entry.getKey(), schema)) { attrCond = new AttrCond(); attrCond.setSchema(entry.getValue()); } @@ -240,10 +244,19 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { } break; - case Group: + case "Group": if (conf.getGroupConf() != null) { for (Map.Entry<String, String> entry : conf.getGroupConf().asMap().entrySet()) { - if (schemaEquals(Resource.Group, entry.getKey(), schema)) { + if (schemaEquals(Resource.Group.schema(), entry.getKey(), schema)) { + attrCond = new AttrCond(); + attrCond.setSchema(entry.getValue()); + } + } + } + + if (conf.getExtensionGroupConf() != null) { + for (Map.Entry<String, String> entry : conf.getExtensionGroupConf().asMap().entrySet()) { + if (schemaEquals(Resource.ExtensionGroup.schema(), entry.getKey(), schema)) { attrCond = new AttrCond(); attrCond.setSchema(entry.getValue()); } @@ -252,6 +265,20 @@ public class SearchCondVisitor extends SCIMFilterBaseVisitor<SearchCond> { break; default: + if (!conf.getExtensionAnyObjectsConf().isEmpty()) { + Optional<SCIMExtensionAnyObjectConf> scimExtAnyObject = conf.getExtensionAnyObjectsConf().stream() + .filter(scimExtensionAnyConf -> scimExtensionAnyConf.getType().equals(resource)) + .findFirst(); + if (scimExtAnyObject.isPresent()) { + for (Map.Entry<String, String> entry : scimExtAnyObject.get().asMap().entrySet()) { + if (schemaEquals("urn:ietf:params:scim:schemas:extension:syncope:2.0:" + resource, + entry.getKey(), schema)) { + attrCond = new AttrCond(); + attrCond.setSchema(entry.getValue()); + } + } + } + } } if (attrCond == null) { diff --git a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java index 5e62a41252..bacef28cb9 100644 --- a/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java +++ b/ext/scimv2/logic/src/test/java/org/apache/syncope/core/logic/scim/SCIMFilterTest.java @@ -25,7 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.apache.syncope.common.lib.scim.SCIMComplexConf; import org.apache.syncope.common.lib.scim.SCIMConf; import org.apache.syncope.common.lib.scim.SCIMEnterpriseUserConf; -import org.apache.syncope.common.lib.scim.SCIMExtensionUserConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyConf; import org.apache.syncope.common.lib.scim.SCIMItem; import org.apache.syncope.common.lib.scim.SCIMUserConf; import org.apache.syncope.common.lib.scim.SCIMUserNameConf; @@ -65,14 +65,14 @@ public class SCIMFilterTest { entConf.setOrganization("org"); conf.setEnterpriseUserConf(entConf); - SCIMExtensionUserConf extConf = new SCIMExtensionUserConf(); + SCIMExtensionAnyConf extConf = new SCIMExtensionAnyConf(); SCIMItem item = new SCIMItem(); item.setIntAttrName("realm"); item.setExtAttrName("realm"); extConf.add(item); conf.setExtensionUserConf(extConf); - VISITOR = new SearchCondVisitor(Resource.User, conf); + VISITOR = new SearchCondVisitor(Resource.User.name(), conf); } @Test diff --git a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Meta.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Meta.java index 7c6d7a9cbe..7d17eac50e 100644 --- a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Meta.java +++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/Meta.java @@ -24,13 +24,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.time.OffsetDateTime; import java.util.Optional; import javax.ws.rs.core.EntityTag; -import org.apache.syncope.ext.scimv2.api.type.Resource; public class Meta extends SCIMBean { private static final long serialVersionUID = 8976451652101091915L; - private final Resource resourceType; + private final String resourceType; private final OffsetDateTime created; @@ -43,7 +42,7 @@ public class Meta extends SCIMBean { @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public Meta( - @JsonProperty("resourceType") final Resource resourceType, + @JsonProperty("resourceType") final String resourceType, @JsonProperty("created") final OffsetDateTime created, @JsonProperty("lastModified") final OffsetDateTime lastModified, @JsonProperty("version") final String version, @@ -56,7 +55,7 @@ public class Meta extends SCIMBean { this.location = location; } - public Resource getResourceType() { + public String getResourceType() { return resourceType; } diff --git a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMAnyObject.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMAnyObject.java new file mode 100644 index 0000000000..5316ae1a3c --- /dev/null +++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMAnyObject.java @@ -0,0 +1,90 @@ +/* + * 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 com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.util.List; +import java.util.Map; + +@JsonPropertyOrder({ "schemas", "id", "externalId", "displayName", "extensionInfo", "meta" }) +public class SCIMAnyObject extends SCIMResource { + + @JsonIgnore + private String extensionUrn; + + @JsonIgnore + private SCIMExtensionInfo extensionInfo; + + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) + public SCIMAnyObject( + @JsonProperty("id") final String id, + @JsonProperty("schemas") final List<String> schemas, + @JsonProperty("meta") final Meta meta, + @JsonProperty("displayName") final String displayName) { + + super(id, schemas, meta); + super.setDisplayName(displayName); + this.extensionUrn = schemas.isEmpty() ? null : schemas.get(0); + } + + @JsonAnyGetter + public Map<String, SCIMExtensionInfo> getExtensionAsMap() { + if (extensionUrn != null && extensionInfo != null && !extensionInfo.isEmpty()) { + return Map.of(extensionUrn, extensionInfo); + } + return Map.of(); + } + + @JsonAnySetter + public void setDynamicExtension(final String key, final Object value) { + if (key.startsWith("urn:ietf:params:scim:schemas:extension:syncope:2.0:")) { + this.extensionUrn = key; + + if (value instanceof Map) { + SCIMExtensionInfo info = new SCIMExtensionInfo(); + Map<?, ?> rawMap = (Map<?, ?>) value; + for (Map.Entry<?, ?> entry : rawMap.entrySet()) { + info.add(entry.getKey().toString(), entry.getValue().toString()); + } + this.extensionInfo = info; + } + } + } + + public String getExtensionUrn() { + return extensionUrn; + } + + public void setExtensionUrn(final String extensionUrn) { + this.extensionUrn = extensionUrn; + } + + 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/data/SCIMGroup.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMGroup.java index c45d6da49d..248f09ab25 100644 --- a/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMGroup.java +++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/data/SCIMGroup.java @@ -23,26 +23,37 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import java.util.ArrayList; import java.util.List; -import org.apache.syncope.ext.scimv2.api.type.Resource; -@JsonPropertyOrder({ "schemas", "id", "externalId", "displayName", "members", "meta" }) +@JsonPropertyOrder({ "schemas", "id", "externalId", "displayName", "members", "extensionInfo", "meta" }) public class SCIMGroup extends SCIMResource { private static final long serialVersionUID = -2935466041674390279L; private final List<Member> members = new ArrayList<>(); + @JsonProperty("urn:ietf:params:scim:schemas:extension:syncope:2.0:Group") + private SCIMExtensionInfo extensionInfo; + @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) public SCIMGroup( @JsonProperty("id") final String id, + @JsonProperty("schemas") final List<String> schemas, @JsonProperty("meta") final Meta meta, @JsonProperty("displayName") final String displayName) { - super(id, List.of(Resource.Group.schema()), meta); + super(id, schemas, meta); super.setDisplayName(displayName); } public List<Member> getMembers() { return members; } + + 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/service/SCIMAnyObjectService.java b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/service/SCIMAnyObjectService.java new file mode 100644 index 0000000000..5572a30ab0 --- /dev/null +++ b/ext/scimv2/scim-rest-api/src/main/java/org/apache/syncope/ext/scimv2/api/service/SCIMAnyObjectService.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.ext.scimv2.api.service; + +import javax.ws.rs.Path; +import org.apache.syncope.ext.scimv2.api.data.SCIMAnyObject; + +@Path("v2/AnyObjects") +public interface SCIMAnyObjectService extends SCIMResourceService<SCIMAnyObject> { +} 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 0cbd6d2828..6882adff77 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 @@ -27,6 +27,7 @@ public enum Resource { 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"), + ExtensionGroup("urn:ietf:params:scim:schemas:extension:syncope:2.0:Group"), SearchRequest("urn:ietf:params:scim:api:messages:2.0:SearchRequest"), ListResponse("urn:ietf:params:scim:api:messages:2.0:ListResponse"), PatchOp("urn:ietf:params:scim:api:messages:2.0:PatchOp"), diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMv2RESTCXFContext.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMv2RESTCXFContext.java index d90b301fd9..b51b4858ec 100644 --- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMv2RESTCXFContext.java +++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/SCIMv2RESTCXFContext.java @@ -29,16 +29,20 @@ import org.apache.cxf.jaxrs.spring.JAXRSServerFactoryBeanDefinitionParser.Spring import org.apache.cxf.jaxrs.validation.JAXRSBeanValidationInInterceptor; import org.apache.cxf.transport.common.gzip.GZIPInInterceptor; import org.apache.cxf.transport.common.gzip.GZIPOutInterceptor; +import org.apache.syncope.core.logic.AnyObjectLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.SCIMDataBinder; import org.apache.syncope.core.logic.SCIMLogic; import org.apache.syncope.core.logic.UserLogic; import org.apache.syncope.core.logic.scim.SCIMConfManager; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.ext.scimv2.api.service.SCIMAnyObjectService; import org.apache.syncope.ext.scimv2.api.service.SCIMGroupService; import org.apache.syncope.ext.scimv2.api.service.SCIMService; import org.apache.syncope.ext.scimv2.api.service.SCIMUserService; +import org.apache.syncope.ext.scimv2.cxf.service.SCIMAnyObjectServiceImpl; import org.apache.syncope.ext.scimv2.cxf.service.SCIMGroupServiceImpl; import org.apache.syncope.ext.scimv2.cxf.service.SCIMServiceImpl; import org.apache.syncope.ext.scimv2.cxf.service.SCIMUserServiceImpl; @@ -75,6 +79,7 @@ public class SCIMv2RESTCXFContext { final SCIMService scimService, final SCIMGroupService scimv2GroupService, final SCIMUserService scimv2UserService, + final SCIMAnyObjectService scimv2AnyObjectService, final GZIPInInterceptor gzipInInterceptor, final GZIPOutInterceptor gzipOutInterceptor, final JAXRSBeanValidationInInterceptor validationInInterceptor, @@ -91,7 +96,8 @@ public class SCIMv2RESTCXFContext { scimv2Container.setProperties(Map.of("convert.wadl.resources.to.dom", "false")); - scimv2Container.setServiceBeans(List.of(scimService, scimv2GroupService, scimv2UserService)); + scimv2Container.setServiceBeans( + List.of(scimService, scimv2GroupService, scimv2UserService, scimv2AnyObjectService)); scimv2Container.setInInterceptors(List.of(gzipInInterceptor, validationInInterceptor)); @@ -108,13 +114,16 @@ public class SCIMv2RESTCXFContext { public SCIMService scimService( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager, final SCIMLogic scimLogic) { - return new SCIMServiceImpl(userDAO, groupDAO, userLogic, groupLogic, binder, confManager, scimLogic); + return new SCIMServiceImpl(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, + confManager, scimLogic); } @ConditionalOnMissingBean @@ -122,12 +131,15 @@ public class SCIMv2RESTCXFContext { public SCIMGroupService scimv2GroupService( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager) { - return new SCIMGroupServiceImpl(userDAO, groupDAO, userLogic, groupLogic, binder, confManager); + return new SCIMGroupServiceImpl(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, + confManager); } @ConditionalOnMissingBean @@ -135,11 +147,30 @@ public class SCIMv2RESTCXFContext { public SCIMUserService scimv2UserService( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager) { - return new SCIMUserServiceImpl(userDAO, groupDAO, userLogic, groupLogic, binder, confManager); + return new SCIMUserServiceImpl(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, + confManager); + } + + @ConditionalOnMissingBean + @Bean + public SCIMAnyObjectService scimv2AnyObjectService( + final UserDAO userDAO, + final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, + final UserLogic userLogic, + final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, + final SCIMDataBinder binder, + final SCIMConfManager confManager) { + + return new SCIMAnyObjectServiceImpl(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, + binder, confManager); } } diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java index da3038c1b6..97b561c1d7 100644 --- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java +++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/AbstractSCIMService.java @@ -29,12 +29,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.cxf.jaxrs.ext.MessageContext; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.rest.api.Preference; import org.apache.syncope.common.rest.api.RESTHeaders; import org.apache.syncope.core.logic.AbstractAnyLogic; +import org.apache.syncope.core.logic.AnyObjectLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.SCIMDataBinder; import org.apache.syncope.core.logic.UserLogic; @@ -42,10 +44,13 @@ import org.apache.syncope.core.logic.scim.SCIMConfManager; import org.apache.syncope.core.logic.scim.SearchCondConverter; import org.apache.syncope.core.logic.scim.SearchCondVisitor; import org.apache.syncope.core.persistence.api.dao.AnyDAO; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.NotFoundException; import org.apache.syncope.core.persistence.api.dao.UserDAO; +import org.apache.syncope.core.persistence.api.dao.search.AnyTypeCond; import org.apache.syncope.core.persistence.api.dao.search.OrderByClause; +import org.apache.syncope.core.persistence.api.dao.search.SearchCond; import org.apache.syncope.ext.scimv2.api.BadRequestException; import org.apache.syncope.ext.scimv2.api.data.ListResponse; import org.apache.syncope.ext.scimv2.api.data.SCIMResource; @@ -56,7 +61,7 @@ import org.apache.syncope.ext.scimv2.api.type.SortOrder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -abstract class AbstractSCIMService<R extends SCIMResource> { +public abstract class AbstractSCIMService<R extends SCIMResource> { protected static final Logger LOG = LoggerFactory.getLogger(AbstractSCIMService.class); @@ -70,10 +75,14 @@ abstract class AbstractSCIMService<R extends SCIMResource> { protected final GroupDAO groupDAO; + protected final AnyObjectDAO anyObjectDAO; + protected final UserLogic userLogic; protected final GroupLogic groupLogic; + protected final AnyObjectLogic anyObjectLogic; + protected final SCIMDataBinder binder; protected final SCIMConfManager confManager; @@ -81,42 +90,46 @@ abstract class AbstractSCIMService<R extends SCIMResource> { protected AbstractSCIMService( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager) { this.userDAO = userDAO; this.groupDAO = groupDAO; + this.anyObjectDAO = anyObjectDAO; this.userLogic = userLogic; this.groupLogic = groupLogic; + this.anyObjectLogic = anyObjectLogic; this.binder = binder; this.confManager = confManager; } - protected AnyDAO<?> anyDAO(final Resource type) { + protected AnyDAO<?> anyDAO(final String type) { switch (type) { - case User: + case "urn:ietf:params:scim:schemas:core:2.0:User": return userDAO; - case Group: + case "urn:ietf:params:scim:schemas:core:2.0:Group": return groupDAO; default: - throw new UnsupportedOperationException(); + return anyObjectDAO; } } - protected AbstractAnyLogic<?, ?, ?> anyLogic(final Resource type) { + protected AbstractAnyLogic<?, ?, ?> anyLogic(final String type) { switch (type) { - case User: + case "User": return userLogic; - case Group: + case "Group": return groupLogic; default: - throw new UnsupportedOperationException(); + return anyObjectLogic; } } @@ -153,7 +166,7 @@ abstract class AbstractSCIMService<R extends SCIMResource> { protected abstract SCIMResource getResource(String key); - protected ResponseBuilder checkETag(final Resource resource, final String key) { + protected ResponseBuilder checkETag(final String resource, final String key) { OffsetDateTime lastChange = anyDAO(resource).findLastChange(key); if (lastChange == null) { throw new NotFoundException("Resource" + key + " not found"); @@ -165,7 +178,7 @@ abstract class AbstractSCIMService<R extends SCIMResource> { @SuppressWarnings("unchecked") protected ListResponse<R> doSearch( - final Resource type, + final String type, final SCIMSearchRequest request) { if (type == null) { @@ -206,10 +219,24 @@ abstract class AbstractSCIMService<R extends SCIMResource> { sort = List.of(clause); } + SearchCond searchCond = null; + String filter = request.getFilter(); + if (!Resource.Group.name().equals(type) && !Resource.User.name().equals(type)) { + AnyTypeCond cond = new AnyTypeCond(); + cond.setAnyTypeKey(type); + searchCond = SearchCond.getLeaf(cond); + filter = filter.replaceAll( + "(\\s*(and|or)\\s+)?type\\s+eq\\s+\"[^\"]*\"(\\s*(and|or)\\s+)?", " ") + .trim().replaceAll("\\s{2,}", " "); + } + if (StringUtils.isNotBlank(filter)) { + SearchCond filterCond = SearchCondConverter.convert(visitor, filter); + searchCond = (searchCond == null) + ? filterCond + : SearchCond.getAnd(filterCond, searchCond); + } Pair<Integer, ? extends List<? extends AnyTO>> result = anyLogic(type).search( - StringUtils.isBlank(request.getFilter()) - ? null - : SearchCondConverter.convert(visitor, request.getFilter()), + searchCond, page, itemsPerPage, sort, @@ -237,6 +264,12 @@ abstract class AbstractSCIMService<R extends SCIMResource> { uriInfo.getAbsolutePathBuilder().path(anyTO.getKey()).build().toASCIIString(), request.getAttributes(), request.getExcludedAttributes()); + } else if (anyTO instanceof AnyObjectTO) { + resource = binder.toSCIMAnyObject( + (AnyObjectTO) anyTO, + uriInfo.getAbsolutePathBuilder().path(anyTO.getKey()).build().toASCIIString(), + request.getAttributes(), + request.getExcludedAttributes()); } if (resource != null) { diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMAnyObjectServiceImpl.java similarity index 57% copy from ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java copy to ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMAnyObjectServiceImpl.java index e5e9ec9d38..e226becc50 100644 --- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java +++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMAnyObjectServiceImpl.java @@ -19,86 +19,114 @@ package org.apache.syncope.ext.scimv2.cxf.service; import java.util.List; -import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.Pair; import org.apache.syncope.common.lib.AnyOperations; -import org.apache.syncope.common.lib.request.StatusR; -import org.apache.syncope.common.lib.request.UserUR; +import org.apache.syncope.common.lib.request.AnyObjectUR; +import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.ProvisioningResult; -import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.lib.types.StatusRType; +import org.apache.syncope.core.logic.AnyObjectLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.SCIMDataBinder; import org.apache.syncope.core.logic.UserLogic; import org.apache.syncope.core.logic.scim.SCIMConfManager; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.ext.scimv2.api.BadRequestException; import org.apache.syncope.ext.scimv2.api.data.ListResponse; +import org.apache.syncope.ext.scimv2.api.data.SCIMAnyObject; import org.apache.syncope.ext.scimv2.api.data.SCIMPatchOp; import org.apache.syncope.ext.scimv2.api.data.SCIMResource; import org.apache.syncope.ext.scimv2.api.data.SCIMSearchRequest; -import org.apache.syncope.ext.scimv2.api.data.SCIMUser; -import org.apache.syncope.ext.scimv2.api.service.SCIMUserService; +import org.apache.syncope.ext.scimv2.api.service.SCIMAnyObjectService; import org.apache.syncope.ext.scimv2.api.type.ErrorType; -import org.apache.syncope.ext.scimv2.api.type.Resource; import org.apache.syncope.ext.scimv2.api.type.SortOrder; -public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implements SCIMUserService { +public class SCIMAnyObjectServiceImpl extends AbstractSCIMService<SCIMAnyObject> implements SCIMAnyObjectService { - public SCIMUserServiceImpl( + public SCIMAnyObjectServiceImpl( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager) { - super(userDAO, groupDAO, userLogic, groupLogic, binder, confManager); + super(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, confManager); } @Override - public Response create(final SCIMUser user) { - ProvisioningResult<UserTO> result = userLogic.create(binder.toUserCR(user), false); + public SCIMAnyObject get(final String id, final String attributes, final String excludedAttributes) { + return binder.toSCIMAnyObject( + anyObjectLogic.read(id), + uriInfo.getAbsolutePathBuilder().build().toASCIIString(), + List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))), + List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ',')))); + } + + @Override + public ListResponse<SCIMAnyObject> search( + final String attributes, final String excludedAttributes, final String filter, final String sortBy, + final SortOrder sortOrder, final Integer startIndex, final Integer count) { + + SCIMSearchRequest request = new SCIMSearchRequest(filter, sortBy, sortOrder, startIndex, count); + if (attributes != null) { + request.getAttributes().addAll( + List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ',')))); + } + if (excludedAttributes != null) { + request.getExcludedAttributes().addAll( + List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ',')))); + } + + Matcher matcher = Pattern.compile("type\\s+eq\\s+\"(.*?)\"").matcher(filter); + if (matcher.find()) { + return doSearch(matcher.group(1), request); + } else { + throw new UnsupportedOperationException("Need to specify type"); + } + } + + @Override + public ListResponse<SCIMAnyObject> search(final SCIMSearchRequest request) { + Matcher matcher = Pattern.compile("type\\s+eq\\s+\"(.*?)\"").matcher(request.getFilter()); + if (matcher.find()) { + return doSearch(matcher.group(1), request); + } else { + throw new UnsupportedOperationException("Need to specify type"); + } + } + + @Override + public Response create(final SCIMAnyObject anyObject) { + ProvisioningResult<AnyObjectTO> result = anyObjectLogic.create(binder.toAnyObjectCR(anyObject), false); return createResponse( result.getEntity().getKey(), - binder.toSCIMUser( + binder.toSCIMAnyObject( result.getEntity(), uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(), List.of(), List.of())); } - @Override - public SCIMUser get(final String id, - final String attributes, - final String excludedAttributes) { - - return binder.toSCIMUser( - userLogic.read(id), - uriInfo.getAbsolutePathBuilder().build().toASCIIString(), - List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ','))), - List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ',')))); - } - @Override public Response update(final String id, final SCIMPatchOp patch) { - ResponseBuilder builder = checkETag(Resource.User, id); + SCIMResource resource = getResource(id); + Response.ResponseBuilder builder = checkETag( + "urn:ietf:params:scim:schemas:extension:syncope:2.0:" + resource.getSchemas().get(0), id); if (builder != null) { return builder.build(); } patch.getOperations().forEach(op -> { - Pair<UserUR, StatusR> update = binder.toUserUpdate( - userLogic.read(id), - userDAO.findAllResourceKeys(id), - op); - userLogic.update(update.getLeft(), false); - Optional.ofNullable(update.getRight()).ifPresent(statusR -> userLogic.status(statusR, false)); + AnyObjectUR update = binder.toAnyObjectUR(anyObjectLogic.read(id), op); + anyObjectLogic.update(update, false); }); return updateResponse( @@ -108,37 +136,29 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement } @Override - public Response replace(final String id, final SCIMUser user) { - if (!id.equals(user.getId())) { - throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + user.getId()); + public Response replace(final String id, final SCIMAnyObject anyObject) { + if (!id.equals(anyObject.getId())) { + throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + anyObject.getId()); } - ResponseBuilder builder = checkETag(Resource.User, id); + SCIMResource resource = getResource(id); + Response.ResponseBuilder builder = checkETag( + "urn:ietf:params:scim:schemas:extension:syncope:2.0:" + resource.getSchemas().get(0), id); if (builder != null) { return builder.build(); } - UserTO before = userLogic.read(id); + AnyObjectTO before = anyObjectLogic.read(id); - UserUR req = AnyOperations.diff(binder.toUserTO(user, true), before, false); + AnyObjectUR req = AnyOperations.diff(binder.toAnyObjectTO(anyObject, true), before, false); req.getResources().clear(); req.getAuxClasses().clear(); req.getRelationships().clear(); - req.getRoles().clear(); - req.getLinkedAccounts().clear(); - ProvisioningResult<UserTO> result = userLogic.update(req, false); - - if (before.isSuspended() == user.isActive()) { - StatusR statusR = new StatusR.Builder( - before.getKey(), - user.isActive() ? StatusRType.REACTIVATE : StatusRType.SUSPEND). - build(); - userLogic.status(statusR, false); - } + ProvisioningResult<AnyObjectTO> result = anyObjectLogic.update(req, false); return updateResponse( result.getEntity().getKey(), - binder.toSCIMUser( + binder.toSCIMAnyObject( result.getEntity(), uriInfo.getAbsolutePathBuilder().path(result.getEntity().getKey()).build().toASCIIString(), List.of(), @@ -146,51 +166,25 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement false); } - @Override - protected SCIMResource getResource(final String key) { - return binder.toSCIMUser( - userLogic.read(key), - uriInfo.getAbsolutePathBuilder().path(key).build().toASCIIString(), - List.of(), - List.of()); - } - @Override public Response delete(final String id) { - ResponseBuilder builder = checkETag(Resource.User, id); + SCIMResource resource = getResource(id); + Response.ResponseBuilder builder = checkETag( + "urn:ietf:params:scim:schemas:extension:syncope:2.0:" + resource.getSchemas().get(0), id); if (builder != null) { return builder.build(); } - anyLogic(Resource.User).delete(id, false); + anyObjectLogic.delete(id, false); return Response.noContent().build(); } @Override - public ListResponse<SCIMUser> search( - final String attributes, - final String excludedAttributes, - final String filter, - final String sortBy, - final SortOrder sortOrder, - final Integer startIndex, - final Integer count) { - - SCIMSearchRequest request = new SCIMSearchRequest(filter, sortBy, sortOrder, startIndex, count); - if (attributes != null) { - request.getAttributes().addAll( - List.of(ArrayUtils.nullToEmpty(StringUtils.split(attributes, ',')))); - } - if (excludedAttributes != null) { - request.getExcludedAttributes().addAll( - List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ',')))); - } - - return doSearch(Resource.User, request); - } - - @Override - public ListResponse<SCIMUser> search(final SCIMSearchRequest request) { - return doSearch(Resource.User, request); + protected SCIMResource getResource(final String key) { + return binder.toSCIMAnyObject( + anyObjectLogic.read(key), + uriInfo.getAbsolutePathBuilder().path(key).build().toASCIIString(), + List.of(), + List.of()); } } diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMGroupServiceImpl.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMGroupServiceImpl.java index 04e1777742..f4cd1968c2 100644 --- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMGroupServiceImpl.java +++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMGroupServiceImpl.java @@ -38,11 +38,13 @@ import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.PatchOperation; +import org.apache.syncope.core.logic.AnyObjectLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.SCIMDataBinder; import org.apache.syncope.core.logic.UserLogic; import org.apache.syncope.core.logic.scim.SCIMConfManager; import org.apache.syncope.core.persistence.api.dao.AnyDAO; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.core.persistence.api.dao.search.MembershipCond; @@ -67,12 +69,14 @@ public class SCIMGroupServiceImpl extends AbstractSCIMService<SCIMGroup> impleme public SCIMGroupServiceImpl( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager) { - super(userDAO, groupDAO, userLogic, groupLogic, binder, confManager); + super(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, confManager); } private void changeMembership(final String user, final String group, final PatchOp patchOp) { @@ -142,7 +146,7 @@ public class SCIMGroupServiceImpl extends AbstractSCIMService<SCIMGroup> impleme @Override public Response update(final String id, final SCIMPatchOp patch) { - ResponseBuilder builder = checkETag(Resource.Group, id); + ResponseBuilder builder = checkETag(Resource.Group.schema(), id); if (builder != null) { return builder.build(); } @@ -177,7 +181,7 @@ public class SCIMGroupServiceImpl extends AbstractSCIMService<SCIMGroup> impleme throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + group.getId()); } - ResponseBuilder builder = checkETag(Resource.Group, id); + ResponseBuilder builder = checkETag(Resource.Group.schema(), id); if (builder != null) { return builder.build(); } @@ -225,12 +229,12 @@ public class SCIMGroupServiceImpl extends AbstractSCIMService<SCIMGroup> impleme @Override public Response delete(final String id) { - ResponseBuilder builder = checkETag(Resource.Group, id); + ResponseBuilder builder = checkETag(Resource.Group.schema(), id); if (builder != null) { return builder.build(); } - anyLogic(Resource.Group).delete(id, false); + anyLogic(Resource.Group.name()).delete(id, false); return Response.noContent().build(); } @@ -254,11 +258,11 @@ public class SCIMGroupServiceImpl extends AbstractSCIMService<SCIMGroup> impleme List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ',')))); } - return doSearch(Resource.Group, request); + return doSearch(Resource.Group.name(), request); } @Override public ListResponse<SCIMGroup> search(final SCIMSearchRequest request) { - return doSearch(Resource.Group, request); + return doSearch(Resource.Group.name(), request); } } diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMServiceImpl.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMServiceImpl.java index 2d124aaae5..0b1a868f8e 100644 --- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMServiceImpl.java +++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMServiceImpl.java @@ -20,11 +20,13 @@ package org.apache.syncope.ext.scimv2.cxf.service; import java.util.List; import javax.ws.rs.core.Response; +import org.apache.syncope.core.logic.AnyObjectLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.SCIMDataBinder; import org.apache.syncope.core.logic.SCIMLogic; import org.apache.syncope.core.logic.UserLogic; import org.apache.syncope.core.logic.scim.SCIMConfManager; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.ext.scimv2.api.data.ResourceType; @@ -39,13 +41,15 @@ public class SCIMServiceImpl extends AbstractSCIMService<SCIMResource> implement public SCIMServiceImpl( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager, final SCIMLogic scimLogic) { - super(userDAO, groupDAO, userLogic, groupLogic, binder, confManager); + super(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, confManager); this.scimLogic = scimLogic; } diff --git a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java index e5e9ec9d38..52810c2caa 100644 --- a/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java +++ b/ext/scimv2/scim-rest-cxf/src/main/java/org/apache/syncope/ext/scimv2/cxf/service/SCIMUserServiceImpl.java @@ -31,10 +31,12 @@ import org.apache.syncope.common.lib.request.UserUR; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.UserTO; import org.apache.syncope.common.lib.types.StatusRType; +import org.apache.syncope.core.logic.AnyObjectLogic; import org.apache.syncope.core.logic.GroupLogic; import org.apache.syncope.core.logic.SCIMDataBinder; import org.apache.syncope.core.logic.UserLogic; import org.apache.syncope.core.logic.scim.SCIMConfManager; +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO; import org.apache.syncope.core.persistence.api.dao.GroupDAO; import org.apache.syncope.core.persistence.api.dao.UserDAO; import org.apache.syncope.ext.scimv2.api.BadRequestException; @@ -53,12 +55,14 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement public SCIMUserServiceImpl( final UserDAO userDAO, final GroupDAO groupDAO, + final AnyObjectDAO anyObjectDAO, final UserLogic userLogic, final GroupLogic groupLogic, + final AnyObjectLogic anyObjectLogic, final SCIMDataBinder binder, final SCIMConfManager confManager) { - super(userDAO, groupDAO, userLogic, groupLogic, binder, confManager); + super(userDAO, groupDAO, anyObjectDAO, userLogic, groupLogic, anyObjectLogic, binder, confManager); } @Override @@ -87,7 +91,7 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement @Override public Response update(final String id, final SCIMPatchOp patch) { - ResponseBuilder builder = checkETag(Resource.User, id); + ResponseBuilder builder = checkETag(Resource.User.schema(), id); if (builder != null) { return builder.build(); } @@ -113,7 +117,7 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement throw new BadRequestException(ErrorType.invalidPath, "Expected " + id + ", found " + user.getId()); } - ResponseBuilder builder = checkETag(Resource.User, id); + ResponseBuilder builder = checkETag(Resource.User.schema(), id); if (builder != null) { return builder.build(); } @@ -157,12 +161,12 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement @Override public Response delete(final String id) { - ResponseBuilder builder = checkETag(Resource.User, id); + ResponseBuilder builder = checkETag(Resource.User.schema(), id); if (builder != null) { return builder.build(); } - anyLogic(Resource.User).delete(id, false); + anyLogic(Resource.User.name()).delete(id, false); return Response.noContent().build(); } @@ -186,11 +190,11 @@ public class SCIMUserServiceImpl extends AbstractSCIMService<SCIMUser> implement List.of(ArrayUtils.nullToEmpty(StringUtils.split(excludedAttributes, ',')))); } - return doSearch(Resource.User, request); + return doSearch(Resource.User.name(), request); } @Override public ListResponse<SCIMUser> search(final SCIMSearchRequest request) { - return doSearch(Resource.User, request); + return doSearch(Resource.User.name(), request); } } 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 c9c705db93..2b7b9a2d9c 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 @@ -46,18 +46,21 @@ import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.syncope.common.lib.SyncopeConstants; +import org.apache.syncope.common.lib.request.AnyObjectUR; import org.apache.syncope.common.lib.request.GroupUR; import org.apache.syncope.common.lib.request.StringPatchItem; import org.apache.syncope.common.lib.request.UserUR; import org.apache.syncope.common.lib.scim.SCIMComplexConf; import org.apache.syncope.common.lib.scim.SCIMConf; import org.apache.syncope.common.lib.scim.SCIMEnterpriseUserConf; -import org.apache.syncope.common.lib.scim.SCIMExtensionUserConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyConf; +import org.apache.syncope.common.lib.scim.SCIMExtensionAnyObjectConf; 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; +import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.GroupTO; import org.apache.syncope.common.lib.to.ProvisioningResult; import org.apache.syncope.common.lib.to.UserTO; @@ -70,6 +73,7 @@ import org.apache.syncope.ext.scimv2.api.data.Group; import org.apache.syncope.ext.scimv2.api.data.ListResponse; 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.SCIMAnyObject; 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; @@ -118,6 +122,20 @@ public class SCIMITCase extends AbstractITCase { email.setValue("email"); email.setType(EmailCanonicalType.home); CONF.getUserConf().getEmails().add(email); + + SCIMExtensionAnyObjectConf printerConf = new SCIMExtensionAnyObjectConf(); + printerConf.setType(PRINTER); + printerConf.setName("syncope"); + printerConf.setDescription("syncope printer"); + SCIMItem model = new SCIMItem(); + model.setIntAttrName("model"); + model.setExtAttrName("model"); + SCIMItem location = new SCIMItem(); + location.setIntAttrName("location"); + location.setExtAttrName("location"); + printerConf.add(model); + printerConf.add(location); + CONF.getExtensionAnyObjectsConf().add(printerConf); } private static SCIMUser getSampleUser(final String username, final List<String> schemas) { @@ -143,6 +161,13 @@ public class SCIMITCase extends AbstractITCase { return user; } + private static SCIMGroup getSampleGroup(final String name, final List<String> schemas) { + SCIMGroup group = new SCIMGroup(null, schemas, null, name); + group.setDisplayName(name); + + return group; + } + @BeforeAll public static void isSCIMAvailable() { if (ENABLED == null) { @@ -209,7 +234,7 @@ public class SCIMITCase extends AbstractITCase { @Test public void schemas() { - SCIMExtensionUserConf extensionUserConf = new SCIMExtensionUserConf(); + SCIMExtensionAnyConf extensionUserConf = new SCIMExtensionAnyConf(); extensionUserConf.setName("syncope"); extensionUserConf.setDescription("syncope user"); SCIMItem scimItem = new SCIMItem(); @@ -217,6 +242,16 @@ public class SCIMITCase extends AbstractITCase { scimItem.setExtAttrName("gender"); extensionUserConf.add(scimItem); CONF.setExtensionUserConf(extensionUserConf); + + SCIMExtensionAnyConf extensionGroupConf = new SCIMExtensionAnyConf(); + extensionGroupConf.setName("syncope"); + extensionGroupConf.setDescription("syncope group"); + SCIMItem scimItemGroup = new SCIMItem(); + scimItemGroup.setIntAttrName("originalName"); + scimItemGroup.setExtAttrName("originalName"); + scimItemGroup.setUniqueness(true); + extensionGroupConf.add(scimItemGroup); + CONF.setExtensionGroupConf(extensionGroupConf); SCIM_CONF_SERVICE.set(CONF); Response response = webClient().path("Schemas").get(); @@ -227,7 +262,7 @@ public class SCIMITCase extends AbstractITCase { ArrayNode schemas = response.readEntity(ArrayNode.class); assertNotNull(schemas); - assertEquals(4, schemas.size()); + assertEquals(6, schemas.size()); response = webClient().path("Schemas").path("none").get(); assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); @@ -246,7 +281,23 @@ public class SCIMITCase extends AbstractITCase { assertNotNull(extensionUser); assertEquals(Resource.ExtensionUser.schema(), extensionUser.get("id").textValue()); + response = webClient().path("Schemas").path(Resource.ExtensionGroup.schema()).get(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + + ObjectNode extensionGroup = response.readEntity(ObjectNode.class); + assertNotNull(extensionGroup); + assertEquals(Resource.ExtensionGroup.schema(), extensionGroup.get("id").textValue()); + + response = webClient().path("Schemas").path("urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER").get(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + + ObjectNode extensionPrinter = response.readEntity(ObjectNode.class); + assertNotNull(extensionPrinter); + assertEquals("urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER", + extensionPrinter.get("id").textValue()); + CONF.setExtensionUserConf(null); + CONF.setExtensionGroupConf(null); SCIM_CONF_SERVICE.set(CONF); } @@ -306,7 +357,7 @@ public class SCIMITCase extends AbstractITCase { @Test void invalidConf() { - SCIMExtensionUserConf extensionUserConf = new SCIMExtensionUserConf(); + SCIMExtensionAnyConf extensionUserConf = new SCIMExtensionAnyConf(); extensionUserConf.setName("syncope"); extensionUserConf.setDescription("syncope user"); SCIMItem scimItem = new SCIMItem(); @@ -456,13 +507,13 @@ public class SCIMITCase extends AbstractITCase { assertEquals(newUser.getUsername(), newSCIMUser.getUserName()); SCIMEnterpriseUserConf beforeEntConf = CONF.getEnterpriseUserConf(); - SCIMExtensionUserConf beforeExtConf = CONF.getExtensionUserConf(); + SCIMExtensionAnyConf beforeExtConf = CONF.getExtensionUserConf(); try { SCIMEnterpriseUserConf entConf = new SCIMEnterpriseUserConf(); entConf.setOrganization("userId"); CONF.setEnterpriseUserConf(entConf); - SCIMExtensionUserConf extConf = new SCIMExtensionUserConf(); + SCIMExtensionAnyConf extConf = new SCIMExtensionAnyConf(); SCIMItem item = new SCIMItem(); item.setIntAttrName("email"); item.setExtAttrName("email"); @@ -501,6 +552,28 @@ public class SCIMITCase extends AbstractITCase { }); assertNotNull(users); assertEquals(1, users.getTotalResults()); + + // PRINTER + response = webClient().path("AnyObjects").query( + "filter", + "urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER:model eq \"Canon MFC8030\""). + get(); + assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); + + response = webClient().path("AnyObjects").query( + "filter", + "urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER:model eq \"Canon MFC8030\" " + + "and type eq \"PRINTER\""). + get(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals( + SCIMConstants.APPLICATION_SCIM_JSON, + StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); + + ListResponse<SCIMAnyObject> printers = response.readEntity(new GenericType<>() { + }); + assertNotNull(printers); + assertEquals(1, printers.getTotalResults()); } finally { CONF.setEnterpriseUserConf(beforeEntConf); CONF.setExtensionUserConf(beforeExtConf); @@ -538,7 +611,7 @@ public class SCIMITCase extends AbstractITCase { @Test void crudExtensionUser() { - SCIMExtensionUserConf extensionUserConf = new SCIMExtensionUserConf(); + SCIMExtensionAnyConf extensionUserConf = new SCIMExtensionAnyConf(); extensionUserConf.setName("syncope"); extensionUserConf.setDescription("syncope user"); SCIMItem scimItem = new SCIMItem(); @@ -792,7 +865,7 @@ public class SCIMITCase extends AbstractITCase { public void createGroup() { String displayName = UUID.randomUUID().toString(); - SCIMGroup group = new SCIMGroup(null, null, displayName); + SCIMGroup group = new SCIMGroup(null, List.of(Resource.Group.schema()), null, displayName); group.getMembers().add(new Member("1417acbe-cbf6-4277-9372-e75e04f97000", null, null)); assertNull(group.getId()); assertEquals(displayName, group.getDisplayName()); @@ -820,11 +893,51 @@ public class SCIMITCase extends AbstractITCase { assertEquals(ErrorType.uniqueness, error.getScimType()); } + @Test + void crudExtensionGroup() { + SCIMExtensionAnyConf extensionGroupConf = new SCIMExtensionAnyConf(); + extensionGroupConf.setName("syncope"); + extensionGroupConf.setDescription("syncope group"); + SCIMItem scimItemGroup = new SCIMItem(); + scimItemGroup.setIntAttrName("originalName"); + scimItemGroup.setExtAttrName("originalName"); + scimItemGroup.setUniqueness(true); + extensionGroupConf.add(scimItemGroup); + CONF.setExtensionGroupConf(extensionGroupConf); + SCIM_CONF_SERVICE.set(CONF); + + SCIMGroup group = getSampleGroup( + UUID.randomUUID().toString(), List.of(Resource.Group.schema(), Resource.ExtensionGroup.schema())); + SCIMExtensionInfo scimExtensionInfo = new SCIMExtensionInfo(); + scimExtensionInfo.getAttributes().put("originalName", "originalName"); + group.setExtensionInfo(scimExtensionInfo); + + Response response = webClient().path("Groups").post(group); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + group = response.readEntity(SCIMGroup.class); + assertNotNull(group.getId()); + assertTrue(response.getLocation().toASCIIString().endsWith(group.getId())); + + GroupTO groupTO = GROUP_SERVICE.read(group.getId()); + assertEquals(group.getDisplayName(), groupTO.getName()); + assertEquals(group.getExtensionInfo().getAttributes().get("originalName"), + groupTO.getPlainAttr("originalName").get().getValues().get(0)); + + response = webClient().path("Groups").path(group.getId()).delete(); + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + + response = webClient().path("Groups").path(group.getId()).get(); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + CONF.setExtensionGroupConf(null); + SCIM_CONF_SERVICE.set(CONF); + } + @Test public void updateGroup() { SCIM_CONF_SERVICE.set(CONF); - SCIMGroup group = new SCIMGroup(null, null, UUID.randomUUID().toString()); + SCIMGroup group = new SCIMGroup(null, List.of(Resource.Group.schema()), null, UUID.randomUUID().toString()); group.getMembers().add(new Member("74cd8ece-715a-44a4-a736-e17b46c4e7e6", null, null)); group.getMembers().add(new Member("1417acbe-cbf6-4277-9372-e75e04f97000", null, null)); Response response = webClient().path("Groups").post(group); @@ -922,7 +1035,7 @@ public class SCIMITCase extends AbstractITCase { @Test public void replaceGroup() { - SCIMGroup group = new SCIMGroup(null, null, UUID.randomUUID().toString()); + SCIMGroup group = new SCIMGroup(null, List.of(Resource.Group.schema()), null, UUID.randomUUID().toString()); group.getMembers().add(new Member("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee", null, null)); Response response = webClient().path("Groups").post(group); assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); @@ -968,7 +1081,7 @@ public class SCIMITCase extends AbstractITCase { @Test public void deleteGroup() { - SCIMGroup group = new SCIMGroup(null, null, UUID.randomUUID().toString()); + SCIMGroup group = new SCIMGroup(null, List.of(Resource.Group.schema()), null, UUID.randomUUID().toString()); Response response = webClient().path("Groups").post(group); assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); @@ -984,4 +1097,133 @@ public class SCIMITCase extends AbstractITCase { response = webClient().path("Groups").path(group.getId()).get(); assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); } + + @Test + public void createPrinter() { + SCIM_CONF_SERVICE.set(CONF); + + String displayName = UUID.randomUUID().toString(); + + SCIMAnyObject printer = new SCIMAnyObject( + null, List.of("urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER"), null, displayName); + SCIMExtensionInfo scimExtensionInfo = new SCIMExtensionInfo(); + scimExtensionInfo.getAttributes().put("model", "HP Inspire 7224e"); + printer.setExtensionInfo(scimExtensionInfo); + assertNull(printer.getId()); + assertEquals(displayName, printer.getDisplayName()); + + Response response = webClient().path("AnyObjects").post(printer); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + printer = response.readEntity(SCIMAnyObject.class); + assertNotNull(printer.getId()); + assertTrue(response.getLocation().toASCIIString().endsWith(printer.getId())); + assertEquals(displayName, printer.getDisplayName()); + assertTrue(printer.getExtensionInfo().getAttributes().containsKey("model")); + assertTrue(printer.getExtensionInfo().getAttributes().containsValue("HP Inspire 7224e")); + + response = webClient().path("AnyObjects").post(printer); + assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus()); + + SCIMError error = response.readEntity(SCIMError.class); + assertEquals(Response.Status.CONFLICT.getStatusCode(), error.getStatus()); + assertEquals(ErrorType.uniqueness, error.getScimType()); + } + + @Test + public void updatePrinter() { + SCIM_CONF_SERVICE.set(CONF); + + SCIMAnyObject printer = new SCIMAnyObject( + null, + List.of("urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER"), + null, + UUID.randomUUID().toString()); + Response response = webClient().path("AnyObjects").post(printer); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + printer = response.readEntity(SCIMAnyObject.class); + assertNotNull(printer.getId()); + assertNotNull(printer.getDisplayName()); + + // update with path, add value + String body = + "{" + + "\"schemas\":[\"urn:ietf:params:scim:api:messages:2.0:PatchOp\"]," + + "\"Operations\":[{" + + "\"op\":\"Add\"," + + "\"path\":\"displayName\"," + + "\"value\":\"" + printer.getId() + "\"" + + "}]" + + "}"; + response = webClient().path("AnyObjects").path(printer.getId()).invoke(HttpMethod.PATCH, body); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + + printer = response.readEntity(SCIMAnyObject.class); + assertEquals(printer.getId(), printer.getDisplayName()); + } + + @Test + public void replacePrinter() { + SCIM_CONF_SERVICE.set(CONF); + + SCIMAnyObject printer = new SCIMAnyObject( + null, + List.of("urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER"), + null, + UUID.randomUUID().toString()); + SCIMExtensionInfo scimExtensionInfo = new SCIMExtensionInfo(); + scimExtensionInfo.getAttributes().put("location", "1st floor"); + printer.setExtensionInfo(scimExtensionInfo); + Response response = webClient().path("AnyObjects").post(printer); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + printer = response.readEntity(SCIMAnyObject.class); + assertNotNull(printer.getId()); + + AnyObjectTO anyObjectTO = ANY_OBJECT_SERVICE.read(printer.getId()); + assertNotNull(anyObjectTO); + ANY_OBJECT_SERVICE.update(new AnyObjectUR.Builder(anyObjectTO.getKey()).resource( + new StringPatchItem.Builder().value(RESOURCE_NAME_DBSCRIPTED) + .operation(PatchOperation.ADD_REPLACE).build()) + .build()); + anyObjectTO = ANY_OBJECT_SERVICE.read(printer.getId()); + assertNotNull(anyObjectTO); + assertTrue(anyObjectTO.getResources().contains(RESOURCE_NAME_DBSCRIPTED)); + + printer.setDisplayName("other" + printer.getId()); + + response = webClient().path("AnyObjects").path(printer.getId()).put(printer); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + + printer = response.readEntity(SCIMAnyObject.class); + assertTrue(printer.getDisplayName().startsWith("other")); + + anyObjectTO = ANY_OBJECT_SERVICE.read(printer.getId()); + assertNotNull(anyObjectTO); + assertTrue(anyObjectTO.getResources().contains(RESOURCE_NAME_DBSCRIPTED)); + } + + @Test + public void deletePrinter() { + SCIMAnyObject printer = new SCIMAnyObject( + null, + List.of("urn:ietf:params:scim:schemas:extension:syncope:2.0:PRINTER"), + null, + UUID.randomUUID().toString()); + Response response = webClient().path("AnyObjects").post(printer); + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + + printer = response.readEntity(SCIMAnyObject.class); + assertNotNull(printer.getId()); + + response = webClient().path("AnyObjects").path(printer.getId()).get(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + + response = webClient().path("AnyObjects").path(printer.getId()).delete(); + assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); + + response = webClient().path("AnyObjects").path(printer.getId()).get(); + assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); + } }