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

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


The following commit(s) were added to refs/heads/4_0_X by this push:
     new d2301197ef [SYNCOPE-1914] Multiple SAML 2.0 IdP support for WA (#1183)
d2301197ef is described below

commit d2301197ef87ef293f6a79301d35ba69029415a6
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Fri Sep 19 09:19:30 2025 +0200

    [SYNCOPE-1914] Multiple SAML 2.0 IdP support for WA (#1183)
---
 .../clientapps/ClientAppModalPanelBuilder.java     | 14 ++++
 .../apache/syncope/client/console/pages/WA.java    |  6 +-
 .../panels/SAML2IdPEntityDirectoryPanel.java       | 89 +++++++++++++++++-----
 .../console/rest/SAML2IdPEntityRestClient.java     |  4 +
 .../clientapps/ClientAppDirectoryPanel.properties  |  1 +
 .../ClientAppDirectoryPanel_fr_CA.properties       |  1 +
 .../ClientAppDirectoryPanel_it.properties          |  1 +
 .../ClientAppDirectoryPanel_ja.properties          |  1 +
 .../ClientAppDirectoryPanel_pt_BR.properties       |  1 +
 .../ClientAppDirectoryPanel_ru.properties          |  1 +
 .../syncope/common/lib/to/SAML2SPClientAppTO.java  | 10 +++
 .../syncope/common/lib/types/AMEntitlement.java    |  2 +
 .../rest/api/service/SAML2IdPEntityService.java    | 15 +++-
 .../syncope/core/logic/SAML2IdPEntityLogic.java    |  9 ++-
 .../cxf/service/SAML2IdPEntityServiceImpl.java     |  5 ++
 .../api/entity/am/SAML2SPClientApp.java            |  5 ++
 .../jpa/entity/am/JPASAML2SPClientApp.java         | 13 ++++
 .../neo4j/entity/am/Neo4jSAML2SPClientApp.java     | 13 ++++
 .../java/data/ClientAppDataBinderImpl.java         | 22 ++++--
 .../java/data/SAML2IdPEntityDataBinderImpl.java    | 24 +++---
 pom.xml                                            | 13 +++-
 .../syncope/wa/starter/config/WAContext.java       | 19 ++++-
 .../starter/mapping/SAML2SPClientAppTOMapper.java  |  5 ++
 .../metadata/WASamlIdPMetadataCacheRefresher.java  | 45 +++++++++++
 .../idp/metadata/WASamlIdPMetadataGenerator.java   |  3 +-
 .../idp/metadata/WASamlIdPMetadataLocator.java     | 50 ++++++++----
 wa/starter/src/main/resources/wa.properties        |  2 +-
 27 files changed, 308 insertions(+), 66 deletions(-)

diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
index 211ff1fce4..440d32d132 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/clientapps/ClientAppModalPanelBuilder.java
@@ -27,6 +27,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.RandomStringUtils;
@@ -59,6 +60,7 @@ import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.to.ClientAppTO;
 import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.syncope.common.lib.to.SAML2SPClientAppTO;
 import org.apache.syncope.common.lib.types.ClientAppType;
 import org.apache.syncope.common.lib.types.LogoutType;
 import org.apache.syncope.common.lib.types.OIDCApplicationType;
@@ -72,6 +74,7 @@ import 
org.apache.syncope.common.lib.types.OIDCTokenSigningAlg;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.types.SAML2SPNameId;
 import org.apache.syncope.common.lib.types.XmlSecAlgorithm;
+import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService;
 import org.apache.wicket.Component;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -469,6 +472,17 @@ public class ClientAppModalPanelBuilder<T extends 
ClientAppTO> extends AbstractM
                             "field", "entityId", new 
PropertyModel<>(clientAppTO, "entityId"), false);
                     fields.add(entityId.setRequired(true));
 
+                    fields.add(new AjaxTextFieldPanel("field", "idp", new 
PropertyModel<>(clientAppTO, "idp") {
+
+                        private static final long serialVersionUID = 
-1476737401674143862L;
+
+                        @Override
+                        public String getObject() {
+                            return Optional.ofNullable(((SAML2SPClientAppTO) 
clientAppTO).getIdp()).
+                                    
orElse(SAML2IdPEntityService.DEFAULT_OWNER);
+                        }
+                    }, false));
+
                     fields.add(new AjaxTextFieldPanel("field", 
"metadataLocation",
                             new PropertyModel<>(clientAppTO, 
"metadataLocation"), false).setRequired(true));
 
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 58dda5ea16..39b0bd8562 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
@@ -45,6 +45,7 @@ import 
org.apache.syncope.client.console.panels.WAPushModalPanel;
 import org.apache.syncope.client.console.rest.AttrRepoRestClient;
 import org.apache.syncope.client.console.rest.AuthModuleRestClient;
 import org.apache.syncope.client.console.rest.AuthProfileRestClient;
+import org.apache.syncope.client.console.rest.ClientAppRestClient;
 import org.apache.syncope.client.console.rest.SAML2IdPEntityRestClient;
 import org.apache.syncope.client.console.rest.WAConfigRestClient;
 import org.apache.syncope.client.console.rest.WASessionRestClient;
@@ -87,6 +88,9 @@ public class WA extends BasePage {
     @SpringBean
     protected SAML2IdPEntityRestClient saml2IdPEntityRestClient;
 
+    @SpringBean
+    protected ClientAppRestClient clientAppRestClient;
+
     @SpringBean
     protected ServiceOps serviceOps;
 
@@ -210,7 +214,7 @@ public class WA extends BasePage {
             @Override
             public Panel getPanel(final String panelId) {
                 return new SAML2IdPEntityDirectoryPanel(
-                        panelId, saml2IdPEntityRestClient, waPrefix, 
getPageReference());
+                        panelId, saml2IdPEntityRestClient, 
clientAppRestClient, waPrefix, getPageReference());
             }
         });
 
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPEntityDirectoryPanel.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPEntityDirectoryPanel.java
index 2ec1e9ea21..735d7cc99b 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPEntityDirectoryPanel.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/panels/SAML2IdPEntityDirectoryPanel.java
@@ -23,18 +23,25 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.AMConstants;
 import org.apache.syncope.client.console.commons.DirectoryDataProvider;
 import 
org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.pages.BasePage;
 import 
org.apache.syncope.client.console.panels.SAML2IdPEntityDirectoryPanel.SAML2IdPEntityProvider;
+import org.apache.syncope.client.console.rest.ClientAppRestClient;
 import org.apache.syncope.client.console.rest.SAML2IdPEntityRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
 import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
 import org.apache.syncope.client.console.wizards.SAML2IdPEntityWizardBuilder;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.SAML2IdPEntityTO;
+import org.apache.syncope.common.lib.to.SAML2SPClientAppTO;
 import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.event.Broadcast;
@@ -43,10 +50,12 @@ import 
org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColu
 import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
 import 
org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
 import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.ExternalLink;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
 import org.apache.wicket.model.StringResourceModel;
 
@@ -55,15 +64,47 @@ public class SAML2IdPEntityDirectoryPanel extends 
DirectoryPanel<
 
     private static final long serialVersionUID = -6535332920023200166L;
 
+    private static ExternalLink metadataLink(final String componentId, final 
String url) {
+        return new ExternalLink(componentId, Model.of(url), Model.of(url)) {
+
+            private static final long serialVersionUID = -1919646533527005367L;
+
+            @Override
+            protected void onComponentTag(final ComponentTag tag) {
+                super.onComponentTag(tag);
+
+                tag.setName("a");
+                if (url.startsWith("http")) {
+                    tag.put("href", getDefaultModelObject().toString());
+                    tag.put("target", "_blank");
+                }
+            }
+        };
+    }
+
+    private final LoadableDetachableModel<List<SAML2SPClientAppTO>> clientApps;
+
     private final String metadataURL;
 
     public SAML2IdPEntityDirectoryPanel(
             final String id,
             final SAML2IdPEntityRestClient restClient,
+            final ClientAppRestClient clientAppRestClient,
             final String waPrefix,
             final PageReference pageRef) {
 
         super(id, restClient, pageRef);
+
+        clientApps = new LoadableDetachableModel<>() {
+
+            private static final long serialVersionUID = 7172461137064525667L;
+
+            @Override
+            protected List<SAML2SPClientAppTO> load() {
+                return clientAppRestClient.list(ClientAppType.SAML2SP);
+            }
+        };
+
         this.metadataURL = waPrefix + "/idp/metadata";
 
         disableCheckBoxes();
@@ -98,24 +139,16 @@ public class SAML2IdPEntityDirectoryPanel extends 
DirectoryPanel<
                     final String componentId,
                     final IModel<SAML2IdPEntityTO> rowModel) {
 
-                cellItem.add(new ExternalLink(
-                        componentId,
-                        Model.of(metadataURL),
-                        Model.of(metadataURL)) {
-
-                    private static final long serialVersionUID = 
-1919646533527005367L;
-
-                    @Override
-                    protected void onComponentTag(final ComponentTag tag) {
-                        super.onComponentTag(tag);
-
-                        tag.setName("a");
-                        if (metadataURL.startsWith("http")) {
-                            tag.put("href", 
getDefaultModelObject().toString());
-                            tag.put("target", "_blank");
-                        }
-                    }
-                });
+                if 
(SAML2IdPEntityService.DEFAULT_OWNER.equals(rowModel.getObject().getKey())) {
+                    cellItem.add(metadataLink(componentId, metadataURL));
+                } else {
+                    clientApps.getObject().stream().
+                            filter(app -> 
rowModel.getObject().getKey().equals(app.getIdp())).
+                            findFirst().ifPresentOrElse(
+                                    app -> cellItem.add(metadataLink(
+                                            componentId, metadataURL + 
"?service=" + app.getClientAppId())),
+                                    () -> cellItem.add(new Label(componentId, 
Model.of())));
+                }
             }
         });
 
@@ -138,6 +171,26 @@ public class SAML2IdPEntityDirectoryPanel extends 
DirectoryPanel<
             }
         }, ActionLink.ActionType.EDIT, AMEntitlement.SAML2_IDP_ENTITY_SET);
 
+        if 
(!SAML2IdPEntityService.DEFAULT_OWNER.equals(model.getObject().getKey())) {
+            panel.add(new ActionLink<>() {
+
+                private static final long serialVersionUID = 
-3722207913631435501L;
+
+                @Override
+                public void onClick(final AjaxRequestTarget target, final 
SAML2IdPEntityTO ignore) {
+                    try {
+                        restClient.delete(model.getObject().getKey());
+                        
SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                        target.add(container);
+                    } catch (SyncopeClientException e) {
+                        LOG.error("While deleting IdP {}", 
model.getObject().getKey(), e);
+                        SyncopeConsoleSession.get().onException(e);
+                    }
+                    ((BasePage) 
pageRef.getPage()).getNotificationPanel().refresh(target);
+                }
+            }, ActionLink.ActionType.DELETE, 
AMEntitlement.SAML2_IDP_ENTITY_DELETE, true);
+        }
+
         return panel;
     }
 
diff --git 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPEntityRestClient.java
 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPEntityRestClient.java
index 9c66b1e784..26b7a13c36 100644
--- 
a/client/am/console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPEntityRestClient.java
+++ 
b/client/am/console/src/main/java/org/apache/syncope/client/console/rest/SAML2IdPEntityRestClient.java
@@ -38,4 +38,8 @@ public class SAML2IdPEntityRestClient extends BaseRestClient {
     public void set(final SAML2IdPEntityTO entityTO) {
         getService(SAML2IdPEntityService.class).set(entityTO);
     }
+
+    public void delete(final String key) {
+        getService(SAML2IdPEntityService.class).delete(key);
+    }
 }
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
index 229c25a262..e3ba1847eb 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel.properties
@@ -81,3 +81,4 @@ userInfoSigningAlg=UserInfo Signing Algorithm
 userInfoEncryptedResponseAlg=UserInfo Encrypted Response Algorithm
 userInfoEncryptedResponseEncoding=UserInfo Encrypted Response Encoding
 evaluationOrder=Evaluation Order
+idp=Identity Provider
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
index b8f5fc8798..2bb0a441b3 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_fr_CA.properties
@@ -81,3 +81,4 @@ userInfoSigningAlg=UserInfo Signing Algorithm
 userInfoEncryptedResponseAlg=UserInfo Encrypted Response Algorithm
 userInfoEncryptedResponseEncoding=UserInfo Encrypted Response Encoding
 evaluationOrder=Evaluation Order
+idp=Identity Provider
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
index 90bfaf968a..2ad1b0f69b 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_it.properties
@@ -81,3 +81,4 @@ userInfoSigningAlg=Algoritmo di firma UserInfo
 userInfoEncryptedResponseAlg=Algoritmo di cifratura risposta UserInfo
 userInfoEncryptedResponseEncoding=Codifica di cifratura risposta UserInfo
 evaluationOrder=Ordine di valutazione
+idp=Identity Provider
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
index 428c4dce87..d0868ef9f2 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ja.properties
@@ -81,3 +81,4 @@ userInfoSigningAlg=UserInfo Signing Algorithm
 userInfoEncryptedResponseAlg=UserInfo Encrypted Response Algorithm
 userInfoEncryptedResponseEncoding=UserInfo Encrypted Response Encoding
 evaluationOrder=Evaluation Order
+idp=Identity Provider
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
index 5438363123..1bd4e93e86 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_pt_BR.properties
@@ -81,3 +81,4 @@ userInfoSigningAlg=UserInfo Signing Algorithm
 userInfoEncryptedResponseAlg=UserInfo Encrypted Response Algorithm
 userInfoEncryptedResponseEncoding=UserInfo Encrypted Response Encoding
 evaluationOrder=Evaluation Order
+idp=Identity Provider
diff --git 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
index 09307c8825..bc0ab53d9d 100644
--- 
a/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
+++ 
b/client/am/console/src/main/resources/org/apache/syncope/client/console/clientapps/ClientAppDirectoryPanel_ru.properties
@@ -82,3 +82,4 @@ userInfoSigningAlg=UserInfo Signing Algorithm
 userInfoEncryptedResponseAlg=UserInfo Encrypted Response Algorithm
 userInfoEncryptedResponseEncoding=UserInfo Encrypted Response Encoding
 evaluationOrder=Evaluation Order
+idp=Identity Provider
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2SPClientAppTO.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2SPClientAppTO.java
index 7887bbcd21..46ad7f5384 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2SPClientAppTO.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2SPClientAppTO.java
@@ -36,6 +36,8 @@ public class SAML2SPClientAppTO extends ClientAppTO {
 
     private String entityId;
 
+    private String idp;
+
     private String metadataLocation;
 
     private String metadataSignatureLocation;
@@ -89,6 +91,14 @@ public class SAML2SPClientAppTO extends ClientAppTO {
         this.entityId = entityId;
     }
 
+    public String getIdp() {
+        return idp;
+    }
+
+    public void setIdp(final String idp) {
+        this.idp = idp;
+    }
+
     public String getMetadataLocation() {
         return metadataLocation;
     }
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
index ceae04b368..f9fd0d6508 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
@@ -74,6 +74,8 @@ public final class AMEntitlement {
 
     public static final String SAML2_IDP_ENTITY_GET = "SAML2_IDP_ENTITY_GET";
 
+    public static final String SAML2_IDP_ENTITY_DELETE = 
"SAML2_IDP_ENTITY_DELETE";
+
     public static final String AUTH_PROFILE_DELETE = "AUTH_PROFILE_DELETE";
 
     public static final String AUTH_PROFILE_CREATE = "AUTH_PROFILE_CREATE";
diff --git 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java
 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java
index 47979cfa27..c13b48b999 100644
--- 
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java
+++ 
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPEntityService.java
@@ -26,6 +26,7 @@ import 
io.swagger.v3.oas.annotations.security.SecurityRequirements;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.constraints.NotNull;
 import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
 import jakarta.ws.rs.GET;
 import jakarta.ws.rs.POST;
 import jakarta.ws.rs.Path;
@@ -69,7 +70,7 @@ public interface SAML2IdPEntityService extends JAXRSService {
     SAML2IdPEntityTO get(@NotNull @PathParam("key") String key);
 
     /**
-     * Store the entity to finalize the generation process.
+     * Stores the entity to finalize the generation process.
      *
      * @param entityTO entity to be created
      */
@@ -80,4 +81,16 @@ public interface SAML2IdPEntityService extends JAXRSService {
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
     void set(@NotNull SAML2IdPEntityTO entityTO);
+
+    /**
+     * Deletes the SAML 2.0 IdP entity matching the given key.
+     *
+     * @param key key of requested SAML 2.0 IdP entity
+     */
+    @Parameter(name = "key", description = "SAML2IdPEntityTO's key", in = 
ParameterIn.PATH, schema =
+            @Schema(type = "string"))
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, 
MediaType.APPLICATION_XML })
+    void delete(@NotNull @PathParam("key") String key);
 }
diff --git 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPEntityLogic.java
 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPEntityLogic.java
index cc50248f79..243c00eebb 100644
--- 
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPEntityLogic.java
+++ 
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPEntityLogic.java
@@ -68,6 +68,11 @@ public class SAML2IdPEntityLogic extends 
AbstractTransactionalLogic<SAML2IdPEnti
         return binder.getSAML2IdPEntityTO(saml2IdPEntityDAO.save(entity));
     }
 
+    @PreAuthorize("hasRole('" + AMEntitlement.SAML2_IDP_ENTITY_DELETE + "')")
+    public void delete(final String key) {
+        saml2IdPEntityDAO.deleteById(key);
+    }
+
     @Override
     protected SAML2IdPEntityTO resolveReference(final Method method, final 
Object... args)
             throws UnresolvedReferenceException {
@@ -77,8 +82,8 @@ public class SAML2IdPEntityLogic extends 
AbstractTransactionalLogic<SAML2IdPEnti
             for (int i = 0; key == null && i < args.length; i++) {
                 if (args[i] instanceof String string) {
                     key = string;
-                } else if (args[i] instanceof SAML2IdPEntityTO 
sAML2IdPEntityTO) {
-                    key = sAML2IdPEntityTO.getKey();
+                } else if (args[i] instanceof SAML2IdPEntityTO idp) {
+                    key = idp.getKey();
                 }
             }
         }
diff --git 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPEntityServiceImpl.java
 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPEntityServiceImpl.java
index 45050ecea7..94a4566dd1 100644
--- 
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPEntityServiceImpl.java
+++ 
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPEntityServiceImpl.java
@@ -45,4 +45,9 @@ public class SAML2IdPEntityServiceImpl extends 
AbstractService implements SAML2I
     public void set(final SAML2IdPEntityTO entityTO) {
         logic.set(entityTO);
     }
+
+    @Override
+    public void delete(final String key) {
+        logic.delete(key);
+    }
 }
diff --git 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/SAML2SPClientApp.java
 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/SAML2SPClientApp.java
index 608b0c2762..caf8d8a143 100644
--- 
a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/SAML2SPClientApp.java
+++ 
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/am/SAML2SPClientApp.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.api.entity.am;
 
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import org.apache.syncope.common.lib.types.SAML2SPNameId;
 import org.apache.syncope.common.lib.types.XmlSecAlgorithm;
@@ -29,6 +30,10 @@ public interface SAML2SPClientApp extends ClientApp {
 
     void setEntityId(String id);
 
+    Optional<String> getIdp();
+
+    void setIdp(String idp);
+
     String getMetadataLocation();
 
     void setMetadataLocation(String location);
diff --git 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPASAML2SPClientApp.java
 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPASAML2SPClientApp.java
index 6512f7d3bf..af6c17c937 100644
--- 
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPASAML2SPClientApp.java
+++ 
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/am/JPASAML2SPClientApp.java
@@ -32,6 +32,7 @@ import jakarta.persistence.Transient;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import org.apache.syncope.common.lib.types.SAML2SPNameId;
 import org.apache.syncope.common.lib.types.XmlSecAlgorithm;
@@ -56,6 +57,8 @@ public class JPASAML2SPClientApp extends AbstractClientApp 
implements SAML2SPCli
     @Column(unique = true, nullable = false)
     private String entityId;
 
+    private String idp;
+
     @Column(nullable = false)
     private String metadataLocation;
 
@@ -139,6 +142,16 @@ public class JPASAML2SPClientApp extends AbstractClientApp 
implements SAML2SPCli
         this.entityId = entityId;
     }
 
+    @Override
+    public Optional<String> getIdp() {
+        return Optional.ofNullable(idp);
+    }
+
+    @Override
+    public void setIdp(final String idp) {
+        this.idp = idp;
+    }
+
     @Override
     public String getMetadataLocation() {
         return metadataLocation;
diff --git 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jSAML2SPClientApp.java
 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jSAML2SPClientApp.java
index 17ea8b8b18..3c6902bfd7 100644
--- 
a/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jSAML2SPClientApp.java
+++ 
b/core/persistence-neo4j/src/main/java/org/apache/syncope/core/persistence/neo4j/entity/am/Neo4jSAML2SPClientApp.java
@@ -23,6 +23,7 @@ import jakarta.validation.constraints.NotNull;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import org.apache.syncope.common.lib.types.SAML2SPNameId;
 import org.apache.syncope.common.lib.types.XmlSecAlgorithm;
@@ -49,6 +50,8 @@ public class Neo4jSAML2SPClientApp extends AbstractClientApp 
implements SAML2SPC
     @NotNull
     private String entityId;
 
+    private String idp;
+
     private String metadataLocation;
 
     private String metadataSignatureLocation;
@@ -116,6 +119,16 @@ public class Neo4jSAML2SPClientApp extends 
AbstractClientApp implements SAML2SPC
         this.entityId = entityId;
     }
 
+    @Override
+    public Optional<String> getIdp() {
+        return Optional.ofNullable(idp);
+    }
+
+    @Override
+    public void setIdp(final String idp) {
+        this.idp = idp;
+    }
+
     @Override
     public String getMetadataLocation() {
         return metadataLocation;
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
index dcb6a84971..7eed004751 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
@@ -25,6 +25,7 @@ import org.apache.syncope.common.lib.to.ClientAppTO;
 import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
 import org.apache.syncope.common.lib.to.SAML2SPClientAppTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmSearchDAO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
@@ -76,14 +77,15 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
 
     @Override
     public <T extends ClientApp> void update(final T clientApp, final 
ClientAppTO clientAppTO) {
-        if (clientAppTO instanceof SAML2SPClientAppTO sAML2SPClientAppTO) {
-            doUpdate((SAML2SPClientApp) clientApp, sAML2SPClientAppTO);
-        } else if (clientAppTO instanceof OIDCRPClientAppTO oIDCRPClientAppTO) 
{
-            doUpdate((OIDCRPClientApp) clientApp, oIDCRPClientAppTO);
-        } else if (clientAppTO instanceof CASSPClientAppTO cASSPClientAppTO) {
-            doUpdate((CASSPClientApp) clientApp, cASSPClientAppTO);
-        } else {
-            throw new IllegalArgumentException("Unsupported client app: " + 
clientAppTO.getClass().getName());
+        switch (clientAppTO) {
+            case SAML2SPClientAppTO saml2sp ->
+                doUpdate((SAML2SPClientApp) clientApp, saml2sp);
+            case OIDCRPClientAppTO oidcrp ->
+                doUpdate((OIDCRPClientApp) clientApp, oidcrp);
+            case CASSPClientAppTO cassp ->
+                doUpdate((CASSPClientApp) clientApp, cassp);
+            default ->
+                throw new IllegalArgumentException("Unsupported client app: " 
+ clientAppTO.getClass().getName());
         }
     }
 
@@ -118,6 +120,9 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
         copyToEntity(clientApp, clientAppTO);
 
         clientApp.setEntityId(clientAppTO.getEntityId());
+        if (clientAppTO.getIdp() != null && 
!SAML2IdPEntityService.DEFAULT_OWNER.equals(clientAppTO.getIdp())) {
+            clientApp.setIdp(clientAppTO.getIdp());
+        }
         clientApp.setMetadataLocation(clientAppTO.getMetadataLocation());
         
clientApp.setMetadataSignatureLocation(clientAppTO.getMetadataSignatureLocation());
         clientApp.setSignAssertions(clientAppTO.isSignAssertions());
@@ -183,6 +188,7 @@ public class ClientAppDataBinderImpl implements 
ClientAppDataBinder {
         SAML2SPClientAppTO clientAppTO = new SAML2SPClientAppTO();
         copyToTO(clientApp, clientAppTO);
 
+        clientAppTO.setIdp(clientApp.getIdp().orElse(null));
         clientAppTO.setEntityId(clientApp.getEntityId());
         clientAppTO.setMetadataLocation(clientApp.getMetadataLocation());
         
clientAppTO.setMetadataSignatureLocation(clientApp.getMetadataSignatureLocation());
diff --git 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPEntityDataBinderImpl.java
 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPEntityDataBinderImpl.java
index af43b597fb..516dcbf7d3 100644
--- 
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPEntityDataBinderImpl.java
+++ 
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPEntityDataBinderImpl.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 import java.util.Base64;
+import java.util.Optional;
 import org.apache.syncope.common.lib.to.SAML2IdPEntityTO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.am.SAML2IdPEntity;
@@ -64,21 +65,14 @@ public class SAML2IdPEntityDataBinderImpl implements 
SAML2IdPEntityDataBinder {
         SAML2IdPEntityTO entityTO = new SAML2IdPEntityTO();
         entityTO.setKey(entity.getKey());
         
entityTO.setMetadata(Base64.getEncoder().encodeToString(entity.getMetadata()));
-        if (entity.getEncryptionCertificate() != null) {
-            entityTO.setEncryptionCertificate(
-                    
Base64.getEncoder().encodeToString(entity.getEncryptionCertificate()));
-        }
-        if (entity.getEncryptionKey() != null) {
-            entityTO.setEncryptionKey(
-                    
Base64.getEncoder().encodeToString(entity.getEncryptionKey()));
-        }
-        if (entity.getSigningCertificate() != null) {
-            entityTO.setSigningCertificate(
-                    
Base64.getEncoder().encodeToString(entity.getSigningCertificate()));
-        }
-        if (entity.getSigningKey() != null) {
-            
entityTO.setSigningKey(Base64.getEncoder().encodeToString(entity.getSigningKey()));
-        }
+        Optional.ofNullable(entity.getEncryptionCertificate()).
+                ifPresent(cert -> 
entityTO.setEncryptionCertificate(Base64.getEncoder().encodeToString(cert)));
+        Optional.ofNullable(entity.getEncryptionKey()).
+                ifPresent(key -> 
entityTO.setEncryptionKey(Base64.getEncoder().encodeToString(key)));
+        Optional.ofNullable(entity.getSigningCertificate()).
+                ifPresent(cert -> 
entityTO.setSigningCertificate(Base64.getEncoder().encodeToString(cert)));
+        Optional.ofNullable(entity.getSigningKey()).
+                ifPresent(key -> 
entityTO.setSigningKey(Base64.getEncoder().encodeToString(key)));
         return entityTO;
     }
 }
diff --git a/pom.xml b/pom.xml
index e3609040cf..c563c2ecd8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -415,7 +415,7 @@ under the License.
     <bouncycastle.version>1.81</bouncycastle.version>
     <nimbus-jose-jwt.version>10.5</nimbus-jose-jwt.version>
 
-    <spring-boot.version>3.4.9</spring-boot.version>
+    <spring-boot.version>3.4.10</spring-boot.version>
     <spring-cloud-gateway.version>4.2.5</spring-cloud-gateway.version>
 
     <openjpa.version>4.1.1</openjpa.version>
@@ -429,7 +429,7 @@ under the License.
 
     <disruptor.version>4.0.0</disruptor.version>
 
-    <elasticsearch.version>9.1.3</elasticsearch.version>
+    <elasticsearch.version>9.1.4</elasticsearch.version>
     <opensearch.version>3.2.0</opensearch.version>
     <opensearch-java.version>3.2.0</opensearch-java.version>
 
@@ -446,7 +446,7 @@ under the License.
     <cas.version>7.2.6</cas.version>
     <cas-client.version>4.0.4</cas-client.version>
 
-    <swagger-core.version>2.2.36</swagger-core.version>
+    <swagger-core.version>2.2.37</swagger-core.version>
     <swagger-ui.version>5.28.0</swagger-ui.version>
 
     <jquery-slimscroll.version>1.3.8</jquery-slimscroll.version>
@@ -984,6 +984,11 @@ under the License.
         <artifactId>groovy-groovysh</artifactId>
         <version>${groovy.version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.apache.groovy</groupId>
+        <artifactId>groovy-nio</artifactId>
+        <version>${groovy.version}</version>
+      </dependency>
 
       <dependency>
         <groupId>net.tirasa</groupId>
@@ -1700,7 +1705,7 @@ under the License.
         <plugin>
           <groupId>org.openapitools</groupId>
           <artifactId>openapi-generator-maven-plugin</artifactId>
-          <version>7.14.0</version>
+          <version>7.15.0</version>
         </plugin>
 
         <plugin>
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 77a8240a4a..7ce97dcf7e 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
@@ -19,6 +19,7 @@
 package org.apache.syncope.wa.starter.config;
 
 import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
 import com.warrenstrange.googleauth.IGoogleAuthenticator;
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.oas.models.info.Contact;
@@ -60,6 +61,7 @@ import 
org.apache.syncope.wa.starter.mapping.TimeBasedAccessMapper;
 import 
org.apache.syncope.wa.starter.mfa.WAMultifactorAuthenticationTrustStorage;
 import org.apache.syncope.wa.starter.oidc.WAOIDCJWKSGeneratorService;
 import org.apache.syncope.wa.starter.pac4j.saml.WASAML2ClientCustomizer;
+import 
org.apache.syncope.wa.starter.saml.idp.metadata.WASamlIdPMetadataCacheRefresher;
 import 
org.apache.syncope.wa.starter.saml.idp.metadata.WASamlIdPMetadataGenerator;
 import 
org.apache.syncope.wa.starter.saml.idp.metadata.WASamlIdPMetadataLocator;
 import org.apache.syncope.wa.starter.services.WAServiceRegistry;
@@ -210,7 +212,7 @@ public class WAContext {
 
         return new RegisteredServiceMapper(
                 
Optional.ofNullable(casProperties.getAuthn().getPac4j().getCore().getName()).
-                    
orElseGet(DelegatedClientAuthenticationHandler.class::getSimpleName),
+                        
orElseGet(DelegatedClientAuthenticationHandler.class::getSimpleName),
                 authenticationEventExecutionPlan,
                 multifactorAuthenticationProviders,
                 authMappers,
@@ -251,6 +253,12 @@ public class WAContext {
         return new WASamlIdPMetadataGenerator(context, waRestClient);
     }
 
+    @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
+    @Bean
+    public Cache<String, SamlIdPMetadataDocument> samlIdPMetadataCache() {
+        return Caffeine.newBuilder().build();
+    }
+
     @Bean
     public SamlIdPMetadataLocator samlIdPMetadataLocator(
             @Qualifier("samlIdPMetadataGeneratorCipherExecutor")
@@ -267,6 +275,15 @@ public class WAContext {
                 waRestClient);
     }
 
+    @ConditionalOnMissingBean
+    @Bean
+    public WASamlIdPMetadataCacheRefresher samlIdPMetadataCacheRefresher(
+            @Qualifier("samlIdPMetadataCache")
+            final Cache<String, SamlIdPMetadataDocument> samlIdPMetadataCache) 
{
+
+        return new WASamlIdPMetadataCacheRefresher(samlIdPMetadataCache);
+    }
+
     @Bean
     public AuditTrailExecutionPlanConfigurer auditConfigurer(final 
WARestClient waRestClient) {
         return plan -> plan.registerAuditTrailManager(new 
WAAuditTrailManager(waRestClient));
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java
index 6642470c98..29ff3cda7f 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/SAML2SPClientAppTOMapper.java
@@ -22,6 +22,7 @@ import java.util.Optional;
 import org.apache.syncope.common.lib.to.ClientAppTO;
 import org.apache.syncope.common.lib.to.SAML2SPClientAppTO;
 import org.apache.syncope.common.lib.wa.WAClientApp;
+import org.apache.syncope.common.rest.api.service.SAML2IdPEntityService;
 import org.apereo.cas.configuration.support.TriStateBoolean;
 import org.apereo.cas.services.RegisteredService;
 import org.apereo.cas.services.RegisteredServiceAccessStrategy;
@@ -59,6 +60,10 @@ public class SAML2SPClientAppTOMapper extends 
AbstractClientAppMapper {
 
         service.setServiceId(sp.getEntityId());
 
+        if (sp.getIdp() != null && 
!SAML2IdPEntityService.DEFAULT_OWNER.equals(sp.getIdp())) {
+            service.setIdpMetadataLocation(sp.getIdp());
+        }
+
         service.setMetadataLocation(sp.getMetadataLocation());
         
service.setMetadataSignatureLocation(sp.getMetadataSignatureLocation());
         
service.setSignAssertions(TriStateBoolean.fromBoolean(sp.isSignAssertions()));
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataCacheRefresher.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataCacheRefresher.java
new file mode 100644
index 0000000000..d34134653f
--- /dev/null
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataCacheRefresher.java
@@ -0,0 +1,45 @@
+/*
+ * 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.saml.idp.metadata;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import 
org.apereo.cas.support.saml.services.idp.metadata.SamlIdPMetadataDocument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import 
org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.annotation.Async;
+
+public class WASamlIdPMetadataCacheRefresher {
+
+    protected static final Logger LOG = 
LoggerFactory.getLogger(WASamlIdPMetadataCacheRefresher.class);
+
+    protected final Cache<String, SamlIdPMetadataDocument> metadataCache;
+
+    public WASamlIdPMetadataCacheRefresher(final Cache<String, 
SamlIdPMetadataDocument> metadataCache) {
+        this.metadataCache = metadataCache;
+    }
+
+    @EventListener(RefreshScopeRefreshedEvent.class)
+    @Async
+    public void onRefresh(final RefreshScopeRefreshedEvent event) {
+        LOG.info("Cleaning up SAML IdP cache");
+        metadataCache.invalidateAll();
+    }
+}
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataGenerator.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataGenerator.java
index a4cd354edf..08261c2816 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataGenerator.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataGenerator.java
@@ -49,7 +49,8 @@ public class WASamlIdPMetadataGenerator extends 
BaseSamlIdPMetadataGenerator {
     @Override
     public String getAppliesToFor(final Optional<SamlRegisteredService> 
registeredService) {
         return registeredService.
-                map(SamlRegisteredService::getName).
+                map(SamlRegisteredService::getIdpMetadataLocation).
+                flatMap(Optional::ofNullable).
                 orElse(SAML2IdPEntityService.DEFAULT_OWNER);
     }
 
diff --git 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataLocator.java
 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataLocator.java
index 8b1a6a79db..9a682e1065 100644
--- 
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataLocator.java
+++ 
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/saml/idp/metadata/WASamlIdPMetadataLocator.java
@@ -51,17 +51,26 @@ public class WASamlIdPMetadataLocator extends 
AbstractSamlIdPMetadataLocator {
         this.waRestClient = waRestClient;
     }
 
+    @Override
+    public String getAppliesToFor(final Optional<SamlRegisteredService> 
registeredService) {
+        return registeredService.
+                map(SamlRegisteredService::getIdpMetadataLocation).
+                flatMap(Optional::ofNullable).
+                orElse(SAML2IdPEntityService.DEFAULT_OWNER);
+    }
+
     protected SAML2IdPEntityTO fetchFromCore(final 
Optional<SamlRegisteredService> registeredService) {
         SAML2IdPEntityService idpEntityService = 
waRestClient.getService(SAML2IdPEntityService.class);
 
         SAML2IdPEntityTO result = null;
         try {
-            result = idpEntityService.get(registeredService.
-                    map(SamlRegisteredService::getName).
-                    orElse(SAML2IdPEntityService.DEFAULT_OWNER));
+            result = idpEntityService.get(getAppliesToFor(registeredService));
         } catch (SyncopeClientException e) {
             if (e.getType() == ClientExceptionType.NotFound && 
registeredService.isPresent()) {
-                result = 
idpEntityService.get(SAML2IdPEntityService.DEFAULT_OWNER);
+                String idp = registeredService.get().getIdpMetadataLocation();
+                if (idp == null || 
SAML2IdPEntityService.DEFAULT_OWNER.equals(idp)) {
+                    result = 
idpEntityService.get(SAML2IdPEntityService.DEFAULT_OWNER);
+                }
             } else {
                 throw e;
             }
@@ -71,14 +80,7 @@ public class WASamlIdPMetadataLocator extends 
AbstractSamlIdPMetadataLocator {
     }
 
     @Override
-    public String getAppliesToFor(final Optional<SamlRegisteredService> 
registeredService) {
-        return registeredService.
-                map(SamlRegisteredService::getName).
-                orElse(SAML2IdPEntityService.DEFAULT_OWNER);
-    }
-
-    @Override
-    public SamlIdPMetadataDocument fetchInternal(final 
Optional<SamlRegisteredService> registeredService) {
+    protected SamlIdPMetadataDocument fetchInternal(final 
Optional<SamlRegisteredService> registeredService) {
         try {
             LOG.info("Locating SAML2 IdP metadata document");
 
@@ -114,8 +116,7 @@ public class WASamlIdPMetadataLocator extends 
AbstractSamlIdPMetadataLocator {
             LOG.warn("Not a valid SAML2 IdP metadata document");
             return null;
         } catch (Exception e) {
-            if (e instanceof final SyncopeClientException 
syncopeClientException
-                    && syncopeClientException.getType() == 
ClientExceptionType.NotFound) {
+            if (e instanceof final SyncopeClientException sce && sce.getType() 
== ClientExceptionType.NotFound) {
                 LOG.info(e.getMessage());
             } else {
                 if (LOG.isDebugEnabled()) {
@@ -128,4 +129,25 @@ public class WASamlIdPMetadataLocator extends 
AbstractSamlIdPMetadataLocator {
 
         return null;
     }
+
+    @Override
+    public SamlIdPMetadataDocument fetch(final Optional<SamlRegisteredService> 
registeredService) {
+        String key = getAppliesToFor(registeredService);
+
+        return getMetadataCache().get(key, __ -> {
+            SamlIdPMetadataDocument metadataDocument = 
fetchInternal(registeredService);
+            if (metadataDocument != null && metadataDocument.isValid()) {
+                LOG.trace("Fetched and cached SAML IdP metadata document [{}] 
under key [{}]", metadataDocument, key);
+                return metadataDocument;
+            }
+
+            LOG.trace("SAML IdP metadata document [{}] is considered invalid", 
metadataDocument);
+            return null;
+        });
+    }
+
+    @Override
+    public boolean shouldGenerateMetadataFor(final 
Optional<SamlRegisteredService> registeredService) {
+        return registeredService.isEmpty() || fetchFromCore(registeredService) 
== null;
+    }
 }
diff --git a/wa/starter/src/main/resources/wa.properties 
b/wa/starter/src/main/resources/wa.properties
index da54a7d426..8c75802d34 100644
--- a/wa/starter/src/main/resources/wa.properties
+++ b/wa/starter/src/main/resources/wa.properties
@@ -74,7 +74,7 @@ cas.standalone.configuration-directory=${syncope.conf.dir}
 
 cas.server.name=http://localhost:8080
 cas.server.prefix=${cas.server.name}/syncope-wa
-cas.server.scope=syncope.org
+cas.server.scope=syncope.apache.org
 
 cas.logout.follow-service-redirects=true
 

Reply via email to