From 75300ae8f55e8e4bbe7bbc153340dd4529a8f719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Wed, 2 Sep 2026 15:38:26 +0200 Subject: [PATCH 1/3] [SYNCOPE-1968] Map each SRA and WA instance to a single Domain --- .github/workflows/fit_WA_Multitenancy.yml | 45 ------ .../AuthProfileWizardBuilder.java | 3 +- .../syncope/client/console/pages/SRA.java | 3 +- .../syncope/client/console/pages/WA.java | 3 +- .../client/enduser/pages/AuthProfile.java | 3 +- .../console/SyncopeConsoleApplication.java | 4 +- .../syncope/client/console/pages/Logs.java | 7 +- .../panels/NetworkServiceDirectoryPanel.java | 19 +-- .../NetworkServiceDirectoryPanel.properties | 1 + ...NetworkServiceDirectoryPanel_it.properties | 1 + ...NetworkServiceDirectoryPanel_ja.properties | 1 + ...workServiceDirectoryPanel_pt_BR.properties | 1 + ...NetworkServiceDirectoryPanel_ru.properties | 1 + .../enduser/SyncopeEnduserApplication.java | 4 +- .../keymaster/client/api/ServiceOps.java | 11 ++ .../client/api/model/NetworkService.java | 13 ++ .../client/api/startstop/KeymasterStart.java | 14 +- .../api/startstop/KeymasterStartStop.java | 32 +++- .../client/api/startstop/KeymasterStop.java | 14 +- .../ZookeeperServiceDiscoveryOps.java | 78 ++++++--- .../client/zookeeper/ZookeeperServiceOps.java | 37 ++++- .../client/self/SelfKeymasterServiceOps.java | 26 ++- .../api/service/NetworkServiceService.java | 10 +- .../syncope/core/logic/SRARouteLogic.java | 3 +- .../syncope/core/logic/DummyServiceOps.java | 10 ++ .../syncope/core/logic/DummyServiceOps.java | 10 ++ .../api/dao/keymaster/NetworkServiceDAO.java | 2 + .../keymaster/NetworkServiceEntity.java | 4 + .../validation/NetworkServiceCheck.java | 40 +++++ .../validation/NetworkServiceValidator.java | 42 +++++ .../jpa/dao/repo/NetworkServiceRepoExt.java | 2 + .../dao/repo/NetworkServiceRepoExtImpl.java | 11 ++ .../entity/keymaster/JPANetworkService.java | 14 ++ .../neo4j/dao/repo/NetworkServiceRepoExt.java | 2 + .../dao/repo/NetworkServiceRepoExtImpl.java | 11 ++ .../entity/keymaster/Neo4jNetworkService.java | 14 ++ .../SelfKeymasterInternalServiceOps.java | 22 +++ .../service/NetworkServiceServiceImpl.java | 13 +- .../core/logic/NetworkServiceLogic.java | 39 +++-- .../syncope/fit/core/KeymasterITCase.java | 2 + .../src/main/resources/wa-embedded.properties | 2 +- .../main/resources/wa-multitenancy.properties | 17 -- .../apache/syncope/fit/AbstractITCase.java | 5 +- .../syncope/fit/MultitenancyITCase.java | 148 ------------------ .../org/apache/syncope/sra/RouteProvider.java | 5 + .../org/apache/syncope/sra/SRAProperties.java | 11 ++ .../syncope/sra/SyncopeSRAApplication.java | 10 +- sra/src/main/resources/sra.properties | 1 + .../reference-guide/concepts/domains.adoc | 4 - .../bootstrap/WABootstrapConfiguration.java | 6 +- .../syncope/wa/bootstrap/WAProperties.java | 11 ++ .../syncope/wa/bootstrap/WARestClient.java | 54 +++---- .../syncope/wa/starter/config/WAContext.java | 30 +--- .../multitenancy/WATenantsManager.java | 143 ----------------- wa/starter/src/main/resources/wa.properties | 3 +- .../test/resources/debug/wa-debug.properties | 2 +- 56 files changed, 533 insertions(+), 491 deletions(-) delete mode 100644 .github/workflows/fit_WA_Multitenancy.yml create mode 100644 core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceCheck.java create mode 100644 core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java delete mode 100644 fit/wa-reference/src/main/resources/wa-multitenancy.properties delete mode 100644 fit/wa-reference/src/test/java/org/apache/syncope/fit/MultitenancyITCase.java delete mode 100644 wa/starter/src/main/java/org/apache/syncope/wa/starter/multitenancy/WATenantsManager.java diff --git a/.github/workflows/fit_WA_Multitenancy.yml b/.github/workflows/fit_WA_Multitenancy.yml deleted file mode 100644 index b05a1776cc8..00000000000 --- a/.github/workflows/fit_WA_Multitenancy.yml +++ /dev/null @@ -1,45 +0,0 @@ -# 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 -name: "FIT WA Multitenancy" - -on: - push: - branches: [master] - pull_request: - # The branches below must be a subset of the branches above - branches: [master] - -jobs: - fit_WA_Multitenancy: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v7.0.1 - - name: Setup Java JDK - uses: actions/setup-java@v6 - with: - distribution: 'temurin' - java-version: 25 - - name: Setup Maven - uses: stCarolas/setup-maven@v5.1 - with: - maven-version: 3.9.6 - - name: Build - run: mvn -U -T 1C -P 'skipTests,all' - - name: 'WA / Multitenancy' - run: mvn -f fit/wa-reference/pom.xml verify -Dinvoker.streamLogs=true -Dmodernizer.skip=true -Drat.skip=true -Dcheckstyle.skip=true -Djacoco.skip=true -Dspring.profiles.active=embedded,https,all,multitenancy -Dit.test=MultitenancyITCase diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java index 44c53dc93ee..10440b912dd 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/authprofiles/AuthProfileWizardBuilder.java @@ -20,6 +20,7 @@ import java.util.List; import org.apache.commons.lang3.SerializationUtils; +import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.panels.BeanPanel; import org.apache.syncope.client.console.rest.AuthProfileRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.JsonEditorPanel; @@ -104,7 +105,7 @@ protected class ConsentAttributes extends WizardStep { String attributes = "{}"; try { attributes = authProfileRestClient.readConsentAttributes( - serviceOps.get(NetworkService.Type.WA), + serviceOps.get(NetworkService.Type.WA, SyncopeConsoleSession.get().getDomain()), consentDecision.getPrincipal(), consentDecision.getId()); } catch (Exception e) { diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/SRA.java b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/SRA.java index 3904aff8f78..86b66beedca 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/SRA.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/SRA.java @@ -60,7 +60,8 @@ public SRA(final PageParameters parameters) { body.add(BookmarkablePageLinkBuilder.build("dashboard", "dashboardBr", Dashboard.class)); body.setOutputMarkupId(true); - List instances = serviceOps.list(NetworkService.Type.SRA); + List instances = serviceOps.list( + NetworkService.Type.SRA, SyncopeConsoleSession.get().getDomain()); AjaxLink push = new AjaxLink<>("push") { diff --git a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java index b16398bc177..d2bf20b89ec 100644 --- a/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java +++ b/client/am/console/src/main/java/org/apache/syncope/client/console/pages/WA.java @@ -110,7 +110,8 @@ public WA(final PageParameters parameters) { body.add(BookmarkablePageLinkBuilder.build("dashboard", "dashboardBr", Dashboard.class)); body.setOutputMarkupId(true); - List instances = serviceOps.list(NetworkService.Type.WA); + List instances = serviceOps.list( + NetworkService.Type.WA, SyncopeConsoleSession.get().getDomain()); modal = new BaseModal<>("push-modal"); modal.setWindowClosedCallback(target -> modal.show(false)); diff --git a/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java index 8f752e7fa3b..191126e799f 100644 --- a/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java +++ b/client/am/enduser/src/main/java/org/apache/syncope/client/enduser/pages/AuthProfile.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.stream.Collectors; +import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.rest.AuthProfileRestClient; import org.apache.syncope.client.ui.commons.Constants; import org.apache.syncope.client.ui.commons.annotations.AMPage; @@ -237,7 +238,7 @@ public void populateItem(final Item item) { String attributes = "{}"; try { attributes = restClient.readConsentAttributes( - serviceOps.get(NetworkService.Type.WA), + serviceOps.get(NetworkService.Type.WA, SyncopeEnduserSession.get().getDomain()), item.getModelObject().getPrincipal(), item.getModelObject().getId()); } catch (Exception e) { diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java index d58f35f5e82..da0bad2f3a3 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java @@ -125,13 +125,13 @@ public SyncopeCoreHealthIndicator syncopeCoreHealthIndicator( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean public KeymasterStart keymasterStart() { - return new KeymasterStart(NetworkService.Type.CONSOLE); + return new KeymasterStart.Builder(NetworkService.Type.CONSOLE).build(); } @ConditionalOnProperty( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean public KeymasterStop keymasterStop() { - return new KeymasterStop(NetworkService.Type.CONSOLE); + return new KeymasterStop.Builder(NetworkService.Type.CONSOLE).build(); } } diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Logs.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Logs.java index d9d96d21acf..edf03993675 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Logs.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/pages/Logs.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.stream.Collectors; import org.apache.syncope.client.console.BookmarkablePageLinkBuilder; +import org.apache.syncope.client.console.SyncopeConsoleSession; import org.apache.syncope.client.console.panels.LogsPanel; import org.apache.syncope.client.console.rest.LoggerConf; import org.apache.syncope.client.console.rest.LoggerConfOp; @@ -123,7 +124,8 @@ public Panel getPanel(final String panelId) { }); } - List waInstances = serviceOps.list(NetworkService.Type.WA); + List waInstances = serviceOps.list( + NetworkService.Type.WA, SyncopeConsoleSession.get().getDomain()); if (!waInstances.isEmpty()) { tabs.add(new AbstractTab(Model.of(NetworkService.Type.WA.name())) { @@ -136,7 +138,8 @@ public Panel getPanel(final String panelId) { }); } - List sraInstances = serviceOps.list(NetworkService.Type.SRA); + List sraInstances = serviceOps.list( + NetworkService.Type.SRA, SyncopeConsoleSession.get().getDomain()); if (!sraInstances.isEmpty()) { tabs.add(new AbstractTab(Model.of(NetworkService.Type.SRA.name())) { diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.java index 2e2051bb89b..5b29dd7c899 100644 --- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.java +++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.java @@ -40,6 +40,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.spring.injection.annot.SpringBean; @@ -58,12 +59,10 @@ public NetworkServiceDirectoryPanel( final NetworkService.Type type, final SyncopeRestClient syncopeRestClient, final PageReference pageRef) { + super(id, syncopeRestClient, pageRef, true); this.type = type; - NetworkService service = new NetworkService(); - service.setType(type); - disableCheckBoxes(); modal.size(Modal.Size.Large); @@ -80,6 +79,10 @@ protected List> getColumns() { columns.add(new PropertyColumn<>(new StringResourceModel("address", this), "address", "address")); + if (type == NetworkService.Type.SRA || type == NetworkService.Type.WA) { + columns.add(new PropertyColumn<>(new StringResourceModel("domain", this), "domain", "domain")); + } + return columns; } @@ -145,15 +148,7 @@ public long size() { @Override public IModel model(final NetworkService service) { - return new IModel<>() { - - private static final long serialVersionUID = 999513782683391483L; - - @Override - public NetworkService getObject() { - return service; - } - }; + return Model.of(service); } } } diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.properties index e0ee5575e30..28fb44a689a 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel.properties @@ -17,3 +17,4 @@ any.edit=Edit implementation ${key} any.new=New ${type} implementation address=Address +domain=Domain diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_it.properties index 4ef085aba88..c17bb07cf32 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_it.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_it.properties @@ -17,3 +17,4 @@ any.edit=Modifica implementazione ${key} any.new=Nuova implementazione di ${type} address=Indirizzo +domain=Dominio diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ja.properties index 292bdb74ddf..3df7b49a8cb 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ja.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ja.properties @@ -17,3 +17,4 @@ any.edit=\u5c0e\u5165 ${key} \u3092\u7de8\u96c6 any.new=\u65b0\u3057\u3044 ${type} \u5c0e\u5165 address=\u30a2\u30c9\u30ec\u30b9 +domain=Domain diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_pt_BR.properties index e0ee5575e30..28fb44a689a 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_pt_BR.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_pt_BR.properties @@ -17,3 +17,4 @@ any.edit=Edit implementation ${key} any.new=New ${type} implementation address=Address +domain=Domain diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ru.properties index ad0f9f648f2..d86b76433b6 100644 --- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ru.properties +++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/panels/NetworkServiceDirectoryPanel_ru.properties @@ -18,3 +18,4 @@ any.edit=Edit implementation ${key} any.new=New ${type} implementation address=Address +domain=Domain diff --git a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java index 21ef0d75c17..998e343a509 100644 --- a/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java +++ b/client/idrepo/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java @@ -86,13 +86,13 @@ public SyncopeCoreHealthIndicator syncopeCoreHealthIndicator( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean public KeymasterStart keymasterStart() { - return new KeymasterStart(NetworkService.Type.ENDUSER); + return new KeymasterStart.Builder(NetworkService.Type.ENDUSER).build(); } @ConditionalOnProperty( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean public KeymasterStop keymasterStop() { - return new KeymasterStop(NetworkService.Type.ENDUSER); + return new KeymasterStop.Builder(NetworkService.Type.ENDUSER).build(); } } diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/ServiceOps.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/ServiceOps.java index 1499bf341a6..739c02609ee 100644 --- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/ServiceOps.java +++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/ServiceOps.java @@ -32,6 +32,8 @@ public interface ServiceOps { List list(NetworkService.Type serviceType); + List list(NetworkService.Type serviceType, String domain); + /** * Returns the service instance to invoke, for the given type. * @@ -39,4 +41,13 @@ public interface ServiceOps { * @return service instance to invoke, for the given type */ NetworkService get(NetworkService.Type serviceType); + + /** + * Returns the service instance to invoke, for the given type and domain. + * + * @param serviceType service type + * @param domain domain + * @return service instance to invoke, for the given type and domain + */ + NetworkService get(NetworkService.Type serviceType, String domain); } diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/NetworkService.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/NetworkService.java index 42579d9d0fa..207d3343b2c 100644 --- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/NetworkService.java +++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/model/NetworkService.java @@ -39,6 +39,8 @@ public enum Type { private String address; + private String domain; + public Type getType() { return type; } @@ -55,11 +57,20 @@ public void setAddress(final String address) { this.address = address; } + public String getDomain() { + return domain; + } + + public void setDomain(final String domain) { + this.domain = domain; + } + @Override public int hashCode() { return new HashCodeBuilder(). append(type). append(address). + append(domain). build(); } @@ -78,6 +89,7 @@ public boolean equals(final Object obj) { return new EqualsBuilder(). append(type, other.type). append(address, other.address). + append(domain, other.domain). build(); } @@ -86,6 +98,7 @@ public String toString() { return "NetworkService{" + "type=" + type + ", address=" + address + + ", domain=" + domain + '}'; } } diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java index abbf86d4b90..91e46834999 100644 --- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java +++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStart.java @@ -24,7 +24,19 @@ public class KeymasterStart extends KeymasterStartStop implements ApplicationListener { - public KeymasterStart(final NetworkService.Type networkServiceType) { + public static class Builder extends KeymasterStartStop.Builder { + + public Builder(final NetworkService.Type networkServiceType) { + super(networkServiceType); + } + + @Override + protected KeymasterStart newInstance(final NetworkService.Type networkServiceType) { + return new KeymasterStart(networkServiceType); + } + } + + protected KeymasterStart(final NetworkService.Type networkServiceType) { super(networkServiceType); } diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java index 29a161b795b..c3a5dc38141 100644 --- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java +++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStartStop.java @@ -25,22 +25,50 @@ abstract class KeymasterStartStop { + protected abstract static class Builder> { + + protected final S instance; + + Builder(final NetworkService.Type networkServiceType) { + instance = newInstance(networkServiceType); + } + + protected abstract S newInstance(NetworkService.Type networkServiceType); + + @SuppressWarnings("unchecked") + public B domain(final String domain) { + instance.setDomain(domain); + return (B) this; + } + + public S build() { + return instance; + } + } + @Autowired protected ServiceOps serviceOps; protected final NetworkService.Type networkServiceType; + private String domain; + + @Value("${service.discovery.address}") + private String address; + protected KeymasterStartStop(final NetworkService.Type networkServiceType) { this.networkServiceType = networkServiceType; } - @Value("${service.discovery.address}") - private String address; + protected void setDomain(final String domain) { + this.domain = domain; + } protected NetworkService getNetworkService() { NetworkService ns = new NetworkService(); ns.setType(networkServiceType); ns.setAddress(address); + ns.setDomain(domain); return ns; } } diff --git a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java index 81270569a78..f7632f5d4ef 100644 --- a/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java +++ b/common/keymaster/client-api/src/main/java/org/apache/syncope/common/keymaster/client/api/startstop/KeymasterStop.java @@ -24,7 +24,19 @@ public class KeymasterStop extends KeymasterStartStop implements ApplicationListener { - public KeymasterStop(final NetworkService.Type networkServiceType) { + public static class Builder extends KeymasterStartStop.Builder { + + public Builder(final NetworkService.Type networkServiceType) { + super(networkServiceType); + } + + @Override + protected KeymasterStop newInstance(final NetworkService.Type networkServiceType) { + return new KeymasterStop(networkServiceType); + } + } + + protected KeymasterStop(final NetworkService.Type networkServiceType) { super(networkServiceType); } diff --git a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceDiscoveryOps.java b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceDiscoveryOps.java index 26183987101..2cf6ecd0d5e 100644 --- a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceDiscoveryOps.java +++ b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceDiscoveryOps.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.x.discovery.ServiceDiscovery; import org.apache.curator.x.discovery.ServiceDiscoveryBuilder; @@ -44,26 +43,37 @@ public class ZookeeperServiceDiscoveryOps implements ServiceOps, InitializingBea private static final String SERVICE_PATH = "/services"; - private final Map> providers = new ConcurrentHashMap<>(); + protected static NetworkService toNetworkService( + final NetworkService.Type serviceType, + final ServiceInstance serviceInstance) { + + NetworkService ns = new NetworkService(); + ns.setType(serviceType); + ns.setAddress(serviceInstance.getAddress()); + ns.setDomain(serviceInstance.getPayload()); + return ns; + } + + private final Map> providers = new ConcurrentHashMap<>(); @Autowired private CuratorFramework client; - private ServiceDiscovery discovery; + private ServiceDiscovery discovery; @Override public void afterPropertiesSet() throws Exception { - discovery = ServiceDiscoveryBuilder.builder(Void.class). + discovery = ServiceDiscoveryBuilder.builder(String.class). client(client). basePath(SERVICE_PATH). build(); discovery.start(); } - private ServiceProvider getProvider(final NetworkService.Type type) { + private ServiceProvider getProvider(final NetworkService.Type type) { return providers.computeIfAbsent(type, t -> { try { - ServiceProvider provider = discovery. + ServiceProvider provider = discovery. serviceProviderBuilder(). serviceName(t.name()).build(); provider.start(); @@ -81,9 +91,10 @@ public void register(final NetworkService service) { try { unregister(service); - ServiceInstance instance = ServiceInstance.builder(). + ServiceInstance instance = ServiceInstance.builder(). name(service.getType().name()). address(service.getAddress()). + payload(service.getDomain()). build(); discovery.registerService(instance); } catch (KeymasterException e) { @@ -99,7 +110,7 @@ public void unregister(final NetworkService service) { try { discovery.queryForInstances(service.getType().name()).stream(). filter(instance -> instance.getName().equals(service.getType().name()) - && instance.getAddress().equals(service.getAddress())).findFirst(). + && instance.getAddress().equals(service.getAddress())).findFirst(). ifPresent(instance -> { try { discovery.unregisterService(instance); @@ -116,22 +127,26 @@ public void unregister(final NetworkService service) { } } - private static NetworkService toNetworkService( - final NetworkService.Type serviceType, - final ServiceInstance serviceInstance) { - - NetworkService ns = new NetworkService(); - ns.setType(serviceType); - ns.setAddress(serviceInstance.getAddress()); - return ns; + @Override + public List list(final NetworkService.Type serviceType) { + try { + return discovery.queryForInstances(serviceType.name()).stream(). + map(serviceInstance -> toNetworkService(serviceType, serviceInstance)). + toList(); + } catch (KeymasterException e) { + throw e; + } catch (Exception e) { + throw new KeymasterException(e); + } } @Override - public List list(final NetworkService.Type serviceType) { + public List list(final NetworkService.Type serviceType, final String domain) { try { return discovery.queryForInstances(serviceType.name()).stream(). + filter(serviceInstance -> domain.equals(serviceInstance.getPayload())). map(serviceInstance -> toNetworkService(serviceType, serviceInstance)). - collect(Collectors.toList()); + toList(); } catch (KeymasterException e) { throw e; } catch (Exception e) { @@ -141,11 +156,30 @@ public List list(final NetworkService.Type serviceType) { @Override public NetworkService get(final NetworkService.Type serviceType) { - ServiceInstance serviceInstance = null; + ServiceInstance serviceInstance = null; try { - if (!discovery.queryForInstances(serviceType.name()).isEmpty()) { - serviceInstance = getProvider(serviceType).getInstance(); - } + serviceInstance = discovery.queryForInstances(serviceType.name()).stream(). + findFirst(). + orElse(null); + } catch (KeymasterException e) { + throw e; + } catch (Exception e) { + throw new KeymasterException(e); + } + + if (serviceInstance == null) { + throw new KeymasterException("No services found for " + serviceType); + } + return toNetworkService(serviceType, serviceInstance); + } + + @Override + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + ServiceInstance serviceInstance = null; + try { + serviceInstance = discovery.queryForInstances(serviceType.name()).stream(). + filter(s -> domain.equals(s.getPayload())).findFirst(). + orElse(null); } catch (KeymasterException e) { throw e; } catch (Exception e) { diff --git a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceOps.java b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceOps.java index 4057e8c667b..1769062358b 100644 --- a/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceOps.java +++ b/common/keymaster/client-zookeeper/src/main/java/org/apache/syncope/common/keymaster/client/zookeeper/ZookeeperServiceOps.java @@ -115,7 +115,8 @@ public List list(final NetworkService.Type serviceType) { List list = new ArrayList<>(); for (String child : client.getChildren().forPath(buildServicePath(serviceType))) { - list.add(MAPPER.readValue(client.getData().forPath(buildServicePath(serviceType, child)), + list.add(MAPPER.readValue( + client.getData().forPath(buildServicePath(serviceType, child)), NetworkService.class)); } @@ -125,6 +126,29 @@ public List list(final NetworkService.Type serviceType) { } } + @Override + public List list(final NetworkService.Type serviceType, final String domain) { + try { + if (client.checkExists().forPath(buildServicePath(serviceType)) == null) { + client.create().creatingParentContainersIfNeeded().forPath(buildServicePath(serviceType)); + } + + List list = new ArrayList<>(); + for (String child : client.getChildren().forPath(buildServicePath(serviceType))) { + NetworkService service = MAPPER.readValue( + client.getData().forPath(buildServicePath(serviceType, child)), + NetworkService.class); + if (domain.equals(service.getDomain())) { + list.add(service); + } + } + + return list; + } catch (Exception e) { + throw new KeymasterException(e); + } + } + @Override public NetworkService get(final NetworkService.Type serviceType) { List list = list(serviceType); @@ -135,4 +159,15 @@ public NetworkService get(final NetworkService.Type serviceType) { // always returns first instance, can be improved return list.getFirst(); } + + @Override + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + List list = list(serviceType, domain); + if (list.isEmpty()) { + throw new KeymasterException("No registered services for type " + serviceType + " and domain " + domain); + } + + // always returns first instance, can be improved + return list.getFirst(); + } } diff --git a/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterServiceOps.java b/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterServiceOps.java index 394bfccb8bb..e3e023680d2 100644 --- a/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterServiceOps.java +++ b/common/keymaster/self/client-self/src/main/java/org/apache/syncope/common/keymaster/client/self/SelfKeymasterServiceOps.java @@ -53,7 +53,18 @@ public SelfKeymasterServiceOps(final JAXRSClientFactoryBean clientFactory, final @Override public List list(final NetworkService.Type serviceType) { try { - return client(NetworkServiceService.class, Map.of()).list(serviceType); + return client(NetworkServiceService.class, Map.of()).list(serviceType, null); + } catch (KeymasterException e) { + throw e; + } catch (Exception e) { + throw new KeymasterException(e); + } + } + + @Override + public List list(final NetworkService.Type serviceType, final String domain) { + try { + return client(NetworkServiceService.class, Map.of()).list(serviceType, domain); } catch (KeymasterException e) { throw e; } catch (Exception e) { @@ -64,7 +75,18 @@ public List list(final NetworkService.Type serviceType) { @Override public NetworkService get(final NetworkService.Type serviceType) { try { - return client(NetworkServiceService.class, Map.of()).get(serviceType); + return client(NetworkServiceService.class, Map.of()).get(serviceType, null); + } catch (KeymasterException e) { + throw e; + } catch (Exception e) { + throw new KeymasterException(e); + } + } + + @Override + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + try { + return client(NetworkServiceService.class, Map.of()).get(serviceType, domain); } catch (KeymasterException e) { throw e; } catch (Exception e) { diff --git a/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/NetworkServiceService.java b/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/NetworkServiceService.java index 2a52f8a55e3..1abb9efedbe 100644 --- a/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/NetworkServiceService.java +++ b/common/keymaster/self/rest-api/src/main/java/org/apache/syncope/common/keymaster/rest/api/service/NetworkServiceService.java @@ -55,23 +55,29 @@ enum Action { * Returns the list of registered services. * * @param serviceType service type + * @param domain optional domain * @return list of registered services */ @GET @Path("{serviceType}") @Produces({ MediaType.APPLICATION_JSON }) - List list(@NotNull @PathParam("serviceType") NetworkService.Type serviceType); + List list( + @NotNull @PathParam("serviceType") NetworkService.Type serviceType, + @QueryParam("domain") String domain); /** * Returns the service instance to invoke, for the given type. * * @param serviceType service type + * @param domain optional domain * @return service instance to invoke, for the given type */ @GET @Path("{serviceType}/get") @Produces({ MediaType.APPLICATION_JSON }) - NetworkService get(@NotNull @PathParam("serviceType") NetworkService.Type serviceType); + NetworkService get( + @NotNull @PathParam("serviceType") NetworkService.Type serviceType, + @QueryParam("domain") String domain); /** * (Un)registers the given service. diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SRARouteLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SRARouteLogic.java index 99dbdd118dc..b60b1290a45 100644 --- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SRARouteLogic.java +++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SRARouteLogic.java @@ -38,6 +38,7 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory; import org.apache.syncope.core.persistence.api.entity.SRARoute; import org.apache.syncope.core.provisioning.api.data.SRARouteDataBinder; +import org.apache.syncope.core.spring.security.AuthContextUtils; import org.apache.syncope.core.spring.security.SecurityProperties; import org.springframework.security.access.prepost.PreAuthorize; @@ -112,7 +113,7 @@ public SRARouteTO delete(final String key) { public void pushToSRA() { HttpClient client = HttpClient.newHttpClient(); try { - serviceOps.list(NetworkService.Type.SRA).forEach(sra -> client.sendAsync( + serviceOps.list(NetworkService.Type.SRA, AuthContextUtils.getDomain()).forEach(sra -> client.sendAsync( HttpRequest.newBuilder(URI.create( Strings.CS.appendIfMissing(sra.getAddress(), "/") + "actuator/gateway/refresh")). header(HttpHeaders.AUTHORIZATION, DefaultBasicAuthSupplier.getBasicAuthHeader( diff --git a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java index efdedf03b99..5a9661fb46b 100644 --- a/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java +++ b/core/idm/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java @@ -39,8 +39,18 @@ public List list(final NetworkService.Type serviceType) { return List.of(); } + @Override + public List list(final NetworkService.Type serviceType, final String domain) { + return List.of(); + } + @Override public NetworkService get(final NetworkService.Type serviceType) { return null; } + + @Override + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + return null; + } } diff --git a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java index efdedf03b99..5a9661fb46b 100644 --- a/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java +++ b/core/idrepo/logic/src/test/java/org/apache/syncope/core/logic/DummyServiceOps.java @@ -39,8 +39,18 @@ public List list(final NetworkService.Type serviceType) { return List.of(); } + @Override + public List list(final NetworkService.Type serviceType, final String domain) { + return List.of(); + } + @Override public NetworkService get(final NetworkService.Type serviceType) { return null; } + + @Override + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + return null; + } } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/keymaster/NetworkServiceDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/keymaster/NetworkServiceDAO.java index 0334abfa14a..7a4b3b01222 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/keymaster/NetworkServiceDAO.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/keymaster/NetworkServiceDAO.java @@ -27,5 +27,7 @@ public interface NetworkServiceDAO extends DAO { List findAll(NetworkService.Type serviceType); + List findAll(NetworkService.Type serviceType, String domain); + void deleteAll(NetworkService service); } diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/keymaster/NetworkServiceEntity.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/keymaster/NetworkServiceEntity.java index ff9959697b3..46696682029 100644 --- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/keymaster/NetworkServiceEntity.java +++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/keymaster/NetworkServiceEntity.java @@ -30,4 +30,8 @@ public interface NetworkServiceEntity extends Entity { String getAddress(); void setAddress(String address); + + String getDomain(); + + void setDomain(String domain); } diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceCheck.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceCheck.java new file mode 100644 index 00000000000..7a0ea76ff5b --- /dev/null +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceCheck.java @@ -0,0 +1,40 @@ +/* + * 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.core.persistence.common.validation; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = NetworkServiceValidator.class) +@Documented +public @interface NetworkServiceCheck { + + String message() default "{org.apache.syncope.core.persistence.validation.service}"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java new file mode 100644 index 00000000000..ca17367ecab --- /dev/null +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java @@ -0,0 +1,42 @@ +/* + * 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.core.persistence.common.validation; + +import jakarta.validation.ConstraintValidatorContext; +import org.apache.syncope.common.keymaster.client.api.model.NetworkService; +import org.apache.syncope.common.lib.types.EntityViolationType; + +public class NetworkServiceValidator extends AbstractValidator { + + @Override + public boolean isValid(final NetworkService service, final ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + + if ((service.getType() == NetworkService.Type.SRA || service.getType() == NetworkService.Type.WA) + && service.getDomain() == null) { + + context.buildConstraintViolationWithTemplate( + getTemplate(EntityViolationType.Standard, "Domain must be set for SRA and WA services")). + addPropertyNode("domain").addConstraintViolation(); + return false; + } + + return true; + } +} diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExt.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExt.java index 25bcf4ebf70..264c29788f9 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExt.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExt.java @@ -26,5 +26,7 @@ public interface NetworkServiceRepoExt { List findAll(NetworkService.Type serviceType); + List findAll(NetworkService.Type serviceType, String domain); + void deleteAll(NetworkService service); } diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExtImpl.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExtImpl.java index 8f4b2800223..0bc6d4a13df 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExtImpl.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/repo/NetworkServiceRepoExtImpl.java @@ -45,6 +45,17 @@ public List findAll(final NetworkService.Type serviceType) return query.getResultList(); } + @Transactional(readOnly = true) + @Override + public List findAll(final NetworkService.Type serviceType, final String domain) { + TypedQuery query = entityManager.createQuery( + "SELECT e FROM " + JPANetworkService.class.getSimpleName() + + " e WHERE e.type=:serviceType AND e.domain=:domain", NetworkServiceEntity.class); + query.setParameter("serviceType", serviceType); + query.setParameter("domain", domain); + return query.getResultList(); + } + @Override public void deleteAll(final NetworkService service) { Query query = entityManager.createQuery( diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/keymaster/JPANetworkService.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/keymaster/JPANetworkService.java index a1ddf5ae03d..cef7c083faf 100644 --- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/keymaster/JPANetworkService.java +++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/keymaster/JPANetworkService.java @@ -26,11 +26,13 @@ import jakarta.validation.constraints.NotNull; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.core.persistence.api.entity.keymaster.NetworkServiceEntity; +import org.apache.syncope.core.persistence.common.validation.NetworkServiceCheck; import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity; @Entity @Table(name = JPANetworkService.TABLE, uniqueConstraints = @UniqueConstraint(columnNames = { "type", "address" })) +@NetworkServiceCheck public class JPANetworkService extends AbstractGeneratedKeyEntity implements NetworkServiceEntity { private static final long serialVersionUID = 8742750097008236475L; @@ -44,6 +46,8 @@ public class JPANetworkService extends AbstractGeneratedKeyEntity implements Net @NotNull private String address; + private String domain; + @Override public NetworkService.Type getType() { return type; @@ -63,4 +67,14 @@ public String getAddress() { public void setAddress(final String address) { this.address = address; } + + @Override + public String getDomain() { + return domain; + } + + @Override + public void setDomain(final String domain) { + this.domain = domain; + } } diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExt.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExt.java index 331a258062c..5bba82679d2 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExt.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExt.java @@ -27,6 +27,8 @@ public interface NetworkServiceRepoExt { List findAll(NetworkService.Type serviceType); + List findAll(NetworkService.Type serviceType, String domain); + S save(S service); void deleteAll(NetworkService service); diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExtImpl.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExtImpl.java index d519e107d9f..936b12eaa35 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExtImpl.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/dao/repo/NetworkServiceRepoExtImpl.java @@ -48,6 +48,17 @@ public List findAll(final NetworkService.Type serviceType) Neo4jNetworkService.class).stream().map(NetworkServiceEntity.class::cast).toList(); } + @Transactional(readOnly = true) + @Override + public List findAll(final NetworkService.Type serviceType, final String domain) { + return neo4jTemplate.findAll( + "MATCH (n:" + Neo4jNetworkService.NODE + ") " + + "WHERE n.type = $serviceType AND n.domain = $ domain " + + "RETURN n", + Map.of("serviceType", serviceType.name(), "domain", domain), + Neo4jNetworkService.class).stream().map(NetworkServiceEntity.class::cast).toList(); + } + @Override public S save(final S service) { if (findAll(service.getType()).stream().anyMatch(s -> s.getAddress().equals(service.getAddress()))) { diff --git a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/keymaster/Neo4jNetworkService.java b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/keymaster/Neo4jNetworkService.java index ec1e24e6aba..48219f384ab 100644 --- a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/keymaster/Neo4jNetworkService.java +++ b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/keymaster/Neo4jNetworkService.java @@ -21,10 +21,12 @@ import jakarta.validation.constraints.NotNull; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.core.persistence.api.entity.keymaster.NetworkServiceEntity; +import org.apache.syncope.core.persistence.common.validation.NetworkServiceCheck; import org.apache.syncope.core.persistence.neo4j.entity.AbstractGeneratedKeyNode; import org.springframework.data.neo4j.core.schema.Node; @Node(Neo4jNetworkService.NODE) +@NetworkServiceCheck public class Neo4jNetworkService extends AbstractGeneratedKeyNode implements NetworkServiceEntity { private static final long serialVersionUID = 8742750097008236475L; @@ -37,6 +39,8 @@ public class Neo4jNetworkService extends AbstractGeneratedKeyNode implements Net @NotNull private String address; + private String domain; + @Override public NetworkService.Type getType() { return type; @@ -56,4 +60,14 @@ public String getAddress() { public void setAddress(final String address) { this.address = address; } + + @Override + public String getDomain() { + return domain; + } + + @Override + public void setDomain(final String domain) { + this.domain = domain; + } } diff --git a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalServiceOps.java b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalServiceOps.java index a6b76a865da..03a0ee4ed0b 100644 --- a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalServiceOps.java +++ b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/internal/SelfKeymasterInternalServiceOps.java @@ -47,6 +47,15 @@ public List list(final NetworkService.Type serviceType) { () -> logic.list(serviceType)); } + @Override + public List list(final NetworkService.Type serviceType, final String domain) { + return AuthContextUtils.callAs( + SyncopeConstants.MASTER_DOMAIN, + props.getUsername(), + List.of(), + () -> logic.list(serviceType, domain)); + } + @Override public NetworkService get(final NetworkService.Type serviceType) { try { @@ -60,6 +69,19 @@ public NetworkService get(final NetworkService.Type serviceType) { } } + @Override + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + try { + return AuthContextUtils.callAs( + SyncopeConstants.MASTER_DOMAIN, + props.getUsername(), + List.of(), + () -> logic.get(serviceType, domain)); + } catch (Exception e) { + throw new KeymasterException(e); + } + } + @Override public void register(final NetworkService service) { AuthContextUtils.callAs( diff --git a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/NetworkServiceServiceImpl.java b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/NetworkServiceServiceImpl.java index c5cd51b1dea..9bfcaa5cb07 100644 --- a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/NetworkServiceServiceImpl.java +++ b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/keymaster/rest/cxf/service/NetworkServiceServiceImpl.java @@ -19,6 +19,7 @@ package org.apache.syncope.core.keymaster.rest.cxf.service; import java.util.List; +import java.util.Optional; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.common.keymaster.rest.api.service.NetworkServiceService; import org.apache.syncope.core.logic.NetworkServiceLogic; @@ -34,13 +35,17 @@ public NetworkServiceServiceImpl(final NetworkServiceLogic logic) { } @Override - public List list(final NetworkService.Type serviceType) { - return logic.list(serviceType); + public List list(final NetworkService.Type serviceType, final String domain) { + return Optional.ofNullable(domain). + map(d -> logic.list(serviceType, d)). + orElseGet(() -> logic.list(serviceType)); } @Override - public NetworkService get(final NetworkService.Type serviceType) { - return logic.get(serviceType); + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + return Optional.ofNullable(domain). + map(d -> logic.get(serviceType, d)). + orElseGet(() -> logic.get(serviceType)); } @Override diff --git a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java index 094f1cde38e..f06e540f38d 100644 --- a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java +++ b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java @@ -32,6 +32,14 @@ public class NetworkServiceLogic extends AbstractTransactionalLogic { + protected static NetworkService toNetworkService(final NetworkServiceEntity service) { + NetworkService ns = new NetworkService(); + ns.setType(ns.getType()); + ns.setAddress(service.getAddress()); + ns.setDomain(service.getDomain()); + return ns; + } + protected final NetworkServiceDAO serviceDAO; protected final EntityFactory entityFactory; @@ -41,21 +49,16 @@ public NetworkServiceLogic(final NetworkServiceDAO serviceDAO, final EntityFacto this.entityFactory = entityFactory; } - protected NetworkService toNetworkService( - final NetworkService.Type serviceType, - final NetworkServiceEntity service) { - - NetworkService ns = new NetworkService(); - ns.setType(serviceType); - ns.setAddress(service.getAddress()); - return ns; + @PreAuthorize("@environment.getProperty('keymaster.username') == authentication.name") + @Transactional(readOnly = true) + public List list(final NetworkService.Type serviceType) { + return serviceDAO.findAll(serviceType).stream().map(NetworkServiceLogic::toNetworkService).toList(); } @PreAuthorize("@environment.getProperty('keymaster.username') == authentication.name") @Transactional(readOnly = true) - public List list(final NetworkService.Type serviceType) { - return serviceDAO.findAll(serviceType).stream(). - map(service -> toNetworkService(serviceType, service)).toList(); + public List list(final NetworkService.Type serviceType, final String domain) { + return serviceDAO.findAll(serviceType, domain).stream().map(NetworkServiceLogic::toNetworkService).toList(); } @PreAuthorize("@environment.getProperty('keymaster.username') == authentication.name") @@ -71,6 +74,19 @@ public NetworkService get(final NetworkService.Type serviceType) { : list.get(SecureRandomUtils.generateRandomInt(0, list.size())); } + @PreAuthorize("@environment.getProperty('keymaster.username') == authentication.name") + @Transactional(readOnly = true) + public NetworkService get(final NetworkService.Type serviceType, final String domain) { + List list = list(serviceType, domain); + if (list.isEmpty()) { + throw new NotFoundException("No registered services for type " + serviceType + " and domain " + domain); + } + + return list.size() == 1 + ? list.getFirst() + : list.get(SecureRandomUtils.generateRandomInt(0, list.size())); + } + @PreAuthorize("@environment.getProperty('keymaster.username') == authentication.name") public void register(final NetworkService networkService) { if (serviceDAO.findAll(networkService.getType()).stream(). @@ -79,6 +95,7 @@ public void register(final NetworkService networkService) { NetworkServiceEntity service = entityFactory.newEntity(NetworkServiceEntity.class); service.setType(networkService.getType()); service.setAddress(networkService.getAddress()); + service.setDomain(networkService.getDomain()); serviceDAO.save(service); } } diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java index 34e76ff91e7..14b2c7982f7 100644 --- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java +++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/KeymasterITCase.java @@ -175,6 +175,7 @@ public void serviceRun() { NetworkService sra1 = new NetworkService(); sra1.setType(NetworkService.Type.SRA); sra1.setAddress("http://localhost:9080/syncope-sra"); + sra1.setDomain(SyncopeConstants.MASTER_DOMAIN); serviceOps.register(sra1); list = findNetworkServices(NetworkService.Type.SRA, List::isEmpty, 30); @@ -187,6 +188,7 @@ public void serviceRun() { NetworkService sra2 = new NetworkService(); sra2.setType(NetworkService.Type.SRA); sra2.setAddress("http://localhost:9080/syncope-sra"); + sra2.setDomain(SyncopeConstants.MASTER_DOMAIN); assertEquals(sra1, sra2); serviceOps.register(sra2); diff --git a/fit/wa-reference/src/main/resources/wa-embedded.properties b/fit/wa-reference/src/main/resources/wa-embedded.properties index c243c0ca03a..40d66584896 100644 --- a/fit/wa-reference/src/main/resources/wa-embedded.properties +++ b/fit/wa-reference/src/main/resources/wa-embedded.properties @@ -44,4 +44,4 @@ cas.tgc.crypto.encryption.key=mW6lMvsSo48eZ1Ntt74a-O9jjQQQ_OLUE24RVN2_A_sPX43mpB cas.webflow.crypto.signing.key=Md6kkPlXx5L18TD0mFELpQXWnDbMffj-uPutPckMnAPPuJQEbfcLLYBnOynYIEDgnEpd7sxUwGYd8_sVYFMcjw cas.webflow.crypto.encryption.key=FhLgLpaPL8GVNuqqo7gtiw -management.endpoints.web.exposure.include=info,health,env,beans,loggers,ssoSessions,registeredServices,refresh,authenticationHandlers,authenticationPolicies,resolveAttributes,attributeConsent,multitenancy +management.endpoints.web.exposure.include=info,health,env,beans,loggers,ssoSessions,registeredServices,refresh,authenticationHandlers,authenticationPolicies,resolveAttributes,attributeConsent diff --git a/fit/wa-reference/src/main/resources/wa-multitenancy.properties b/fit/wa-reference/src/main/resources/wa-multitenancy.properties deleted file mode 100644 index a95a51ea7ad..00000000000 --- a/fit/wa-reference/src/main/resources/wa-multitenancy.properties +++ /dev/null @@ -1,17 +0,0 @@ -# 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. -cas.multitenancy.core.enabled=true diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java index d49586bf834..994a5e2d3c4 100644 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java +++ b/fit/wa-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java @@ -248,8 +248,7 @@ protected static CloseableHttpResponse authenticateToWA( final String password, final String body, final CloseableHttpClient httpclient, - final HttpClientContext context, - final String... tenant) + final HttpClientContext context) throws IOException { List form = new ArrayList<>(); @@ -259,7 +258,7 @@ protected static CloseableHttpResponse authenticateToWA( form.add(new BasicNameValuePair("password", password)); form.add(new BasicNameValuePair("geolocation", "")); - HttpPost post = new HttpPost(WA_ADDRESS + (tenant.length == 0 ? "" : "/tenants/" + tenant[0]) + "/login"); + HttpPost post = new HttpPost(WA_ADDRESS + "/login"); post.addHeader(HttpHeaders.ACCEPT, MediaType.TEXT_HTML); post.addHeader(HttpHeaders.ACCEPT_LANGUAGE, EN_LANGUAGE); post.setEntity(new UrlEncodedFormEntity(form, Consts.UTF_8)); diff --git a/fit/wa-reference/src/test/java/org/apache/syncope/fit/MultitenancyITCase.java b/fit/wa-reference/src/test/java/org/apache/syncope/fit/MultitenancyITCase.java deleted file mode 100644 index 28bb560b182..00000000000 --- a/fit/wa-reference/src/test/java/org/apache/syncope/fit/MultitenancyITCase.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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.fit; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - -import jakarta.ws.rs.core.GenericType; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.Response; -import java.io.IOException; -import java.util.Optional; -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; -import org.apache.syncope.client.lib.SyncopeClientFactoryBean; -import org.apache.syncope.common.lib.SyncopeClientException; -import org.apache.syncope.common.lib.SyncopeConstants; -import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf; -import org.apache.syncope.common.lib.request.UserCR; -import org.apache.syncope.common.lib.to.AuthModuleTO; -import org.apache.syncope.common.lib.to.ProvisioningResult; -import org.apache.syncope.common.lib.to.UserTO; -import org.apache.syncope.common.rest.api.beans.RealmQuery; -import org.apache.syncope.common.rest.api.service.AuthModuleService; -import org.apache.syncope.common.rest.api.service.RealmService; -import org.apache.syncope.common.rest.api.service.UserService; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -public class MultitenancyITCase extends AbstractITCase { - - protected static final String MT_USERNAME = "multitenancy"; - - protected static final String MT_PASSWORD = "password"; - - @BeforeAll - public static void multitenancySetup() { - assumeTrue(Optional.ofNullable(System.getProperties().getProperty("spring.profiles.active")). - map(profiles -> profiles.contains("multitenancy")).orElse(false)); - - CLIENT_FACTORY = new SyncopeClientFactoryBean().setAddress(CORE_ADDRESS).setDomain("Two"); - - ADMIN_CLIENT = CLIENT_FACTORY.create(ADMIN_UNAME, "password2"); - - // 1. create Syncope auth module - AuthModuleService authModuleService = ADMIN_CLIENT.getService(AuthModuleService.class); - AuthModuleTO syncopeAuthModule = null; - try { - syncopeAuthModule = authModuleService.read("syncopeTwo"); - } catch (SyncopeClientException e) { - if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) { - SyncopeAuthModuleConf conf = new SyncopeAuthModuleConf(); - conf.setDomain("Two"); - - syncopeAuthModule = new AuthModuleTO(); - syncopeAuthModule.setKey("syncopeTwo"); - syncopeAuthModule.setConf(conf); - - Response response = authModuleService.create(syncopeAuthModule); - assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatusInfo().getStatusCode()); - } - } - assertNotNull(syncopeAuthModule); - - // 2. create user - assertNull(ADMIN_CLIENT.getService(RealmService.class). - search(new RealmQuery.Builder().build()).getResult().getFirst().getPasswordPolicy()); - - UserService userService = ADMIN_CLIENT.getService(UserService.class); - UserTO user = null; - try { - user = userService.read(MT_USERNAME); - } catch (SyncopeClientException e) { - if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) { - UserCR userCR = new UserCR(); - userCR.setRealm(SyncopeConstants.ROOT_REALM); - userCR.setUsername(MT_USERNAME); - userCR.setPassword(MT_PASSWORD); - - Response response = ADMIN_CLIENT.getService(UserService.class).create(userCR); - assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); - - ProvisioningResult result = response.readEntity(new GenericType<>() { - }); - user = result.getEntity(); - } - } - assertNotNull(user); - } - - @Test - public void login() throws IOException { - try (CloseableHttpClient httpclient = HttpClients.createDefault()) { - HttpClientContext context = HttpClientContext.create(); - context.setCookieStore(new BasicCookieStore()); - - String loginPageBody; - try (CloseableHttpResponse response = - httpclient.execute(new HttpGet(WA_ADDRESS + "/tenants/Two/login"), context)) { - - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - loginPageBody = EntityUtils.toString(response.getEntity()); - } - assertNotNull(loginPageBody); - - String location; - try (CloseableHttpResponse response = - authenticateToWA(MT_USERNAME, MT_PASSWORD, loginPageBody, httpclient, context, "Two")) { - - assertEquals(HttpStatus.SC_MOVED_TEMPORARILY, response.getStatusLine().getStatusCode()); - location = response.getFirstHeader(HttpHeaders.LOCATION).getValue(); - } - assertTrue(location.endsWith("/account")); - - try (CloseableHttpResponse response = - httpclient.execute(new HttpGet(WA_ADDRESS + "/tenants/Two/account"), context)) { - - assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); - assertTrue(EntityUtils.toString(response.getEntity()).contains(MT_USERNAME)); - } - } - } -} diff --git a/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java b/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java index 6443b015bce..085744c703e 100644 --- a/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java +++ b/sra/src/main/java/org/apache/syncope/sra/RouteProvider.java @@ -112,6 +112,8 @@ protected record TranslatedPredicate(SRARoutePredicateCond cond, AsyncPredicate< protected final String anonymousKey; + protected final String domain; + protected final boolean useGZIPCompression; protected SyncopeClient client; @@ -123,12 +125,14 @@ public RouteProvider( final ConfigurableApplicationContext ctx, final String anonymousUser, final String anonymousKey, + final String domain, final boolean useGZIPCompression) { this.serviceOps = serviceOps; this.ctx = ctx; this.anonymousUser = anonymousUser; this.anonymousKey = anonymousKey; + this.domain = domain; this.useGZIPCompression = useGZIPCompression; } @@ -574,6 +578,7 @@ public List fetch() { try { client = new SyncopeClientFactoryBean(). setAddress(serviceOps.get(NetworkService.Type.CORE).getAddress()). + setDomain(domain). setUseCompression(useGZIPCompression). create(new BasicAuthenticationHandler(anonymousUser, anonymousKey)); } catch (Exception e) { diff --git a/sra/src/main/java/org/apache/syncope/sra/SRAProperties.java b/sra/src/main/java/org/apache/syncope/sra/SRAProperties.java index ad541235a30..c7c0ffdff76 100644 --- a/sra/src/main/java/org/apache/syncope/sra/SRAProperties.java +++ b/sra/src/main/java/org/apache/syncope/sra/SRAProperties.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.SyncopeProperties; import org.apache.syncope.common.lib.types.SAML2BindingType; import org.apereo.cas.client.Protocol; @@ -366,6 +367,8 @@ public void setProtocol(final Protocol protocol) { } } + private String domain = SyncopeConstants.MASTER_DOMAIN; + private final Global global = new Global(); private AMType amType = AMType.OIDC; @@ -378,6 +381,14 @@ public void setProtocol(final Protocol protocol) { private final CAS cas = new CAS(); + public String getDomain() { + return domain; + } + + public void setDomain(final String domain) { + this.domain = domain; + } + public Global getGlobal() { return global; } diff --git a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java index bc0eac198d7..44eb1c50366 100644 --- a/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java +++ b/sra/src/main/java/org/apache/syncope/sra/SyncopeSRAApplication.java @@ -79,6 +79,7 @@ public RouteProvider routeProvider( ctx, props.getAnonymousUser(), props.getAnonymousKey(), + props.getDomain(), props.isUseGZIPCompression()); } @@ -116,21 +117,22 @@ public SyncopeSRAInfoContributor syncopeSRAInfoContributor() { @ConditionalOnProperty( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean - public KeymasterStart keymasterStart() { - return new KeymasterStart(NetworkService.Type.SRA); + public KeymasterStart keymasterStart(final SRAProperties props) { + return new KeymasterStart.Builder(NetworkService.Type.SRA).domain(props.getDomain()).build(); } @ConditionalOnProperty( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean - public KeymasterStop keymasterStop() { - return new KeymasterStop(NetworkService.Type.SRA); + public KeymasterStop keymasterStop(final SRAProperties props) { + return new KeymasterStop.Builder(NetworkService.Type.SRA).domain(props.getDomain()).build(); } @Bean public WebExceptionHandler syncopeSRAWebExceptionHandler( @Qualifier("routeProvider") final RouteProvider routeProvider, final SRAProperties props) { + return new SyncopeSRAWebExceptionHandler(routeProvider, props); } } diff --git a/sra/src/main/resources/sra.properties b/sra/src/main/resources/sra.properties index 7b1208157d7..914deb22d05 100644 --- a/sra/src/main/resources/sra.properties +++ b/sra/src/main/resources/sra.properties @@ -37,6 +37,7 @@ logging.config=classpath:log4j2.xml sra.anonymousUser=${anonymousUser} sra.anonymousKey=${anonymousKey} +sra.domain=Master sra.useGZIPCompression=true sra.global.error=/error diff --git a/src/main/asciidoc/reference-guide/concepts/domains.adoc b/src/main/asciidoc/reference-guide/concepts/domains.adoc index 3eea561038b..065b2b7387c 100644 --- a/src/main/asciidoc/reference-guide/concepts/domains.adoc +++ b/src/main/asciidoc/reference-guide/concepts/domains.adoc @@ -26,10 +26,6 @@ External Resources, Policies, Tasks, etc. from different domains (e.g. tenants) By default, a single `Master` domain is defined, which also bears the configuration for additional domains. -Every domain besides `Master` is mapped one-to-one with a -https://apereo.github.io/cas/7.3.x/multitenancy/Multitenancy-Overview.html[CAS tenant^] having the same identifier; this -allows for <> configuration to relate only to the given domain's database instance. - [.text-center] image::domains.png[title="Domains",alt="Domains"] diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java index dea90b075a7..b3cada90b1b 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WABootstrapConfiguration.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.wa.bootstrap; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper; import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; @@ -48,6 +49,9 @@ public static class WAClientConfiguration { @Value("${wa.anonymousKey}") private String anonymousKey; + @Value("${wa.domain:" + SyncopeConstants.MASTER_DOMAIN + "}") + private String domain; + @Value("${wa.useGZIPCompression:true}") private boolean useGZIPCompression; @@ -56,7 +60,7 @@ public static class WAClientConfiguration { @Bean public WARestClient waRestClient() { - return new WARestClient(anonymousUser, anonymousKey, useGZIPCompression, serviceDiscoveryAddress); + return new WARestClient(anonymousUser, anonymousKey, domain, useGZIPCompression, serviceDiscoveryAddress); } } diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAProperties.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAProperties.java index 9735b3e6caa..3aef58a9811 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAProperties.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WAProperties.java @@ -18,6 +18,7 @@ */ package org.apache.syncope.wa.bootstrap; +import org.apache.syncope.common.lib.SyncopeConstants; import org.apache.syncope.common.lib.SyncopeProperties; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -26,8 +27,18 @@ public class WAProperties extends SyncopeProperties { private static final long serialVersionUID = 7925827623055998239L; + private String domain = SyncopeConstants.MASTER_DOMAIN; + private int contextRefreshDelaySeconds = 15; + public String getDomain() { + return domain; + } + + public void setDomain(final String domain) { + this.domain = domain; + } + public int getContextRefreshDelaySeconds() { return contextRefreshDelaySeconds; } diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java index 12920a4aef1..aa1dba9a681 100644 --- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java +++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/WARestClient.java @@ -29,7 +29,6 @@ import org.apache.syncope.common.keymaster.client.api.KeymasterException; import org.apache.syncope.common.keymaster.client.api.ServiceOps; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; -import org.apache.syncope.common.lib.SyncopeConstants; import org.apereo.cas.util.spring.ApplicationContextProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,6 +42,8 @@ public class WARestClient { protected final String anonymousKey; + protected final String domain; + protected final boolean useGZIPCompression; protected final String serviceDiscoveryAddress; @@ -54,11 +55,13 @@ public class WARestClient { public WARestClient( final String anonymousUser, final String anonymousKey, + final String domain, final boolean useGZIPCompression, final String serviceDiscoveryAddress) { this.anonymousUser = anonymousUser; this.anonymousKey = anonymousKey; + this.domain = domain; this.useGZIPCompression = useGZIPCompression; this.serviceDiscoveryAddress = serviceDiscoveryAddress; } @@ -90,38 +93,24 @@ protected Optional getCore() { return Optional.empty(); } - public boolean isReady() { - try { - return getCore().isPresent(); - } catch (Exception e) { - LOG.trace("While checking Core's availability: {}", e.getMessage()); - } - return false; - } - - public Optional getSyncopeClient(final String domain) { - return getCore().flatMap(core -> { - try { - return Optional.of(new SyncopeClientFactoryBean(). - setAddress(core.getAddress()). - setUseCompression(useGZIPCompression). - setDomain(domain). - create(new BasicAuthenticationHandler(anonymousUser, anonymousKey))); - } catch (Exception e) { - LOG.error("Could not init SyncopeClient", e); - return Optional.empty(); - } - }); - } - public SyncopeClient getSyncopeClient() { synchronized (this) { if (client == null) { - client = getSyncopeClient(SyncopeConstants.MASTER_DOMAIN).orElse(null); + getCore().ifPresent(core -> { + try { + client = new SyncopeClientFactoryBean(). + setAddress(core.getAddress()). + setDomain(domain). + setUseCompression(useGZIPCompression). + create(new BasicAuthenticationHandler(anonymousUser, anonymousKey)); + } catch (Exception e) { + LOG.error("Could not init SyncopeClient", e); + } + }); } - } - return client; + return client; + } } @SuppressWarnings("unchecked") @@ -132,4 +121,13 @@ public T getService(final Class serviceClass) { return (T) services.computeIfAbsent(serviceClass, k -> getSyncopeClient().getService(k)); } + + public boolean isReady() { + try { + return getCore().isPresent(); + } catch (Exception e) { + LOG.trace("While checking Core's availability: {}", e.getMessage()); + } + return false; + } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java index abb4ae129d6..e4b2400e9eb 100644 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java +++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/WAContext.java @@ -32,7 +32,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; import org.apache.commons.lang3.StringUtils; -import org.apache.syncope.common.keymaster.client.api.DomainOps; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart; import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop; @@ -40,8 +39,6 @@ import org.apache.syncope.wa.bootstrap.WAProperties; import org.apache.syncope.wa.bootstrap.WARestClient; import org.apache.syncope.wa.bootstrap.mapping.AttrReleaseMapper; -import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper; -import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; import org.apache.syncope.wa.starter.actuate.SyncopeCoreHealthIndicator; import org.apache.syncope.wa.starter.actuate.SyncopeWAInfoContributor; import org.apache.syncope.wa.starter.audit.WAAuditTrailManager; @@ -65,7 +62,6 @@ import org.apache.syncope.wa.starter.mapping.TicketExpirationMapper; import org.apache.syncope.wa.starter.mapping.TimeBasedAccessMapper; import org.apache.syncope.wa.starter.mfa.WAMultifactorAuthenticationTrustStorage; -import org.apache.syncope.wa.starter.multitenancy.WATenantsManager; import org.apache.syncope.wa.starter.oidc.WAOidcJsonWebKeystoreGeneratorService; import org.apache.syncope.wa.starter.pac4j.saml.WASAML2ClientCustomizer; import org.apache.syncope.wa.starter.saml.idp.metadata.WASamlIdPMetadataCacheRefresher; @@ -86,7 +82,6 @@ import org.apereo.cas.consent.ConsentRepositoryBuilder; import org.apereo.cas.gauth.CasGoogleAuthenticator; import org.apereo.cas.gauth.credential.LdapGoogleAuthenticatorTokenCredentialRepository; -import org.apereo.cas.multitenancy.TenantsManager; import org.apereo.cas.oidc.jwks.generator.OidcJsonWebKeystoreGeneratorService; import org.apereo.cas.otp.repository.credentials.OneTimeTokenCredentialRepository; import org.apereo.cas.pm.LdapPasswordManagementService; @@ -607,34 +602,17 @@ public UserDetailsService actuatorUserDetailsService(final WAProperties waProper return new InMemoryUserDetailsManager(user); } - @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) - @ConditionalOnProperty( - prefix = "cas.multitenancy.core", name = "enabled", havingValue = "true", matchIfMissing = false) - @Bean(name = TenantsManager.BEAN_NAME) - public TenantsManager tenantsManager( - final DomainOps domainOps, - final WARestClient waRestClient, - final AuthModulePropertySourceMapper authModulePropertySourceMapper, - final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper) { - - return new WATenantsManager( - domainOps, - waRestClient, - authModulePropertySourceMapper, - attrRepoPropertySourceMapper); - } - @ConditionalOnProperty( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean - public KeymasterStart keymasterStart() { - return new KeymasterStart(NetworkService.Type.WA); + public KeymasterStart keymasterStart(final WAProperties waProperties) { + return new KeymasterStart.Builder(NetworkService.Type.WA).domain(waProperties.getDomain()).build(); } @ConditionalOnProperty( prefix = "keymaster", name = "enableAutoRegistration", havingValue = "true", matchIfMissing = true) @Bean - public KeymasterStop keymasterStop() { - return new KeymasterStop(NetworkService.Type.WA); + public KeymasterStop keymasterStop(final WAProperties waProperties) { + return new KeymasterStop.Builder(NetworkService.Type.WA).domain(waProperties.getDomain()).build(); } } diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/multitenancy/WATenantsManager.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/multitenancy/WATenantsManager.java deleted file mode 100644 index b400b9dc6d6..00000000000 --- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/multitenancy/WATenantsManager.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.wa.starter.multitenancy; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import org.apache.syncope.client.lib.SyncopeClient; -import org.apache.syncope.common.keymaster.client.api.DomainOps; -import org.apache.syncope.common.rest.api.service.AttrRepoService; -import org.apache.syncope.common.rest.api.service.AuthModuleService; -import org.apache.syncope.common.rest.api.service.wa.WAConfigService; -import org.apache.syncope.wa.bootstrap.WAPropertySourceLocator; -import org.apache.syncope.wa.bootstrap.WARestClient; -import org.apache.syncope.wa.bootstrap.mapping.AttrRepoPropertySourceMapper; -import org.apache.syncope.wa.bootstrap.mapping.AuthModulePropertySourceMapper; -import org.apereo.cas.multitenancy.DefaultTenantAuthenticationPolicy; -import org.apereo.cas.multitenancy.DefaultTenantDelegatedAuthenticationPolicy; -import org.apereo.cas.multitenancy.TenantDefinition; -import org.apereo.cas.multitenancy.TenantsManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class WATenantsManager implements TenantsManager { - - protected static final Logger LOG = LoggerFactory.getLogger(WATenantsManager.class); - - protected final DomainOps domainOps; - - protected final WARestClient waRestClient; - - protected final AuthModulePropertySourceMapper authModulePropertySourceMapper; - - protected final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper; - - public WATenantsManager( - final DomainOps domainOps, - final WARestClient waRestClient, - final AuthModulePropertySourceMapper authModulePropertySourceMapper, - final AttrRepoPropertySourceMapper attrRepoPropertySourceMapper) { - - this.domainOps = domainOps; - this.waRestClient = waRestClient; - this.authModulePropertySourceMapper = authModulePropertySourceMapper; - this.attrRepoPropertySourceMapper = attrRepoPropertySourceMapper; - } - - protected TenantDefinition buildTenantDefinition(final SyncopeClient syncopeClient) { - TenantDefinition tenantDefinition = new TenantDefinition(); - tenantDefinition.setId(syncopeClient.getDomain()); - - Map properties = new TreeMap<>(); - Map prefixes = new HashMap<>(); - - DefaultTenantAuthenticationPolicy authPolicy = new DefaultTenantAuthenticationPolicy(); - tenantDefinition.setAuthenticationPolicy(authPolicy); - - DefaultTenantDelegatedAuthenticationPolicy delegatedAuthPolicy = - new DefaultTenantDelegatedAuthenticationPolicy(); - tenantDefinition.setDelegatedAuthenticationPolicy(delegatedAuthPolicy); - - authPolicy.setAuthenticationHandlers(new ArrayList<>()); - delegatedAuthPolicy.setAllowedProviders(new ArrayList<>()); - syncopeClient.getService(AuthModuleService.class).list().forEach(authModuleTO -> { - LOG.debug("Mapping auth module {} ", authModuleTO.getKey()); - - Map map = authModuleTO.getConf().map(authModuleTO, authModulePropertySourceMapper); - properties.putAll(WAPropertySourceLocator.index(map, prefixes)); - - if (map.keySet().stream().anyMatch(k -> k.contains("pac4j"))) { - delegatedAuthPolicy.getAllowedProviders().add(authModuleTO.getKey()); - } else { - authPolicy.getAuthenticationHandlers().add(authModuleTO.getKey()); - } - }); - - authPolicy.setAttributeRepositories(new ArrayList<>()); - syncopeClient.getService(AttrRepoService.class).list().forEach(attrRepoTO -> { - LOG.debug("Mapping attr repo {} ", attrRepoTO.getKey()); - - Map map = attrRepoTO.getConf().map(attrRepoTO, attrRepoPropertySourceMapper); - properties.putAll(WAPropertySourceLocator.index(map, prefixes)); - - authPolicy.getAttributeRepositories().add(attrRepoTO.getKey()); - }); - - syncopeClient.getService(WAConfigService.class).list(). - forEach(attr -> properties.put(attr.getSchema(), String.join(",", attr.getValues()))); - - tenantDefinition.setProperties(properties); - LOG.debug("Collected Tenant {} properties: {}", tenantDefinition.getId(), tenantDefinition.getProperties()); - - return tenantDefinition; - } - - @Override - public Optional findTenant(final String tenantId) { - return waRestClient.getSyncopeClient(tenantId).map(this::buildTenantDefinition); - } - - @Override - public List findTenants() { - List tenants = new ArrayList<>(); - domainOps.list().forEach(domain -> findTenant(domain.getKey()).ifPresent(tenants::add)); - return tenants; - } - - @Override - public TenantDefinition save(final TenantDefinition tenantDefinition) { - // Syncope Domains are managed by Core - return tenantDefinition; - } - - @Override - public boolean delete(final String tenantId) { - // Syncope Domains are managed by Core - return false; - } - - @Override - public void load() { - // nothing to do - } -} diff --git a/wa/starter/src/main/resources/wa.properties b/wa/starter/src/main/resources/wa.properties index 21fc8b05f31..c2f8bb99813 100644 --- a/wa/starter/src/main/resources/wa.properties +++ b/wa/starter/src/main/resources/wa.properties @@ -37,7 +37,7 @@ spring.web.resources.static-locations=classpath:/thymeleaf/static,classpath:/syn cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED management.endpoints.access.default=UNRESTRICTED -management.endpoints.web.exposure.include=info,health,env,loggers,ssoSessions,registeredServices,refresh,authenticationHandlers,authenticationPolicies,resolveAttributes,attributeConsent,multitenancy +management.endpoints.web.exposure.include=info,health,env,loggers,ssoSessions,registeredServices,refresh,authenticationHandlers,authenticationPolicies,resolveAttributes,attributeConsent management.endpoint.health.show-details=ALWAYS management.endpoint.env.show-values=WHEN_AUTHORIZED spring.cloud.discovery.client.health-indicator.enabled=false @@ -64,6 +64,7 @@ service.discovery.address=http://localhost:8080/syncope-wa/ wa.anonymousUser=${anonymousUser} wa.anonymousKey=${anonymousKey} +wa.domain=Master wa.useGZIPCompression=true wa.contextRefreshDelaySeconds=15 diff --git a/wa/starter/src/test/resources/debug/wa-debug.properties b/wa/starter/src/test/resources/debug/wa-debug.properties index 6f14c491bae..7343d1e3e4c 100644 --- a/wa/starter/src/test/resources/debug/wa-debug.properties +++ b/wa/starter/src/test/resources/debug/wa-debug.properties @@ -21,7 +21,7 @@ keymaster.address=http://localhost:9080/syncope/rest/keymaster keymaster.username=${anonymousUser} keymaster.password=${anonymousKey} -management.endpoints.web.exposure.include=info,health,env,beans,loggers,ssoSessions,registeredServices,refresh,authenticationHandlers,authenticationPolicies,resolveAttributes,attributeConsent,multitenancy +management.endpoints.web.exposure.include=info,health,env,beans,loggers,ssoSessions,registeredServices,refresh,authenticationHandlers,authenticationPolicies,resolveAttributes,attributeConsent cas.server.name=http://localhost:8080 cas.server.prefix=${cas.server.name}/syncope-wa From 09f1f71a1530adc05c963300b2a1f60701e6d3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Wed, 2 Sep 2026 16:00:55 +0200 Subject: [PATCH 2/3] Fix2 --- .../common/validation/NetworkServiceValidator.java | 5 +++-- src/main/asciidoc/reference-guide/concepts/domains.adoc | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java index ca17367ecab..f616786f4d9 100644 --- a/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java +++ b/core/persistence-common/src/main/java/org/apache/syncope/core/persistence/common/validation/NetworkServiceValidator.java @@ -21,11 +21,12 @@ import jakarta.validation.ConstraintValidatorContext; import org.apache.syncope.common.keymaster.client.api.model.NetworkService; import org.apache.syncope.common.lib.types.EntityViolationType; +import org.apache.syncope.core.persistence.api.entity.keymaster.NetworkServiceEntity; -public class NetworkServiceValidator extends AbstractValidator { +public class NetworkServiceValidator extends AbstractValidator { @Override - public boolean isValid(final NetworkService service, final ConstraintValidatorContext context) { + public boolean isValid(final NetworkServiceEntity service, final ConstraintValidatorContext context) { context.disableDefaultConstraintViolation(); if ((service.getType() == NetworkService.Type.SRA || service.getType() == NetworkService.Type.WA) diff --git a/src/main/asciidoc/reference-guide/concepts/domains.adoc b/src/main/asciidoc/reference-guide/concepts/domains.adoc index 065b2b7387c..dc327114f04 100644 --- a/src/main/asciidoc/reference-guide/concepts/domains.adoc +++ b/src/main/asciidoc/reference-guide/concepts/domains.adoc @@ -26,6 +26,9 @@ External Resources, Policies, Tasks, etc. from different domains (e.g. tenants) By default, a single `Master` domain is defined, which also bears the configuration for additional domains. +Each <> and <> instance is configured to bind to a given domain. + +This allows to provide segregated configuration for all access-related features. + [.text-center] image::domains.png[title="Domains",alt="Domains"] From 406f260430bf0f663e100fde3dec0f23fbda8069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francesco=20Chicchiricc=C3=B2?= Date: Wed, 2 Sep 2026 16:41:34 +0200 Subject: [PATCH 3/3] Fix --- .../java/org/apache/syncope/core/logic/NetworkServiceLogic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java index f06e540f38d..cfa4ac8c7e9 100644 --- a/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java +++ b/core/self-keymaster-starter/src/main/java/org/apache/syncope/core/logic/NetworkServiceLogic.java @@ -34,7 +34,7 @@ public class NetworkServiceLogic extends AbstractTransactionalLogic { protected static NetworkService toNetworkService(final NetworkServiceEntity service) { NetworkService ns = new NetworkService(); - ns.setType(ns.getType()); + ns.setType(service.getType()); ns.setAddress(service.getAddress()); ns.setDomain(service.getDomain()); return ns;