You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by sk...@apache.org on 2020/04/30 15:34:06 UTC
[syncope] branch master updated: [SYNCOPE-1555] Allow WA as SAMLs
IdP to fetch metadata over REST (#178)
This is an automated email from the ASF dual-hosted git repository.
skylark17 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new a77e29d [SYNCOPE-1555] Allow WA as SAMLs IdP to fetch metadata over REST (#178)
a77e29d is described below
commit a77e29d673296b43681f3e7039e55921cb5edda0
Author: Matteo <ma...@users.noreply.github.com>
AuthorDate: Thu Apr 30 17:33:58 2020 +0200
[SYNCOPE-1555] Allow WA as SAMLs IdP to fetch metadata over REST (#178)
---
.../syncope/common/lib/to/SAML2IdPMetadataTO.java | 182 +++++++++++++++++++++
.../syncope/common/lib/types/AMEntitlement.java | 6 +
.../common/rest/api/service/AuthModuleService.java | 6 +-
.../api/service/SAML2IdPMetadataConfService.java | 63 +++++++
.../rest/api/service/SAML2IdPMetadataService.java | 97 +++++++++++
.../syncope/core/logic/SAML2IdPMetadataLogic.java | 120 ++++++++++++++
.../service/SAML2IdPMetadataConfServiceImpl.java | 37 +++++
.../cxf/service/SAML2IdPMetadataServiceImpl.java | 54 ++++++
.../api/dao/auth/SAML2IdPMetadataDAO.java | 32 ++++
.../api/entity/auth/SAML2IdPMetadata.java | 49 ++++++
.../jpa/dao/auth/JPASAML2IdPMetadataDAO.java | 62 +++++++
.../persistence/jpa/entity/JPAEntityFactory.java | 4 +
.../jpa/entity/auth/JPASAML2IdPMetadata.java | 119 ++++++++++++++
.../jpa/inner/SAML2IdPMetadataTest.java | 84 ++++++++++
.../api/data/SAML2IdPMetadataBinder.java | 32 ++++
.../java/data/SAML2IdPMetadataBinderImpl.java | 80 +++++++++
.../org/apache/syncope/fit/AbstractITCase.java | 21 +++
.../syncope/fit/core/SAML2IdPMetadataITCase.java | 113 +++++++++++++
.../java/org/apache/syncope/wa/WARestClient.java | 14 +-
.../bootstrap/SyncopeWABootstrapConfiguration.java | 1 +
.../bootstrap/SyncopeWAPropertySourceLocator.java | 2 +-
.../metadata/RestfulSamlIdPMetadataGenerator.java | 97 +++++++++++
.../metadata/RestfulSamlIdPMetadataLocator.java | 97 +++++++++++
.../syncope/wa/starter/SyncopeWAApplication.java | 32 +---
.../syncope/wa/starter/SyncopeWAConfiguration.java | 49 +++++-
.../wa/starter/SyncopeWARefreshContextJob.java | 64 ++++++++
26 files changed, 1479 insertions(+), 38 deletions(-)
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPMetadataTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPMetadataTO.java
new file mode 100644
index 0000000..54c0030
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/SAML2IdPMetadataTO.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import javax.ws.rs.PathParam;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.BaseBean;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "saml2idpMetadata")
+@XmlType
+public class SAML2IdPMetadataTO extends BaseBean implements EntityTO {
+
+ private static final long serialVersionUID = 7215073386484048953L;
+
+ private String key;
+
+ private String metadata;
+
+ private String signingCertificate;
+
+ private String signingKey;
+
+ private String encryptionCertificate;
+
+ private String encryptionKey;
+
+ private String appliesTo;
+
+ public static class Builder {
+
+ private final SAML2IdPMetadataTO instance = new SAML2IdPMetadataTO();
+
+ public Builder metadata(final String metadata) {
+ instance.setMetadata(metadata);
+ return this;
+ }
+
+ public Builder signingCertificate(final String signingCertificate) {
+ instance.setSigningCertificate(signingCertificate);
+ return this;
+ }
+
+ public Builder signingKey(final String signingKey) {
+ instance.setSigningKey(signingKey);
+ return this;
+ }
+
+ public Builder encryptionCertificate(final String encryptionCertificate) {
+ instance.setEncryptionCertificate(encryptionCertificate);
+ return this;
+ }
+
+ public Builder encryptionKey(final String encryptionKey) {
+ instance.setEncryptionKey(encryptionKey);
+ return this;
+ }
+
+ public Builder appliesTo(final String appliesTo) {
+ instance.setAppliesTo(appliesTo);
+ return this;
+ }
+
+ public SAML2IdPMetadataTO build() {
+ return instance;
+ }
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @PathParam("key")
+ @Override
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public String getMetadata() {
+ return metadata;
+ }
+
+ public void setMetadata(final String metadata) {
+ this.metadata = metadata;
+ }
+
+ public String getSigningCertificate() {
+ return signingCertificate;
+ }
+
+ public void setSigningCertificate(final String signingCertificate) {
+ this.signingCertificate = signingCertificate;
+ }
+
+ public String getSigningKey() {
+ return signingKey;
+ }
+
+ public void setSigningKey(final String signingKey) {
+ this.signingKey = signingKey;
+ }
+
+ public String getEncryptionCertificate() {
+ return encryptionCertificate;
+ }
+
+ public void setEncryptionCertificate(final String encryptionCertificate) {
+ this.encryptionCertificate = encryptionCertificate;
+ }
+
+ public String getEncryptionKey() {
+ return encryptionKey;
+ }
+
+ public void setEncryptionKey(final String encryptionKey) {
+ this.encryptionKey = encryptionKey;
+ }
+
+ public String getAppliesTo() {
+ return appliesTo;
+ }
+
+ public void setAppliesTo(final String appliesTo) {
+ this.appliesTo = appliesTo;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ SAML2IdPMetadataTO other = (SAML2IdPMetadataTO) obj;
+ return new EqualsBuilder().
+ append(key, other.key).
+ append(metadata, other.metadata).
+ append(encryptionCertificate, other.encryptionCertificate).
+ append(encryptionKey, other.encryptionKey).
+ append(signingCertificate, other.signingCertificate).
+ append(signingKey, other.signingKey).
+ append(appliesTo, other.appliesTo).
+ build();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder().
+ append(key).
+ append(metadata).
+ append(encryptionCertificate).
+ append(encryptionKey).
+ append(signingCertificate).
+ append(signingKey).
+ append(appliesTo).
+ build();
+ }
+
+}
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 5938dd9..eefe798 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
@@ -54,6 +54,12 @@ public final class AMEntitlement {
public static final String AUTH_MODULE_DELETE = "AUTH_MODULE_DELETE";
+ public static final String SAML2_IDP_METADATA_CREATE = "SAML2_IDP_METADATA_CREATE";
+
+ public static final String SAML2_IDP_METADATA_UPDATE = "SAML2_IDP_METADATA_UPDATE";
+
+ public static final String SAML2_IDP_METADATA_READ = "SAML2_IDP_METADATA_READ";
+
private static final Set<String> VALUES;
static {
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
index d617b42..57cce16 100644
--- a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
@@ -65,9 +65,9 @@ public interface AuthModuleService extends JAXRSService {
AuthModuleTO read(@NotNull @PathParam("key") String key);
/**
- * Returns a list of authentication modules of the matching type.
+ * Returns a list of authentication modules.
*
- * @return list of authentication modules with matching type
+ * @return list of authentication modules
*/
@GET
@Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
@@ -76,7 +76,7 @@ public interface AuthModuleService extends JAXRSService {
/**
* Create a new authentication module.
*
- * @param authModuleTO AuthModule to be created (needs to match type)
+ * @param authModuleTO AuthModule to be created.
* @return Response object featuring Location header of created authentication module
*/
@ApiResponses(
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPMetadataConfService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPMetadataConfService.java
new file mode 100644
index 0000000..fafe273
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPMetadataConfService.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for SAML 2.0 IdP metadata.
+ */
+@Tag(name = "SAML 2.0 IdP Metadata")
+@SecurityRequirements({
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer") })
+@Path("saml2idp/conf/metadata")
+public interface SAML2IdPMetadataConfService extends JAXRSService {
+
+ /**
+ * Updates SAML 2.0 IdP metadata matching the given key.
+ *
+ * @param saml2IdPMetadataTO SAML2IdPMetadata to replace existing SAML 2.0 IdP metadata
+ */
+ @Parameter(name = "key", description = "SAML2IdPMetadata's key", in = ParameterIn.PATH, schema =
+ @Schema(type = "string"))
+ @ApiResponses(
+ @ApiResponse(responseCode = "204", description = "Operation was successful"))
+ @PUT
+ @Path("{key}")
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ void update(@NotNull SAML2IdPMetadataTO saml2IdPMetadataTO);
+
+}
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPMetadataService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPMetadataService.java
new file mode 100644
index 0000000..7791372
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/SAML2IdPMetadataService.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for SAML 2.0 IdP metadata.
+ */
+@Tag(name = "SAML 2.0 IdP Metadata")
+@SecurityRequirements({
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer") })
+@Path("saml2idp/metadata")
+public interface SAML2IdPMetadataService extends JAXRSService {
+
+ /**
+ * Returns a document outlining keys and metadata of Syncope as SAML 2.0 IdP.
+ *
+ * @param appliesTo indicates the SAML 2.0 IdP metadata document owner and applicability, where a value of 'Syncope'
+ * indicates the Syncope server as the global owner of the metadata and keys.
+ * @return SAML 2.0 IdP metadata
+ */
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ SAML2IdPMetadataTO get(@QueryParam("appliesTo") @DefaultValue("Syncope") String appliesTo);
+
+ /**
+ * Returns the SAML 2.0 IdP metadata matching the given key.
+ *
+ * @param key key of requested SAML 2.0 IdP metadata
+ * @return SAML 2.0 IdP metadata with matching id
+ */
+ @GET
+ @Path("{key}")
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ SAML2IdPMetadataTO read(@NotNull @PathParam("key") String key);
+
+ /**
+ * Store the metadata and keys to finalize the metadata generation process.
+ *
+ * @param saml2IdPMetadataTO SAML2IdPMetadata to be created
+ * @return Response object featuring Location header of created SAML 2.0 IdP metadata
+ */
+ @ApiResponses({
+ @ApiResponse(responseCode = "201",
+ description = "SAML2IdPMetadata successfully created", headers = {
+ @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+ @Schema(type = "string"),
+ description = "UUID generated for the entity created"),
+ @Header(name = HttpHeaders.LOCATION, schema =
+ @Schema(type = "string"),
+ description = "URL of the entity created") }),
+ @ApiResponse(responseCode = "409",
+ description = "Metadata already existing") })
+ @POST
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ Response set(@NotNull SAML2IdPMetadataTO saml2IdPMetadataTO);
+
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java
new file mode 100644
index 0000000..a6f4930
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java
@@ -0,0 +1,120 @@
+/*
+ * 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.logic;
+
+import static org.apache.syncope.core.logic.AbstractLogic.LOG;
+
+import java.lang.reflect.Method;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.SAML2IdPMetadataDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.provisioning.api.data.SAML2IdPMetadataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+public class SAML2IdPMetadataLogic extends AbstractTransactionalLogic<SAML2IdPMetadataTO> {
+
+ @Autowired
+ private SAML2IdPMetadataBinder binder;
+
+ @Autowired
+ private SAML2IdPMetadataDAO saml2IdPMetadataDAO;
+
+ @PreAuthorize("hasRole('" + AMEntitlement.SAML2_IDP_METADATA_READ + "') "
+ + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+ @Transactional(readOnly = true)
+ public SAML2IdPMetadataTO read(final String key) {
+ SAML2IdPMetadata sAML2IdPMetadata = saml2IdPMetadataDAO.find(key);
+ if (sAML2IdPMetadata == null) {
+ throw new NotFoundException("AuthModule " + key + " not found");
+ }
+
+ return binder.getSAML2IdPMetadataTO(sAML2IdPMetadata);
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.SAML2_IDP_METADATA_READ + "') "
+ + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+ @Transactional(readOnly = true)
+ public SAML2IdPMetadataTO get(final String appliesTo) {
+ SAML2IdPMetadata saml2IdPMetadata = saml2IdPMetadataDAO.findByOwner(appliesTo);
+ if (saml2IdPMetadata == null) {
+ throw new NotFoundException("SAML2 IdP Metadata owned by " + appliesTo + " not found");
+ }
+
+ return binder.getSAML2IdPMetadataTO(saml2IdPMetadata);
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.SAML2_IDP_METADATA_CREATE + "') "
+ + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+ public SAML2IdPMetadataTO set(final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+ SAML2IdPMetadata saml2IdPMetadata = saml2IdPMetadataDAO.findByOwner(saml2IdPMetadataTO.getAppliesTo());
+ if (saml2IdPMetadata == null) {
+ return binder.getSAML2IdPMetadataTO(saml2IdPMetadataDAO.save(binder.create(saml2IdPMetadataTO)));
+ }
+
+ throw SyncopeClientException.build(ClientExceptionType.EntityExists);
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.SAML2_IDP_METADATA_UPDATE + "')")
+ public SAML2IdPMetadataTO update(final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+ SAML2IdPMetadata authModule = saml2IdPMetadataDAO.findByOwner(saml2IdPMetadataTO.getAppliesTo());
+ if (authModule == null) {
+ throw new NotFoundException("AuthModule " + saml2IdPMetadataTO.getKey() + " not found");
+ }
+
+ return binder.getSAML2IdPMetadataTO(saml2IdPMetadataDAO.save(binder.update(authModule, saml2IdPMetadataTO)));
+ }
+
+ @Override
+ protected SAML2IdPMetadataTO resolveReference(final Method method, final Object... args)
+ throws UnresolvedReferenceException {
+
+ String appliesTo = null;
+
+ if (ArrayUtils.isNotEmpty(args)) {
+ for (int i = 0; appliesTo == null && i < args.length; i++) {
+ if (args[i] instanceof String) {
+ appliesTo = (String) args[i];
+ } else if (args[i] instanceof SAML2IdPMetadataTO) {
+ appliesTo = ((SAML2IdPMetadataTO) args[i]).getKey();
+ }
+ }
+ }
+
+ if (appliesTo != null) {
+ try {
+ return binder.getSAML2IdPMetadataTO(saml2IdPMetadataDAO.findByOwner(appliesTo));
+ } catch (Throwable ignore) {
+ LOG.debug("Unresolved reference", ignore);
+ throw new UnresolvedReferenceException(ignore);
+ }
+ }
+
+ throw new UnresolvedReferenceException();
+ }
+}
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPMetadataConfServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPMetadataConfServiceImpl.java
new file mode 100644
index 0000000..12b6cb6
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPMetadataConfServiceImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.rest.cxf.service;
+
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.rest.api.service.SAML2IdPMetadataConfService;
+import org.apache.syncope.core.logic.SAML2IdPMetadataLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SAML2IdPMetadataConfServiceImpl extends AbstractServiceImpl implements SAML2IdPMetadataConfService {
+
+ @Autowired
+ private SAML2IdPMetadataLogic logic;
+
+ @Override
+ public void update(final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+ logic.update(saml2IdPMetadataTO);
+ }
+}
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPMetadataServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPMetadataServiceImpl.java
new file mode 100644
index 0000000..b1eb6a3
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/SAML2IdPMetadataServiceImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.rest.cxf.service;
+
+import java.net.URI;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.SAML2IdPMetadataService;
+import org.apache.syncope.core.logic.SAML2IdPMetadataLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class SAML2IdPMetadataServiceImpl extends AbstractServiceImpl implements SAML2IdPMetadataService {
+
+ @Autowired
+ private SAML2IdPMetadataLogic logic;
+
+ @Override
+ public SAML2IdPMetadataTO get(final String appliesTo) {
+ return logic.get(appliesTo);
+ }
+
+ @Override
+ public SAML2IdPMetadataTO read(final String key) {
+ return logic.read(key);
+ }
+
+ @Override
+ public Response set(final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+ SAML2IdPMetadataTO saml2IdPMetadata = logic.set(saml2IdPMetadataTO);
+ URI location = uriInfo.getAbsolutePathBuilder().path(saml2IdPMetadata.getKey()).build();
+ return Response.created(location).
+ header(RESTHeaders.RESOURCE_KEY, saml2IdPMetadata.getKey()).
+ build();
+ }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2IdPMetadataDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2IdPMetadataDAO.java
new file mode 100644
index 0000000..b17688b
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2IdPMetadataDAO.java
@@ -0,0 +1,32 @@
+/*
+ * 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.api.dao.auth;
+
+import org.apache.syncope.core.persistence.api.dao.DAO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+
+public interface SAML2IdPMetadataDAO extends DAO<SAML2IdPMetadata> {
+
+ SAML2IdPMetadata find(String key);
+
+ SAML2IdPMetadata findByOwner(String appliesTo);
+
+ SAML2IdPMetadata save(SAML2IdPMetadata saml2IdPMetadata);
+
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2IdPMetadata.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2IdPMetadata.java
new file mode 100644
index 0000000..a91eb61
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2IdPMetadata.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.entity.auth;
+
+import org.apache.syncope.core.persistence.api.entity.Entity;
+
+public interface SAML2IdPMetadata extends Entity {
+
+ String getMetadata();
+
+ void setMetadata(String metadata);
+
+ String getSigningCertificate();
+
+ void setSigningCertificate(String signingCertificate);
+
+ String getSigningKey();
+
+ void setSigningKey(String signingKey);
+
+ String getEncryptionCertificate();
+
+ void setEncryptionCertificate(String encryptionCertificate);
+
+ String getEncryptionKey();
+
+ void setEncryptionKey(String encryptionKey);
+
+ String getAppliesTo();
+
+ void setAppliesTo(String appliesTo);
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2IdPMetadataDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2IdPMetadataDAO.java
new file mode 100644
index 0000000..848679e
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2IdPMetadataDAO.java
@@ -0,0 +1,62 @@
+/*
+ * 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.jpa.dao.auth;
+
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.persistence.api.dao.auth.SAML2IdPMetadataDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2IdPMetadata;
+
+@Repository
+public class JPASAML2IdPMetadataDAO extends AbstractDAO<SAML2IdPMetadata> implements SAML2IdPMetadataDAO {
+
+ @Transactional(readOnly = true)
+ @Override
+ public SAML2IdPMetadata find(final String key) {
+ return entityManager().find(JPASAML2IdPMetadata.class, key);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public SAML2IdPMetadata findByOwner(final String appliesTo) {
+ TypedQuery<SAML2IdPMetadata> query = entityManager().createQuery(
+ "SELECT e FROM " + JPASAML2IdPMetadata.class.getSimpleName() + " e WHERE e.appliesTo=:appliesTo",
+ SAML2IdPMetadata.class);
+ query.setParameter("appliesTo", appliesTo);
+
+ SAML2IdPMetadata result = null;
+ try {
+ result = query.getSingleResult();
+ } catch (final NoResultException e) {
+ LOG.debug("No SAML2 IdP Metadata found with appliesTo = {}", appliesTo);
+ }
+
+ return result;
+ }
+
+ @Override
+ public SAML2IdPMetadata save(final SAML2IdPMetadata saml2IdPMetadata) {
+ return entityManager().merge(saml2IdPMetadata);
+ }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index a034449..28e6e66 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -59,6 +59,7 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrUniqueValue;
@@ -155,6 +156,7 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModuleItem;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2IdPMetadata;
public class JPAEntityFactory implements EntityFactory {
@@ -321,6 +323,8 @@ public class JPAEntityFactory implements EntityFactory {
result = (E) new JPAOIDCRP();
} else if (reference.equals(SAML2SP.class)) {
result = (E) new JPASAML2SP();
+ } else if (reference.equals(SAML2IdPMetadata.class)) {
+ result = (E) new JPASAML2IdPMetadata();
} else {
throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2IdPMetadata.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2IdPMetadata.java
new file mode 100644
index 0000000..1bb0130
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2IdPMetadata.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity.auth;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+import javax.persistence.Table;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
+
+@Entity
+@Table(name = JPASAML2IdPMetadata.TABLE)
+public class JPASAML2IdPMetadata extends AbstractGeneratedKeyEntity implements SAML2IdPMetadata {
+
+ public static final String TABLE = "SAML2IdPMetadata";
+
+ private static final long serialVersionUID = 57352617217394093L;
+
+ @Column(unique = true)
+ private String appliesTo;
+
+ @Lob
+ @Column
+ private String metadata;
+
+ @Lob
+ @Column
+ private String signingCertificate;
+
+ @Lob
+ @Column
+ private String signingKey;
+
+ @Lob
+ @Column
+ private String encryptionCertificate;
+
+ @Lob
+ @Column
+ private String encryptionKey;
+
+ @Override
+ public String getMetadata() {
+ return metadata;
+ }
+
+ @Override
+ public void setMetadata(final String metadata) {
+ this.metadata = metadata;
+ }
+
+ @Override
+ public String getSigningCertificate() {
+ return signingCertificate;
+ }
+
+ @Override
+ public void setSigningCertificate(final String signingCertificate) {
+ this.signingCertificate = signingCertificate;
+ }
+
+ @Override
+ public String getSigningKey() {
+ return signingKey;
+ }
+
+ @Override
+ public void setSigningKey(final String signingKey) {
+ this.signingKey = signingKey;
+ }
+
+ @Override
+ public String getEncryptionCertificate() {
+ return encryptionCertificate;
+ }
+
+ @Override
+ public void setEncryptionCertificate(final String encryptionCertificate) {
+ this.encryptionCertificate = encryptionCertificate;
+ }
+
+ @Override
+ public String getEncryptionKey() {
+ return encryptionKey;
+ }
+
+ @Override
+ public void setEncryptionKey(final String encryptionKey) {
+ this.encryptionKey = encryptionKey;
+ }
+
+ @Override
+ public String getAppliesTo() {
+ return appliesTo;
+ }
+
+ @Override
+ public void setAppliesTo(final String appliesTo) {
+ this.appliesTo = appliesTo;
+ }
+
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2IdPMetadataTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2IdPMetadataTest.java
new file mode 100644
index 0000000..8d47b48
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2IdPMetadataTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.jpa.inner;
+
+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 java.util.UUID;
+import org.apache.syncope.core.persistence.api.dao.auth.SAML2IdPMetadataDAO;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+
+@Transactional("Master")
+public class SAML2IdPMetadataTest extends AbstractTest {
+
+ @Autowired
+ private SAML2IdPMetadataDAO saml2IdPMetadataDAO;
+
+ @Test
+ public void find() {
+ create("Syncope");
+ SAML2IdPMetadata saml2IdPMetadata = saml2IdPMetadataDAO.findByOwner("Syncope");
+ assertNotNull(saml2IdPMetadata);
+
+ saml2IdPMetadata = saml2IdPMetadataDAO.findByOwner(UUID.randomUUID().toString());
+ assertNull(saml2IdPMetadata);
+ }
+
+ @Test
+ public void save() {
+ create("SyncopeCreate");
+ }
+
+ @Test
+ public void update() {
+ SAML2IdPMetadata saml2IdPMetadata = create("SyncopeUpdate");
+ assertNotNull(saml2IdPMetadata);
+ saml2IdPMetadata.setAppliesTo("OtherSyncope");
+
+ saml2IdPMetadata = saml2IdPMetadataDAO.save(saml2IdPMetadata);
+ assertNotNull(saml2IdPMetadata);
+ assertNotNull(saml2IdPMetadata.getKey());
+ SAML2IdPMetadata found = saml2IdPMetadataDAO.findByOwner(saml2IdPMetadata.getAppliesTo());
+ assertNotNull(found);
+ assertEquals("OtherSyncope", found.getAppliesTo());
+ }
+
+ private SAML2IdPMetadata create(final String appliesTo) {
+ SAML2IdPMetadata saml2IdPMetadata = entityFactory.newEntity(SAML2IdPMetadata.class);
+ saml2IdPMetadata.setAppliesTo(appliesTo);
+ saml2IdPMetadata.setMetadata("metadata");
+ saml2IdPMetadata.setEncryptionCertificate("encryptionCert");
+ saml2IdPMetadata.setEncryptionKey("encryptionKey");
+ saml2IdPMetadata.setSigningCertificate("signatureCert");
+ saml2IdPMetadata.setSigningKey("signatureKey");
+ saml2IdPMetadataDAO.save(saml2IdPMetadata);
+ assertNotNull(saml2IdPMetadata);
+ assertNotNull(saml2IdPMetadata.getKey());
+ assertNotNull(saml2IdPMetadataDAO.findByOwner(saml2IdPMetadata.getAppliesTo()));
+
+ return saml2IdPMetadata;
+ }
+
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
new file mode 100644
index 0000000..e2e190e
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
@@ -0,0 +1,32 @@
+/*
+ * 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.provisioning.api.data;
+
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+
+public interface SAML2IdPMetadataBinder {
+
+ SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
+
+ SAML2IdPMetadata update(SAML2IdPMetadata saml2IdPMetadata, SAML2IdPMetadataTO saml2IdPMetadataTO);
+
+ SAML2IdPMetadataTO getSAML2IdPMetadataTO(SAML2IdPMetadata saml2IdPMetadata);
+
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataBinderImpl.java
new file mode 100644
index 0000000..1f4d6fa
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataBinderImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.provisioning.java.data;
+
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.provisioning.api.data.SAML2IdPMetadataBinder;
+
+@Component
+public class SAML2IdPMetadataBinderImpl implements SAML2IdPMetadataBinder {
+
+ @Autowired
+ private EntityFactory entityFactory;
+
+ private SAML2IdPMetadata getSAML2IdPMetadata(
+ final SAML2IdPMetadata saml2IdPMetadata,
+ final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+
+ SAML2IdPMetadata saml2IdPMetadataResult = saml2IdPMetadata;
+ if (saml2IdPMetadataResult == null) {
+ saml2IdPMetadataResult = entityFactory.newEntity(SAML2IdPMetadata.class);
+ }
+
+ saml2IdPMetadataResult.setEncryptionCertificate(saml2IdPMetadataTO.getEncryptionCertificate());
+ saml2IdPMetadataResult.setEncryptionKey(saml2IdPMetadataTO.getEncryptionKey());
+ saml2IdPMetadataResult.setMetadata(saml2IdPMetadataTO.getMetadata());
+ saml2IdPMetadataResult.setSigningCertificate(saml2IdPMetadataTO.getSigningCertificate());
+ saml2IdPMetadataResult.setSigningKey(saml2IdPMetadataTO.getSigningKey());
+ saml2IdPMetadataResult.setAppliesTo(saml2IdPMetadataTO.getAppliesTo());
+
+ return saml2IdPMetadataResult;
+ }
+
+ @Override
+ public SAML2IdPMetadata create(final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+ return update(entityFactory.newEntity(SAML2IdPMetadata.class), saml2IdPMetadataTO);
+ }
+
+ @Override
+ public SAML2IdPMetadata update(
+ final SAML2IdPMetadata saml2IdPMetadata,
+ final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+
+ return getSAML2IdPMetadata(saml2IdPMetadata, saml2IdPMetadataTO);
+ }
+
+ @Override
+ public SAML2IdPMetadataTO getSAML2IdPMetadataTO(final SAML2IdPMetadata saml2IdPMetadata) {
+ SAML2IdPMetadataTO saml2IdPMetadataTO = new SAML2IdPMetadataTO();
+
+ saml2IdPMetadataTO.setKey(saml2IdPMetadata.getKey());
+ saml2IdPMetadataTO.setMetadata(saml2IdPMetadata.getMetadata());
+ saml2IdPMetadataTO.setEncryptionCertificate(saml2IdPMetadata.getEncryptionCertificate());
+ saml2IdPMetadataTO.setEncryptionKey(saml2IdPMetadata.getEncryptionKey());
+ saml2IdPMetadataTO.setSigningCertificate(saml2IdPMetadata.getSigningCertificate());
+ saml2IdPMetadataTO.setSigningKey(saml2IdPMetadata.getSigningKey());
+ saml2IdPMetadataTO.setAppliesTo(saml2IdPMetadata.getAppliesTo());
+
+ return saml2IdPMetadataTO;
+ }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index ee19a6b..530113f 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -77,6 +77,7 @@ import org.apache.syncope.common.lib.to.RoleTO;
import org.apache.syncope.common.lib.to.UserTO;
import org.apache.syncope.common.lib.to.AuthModuleTO;
import org.apache.syncope.common.lib.to.AuthPolicyTO;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
import org.apache.syncope.common.lib.to.client.ClientAppTO;
import org.apache.syncope.common.lib.to.client.OIDCRPTO;
import org.apache.syncope.common.lib.to.client.SAML2SPTO;
@@ -128,6 +129,8 @@ import org.apache.syncope.common.rest.api.service.UserService;
import org.apache.syncope.common.rest.api.service.UserRequestService;
import org.apache.syncope.common.rest.api.service.BpmnProcessService;
import org.apache.syncope.common.rest.api.service.GatewayRouteService;
+import org.apache.syncope.common.rest.api.service.SAML2IdPMetadataConfService;
+import org.apache.syncope.common.rest.api.service.SAML2IdPMetadataService;
import org.apache.syncope.common.rest.api.service.UserWorkflowTaskService;
import org.apache.syncope.fit.core.CoreITContext;
import org.apache.syncope.fit.core.UserITCase;
@@ -280,6 +283,10 @@ public abstract class AbstractITCase {
protected static AuthModuleService authModuleService;
+ protected static SAML2IdPMetadataService saml2IdPMetadataService;
+
+ protected static SAML2IdPMetadataConfService saml2IdPMetadataConfService;
+
protected static SecurityQuestionService securityQuestionService;
protected static ImplementationService implementationService;
@@ -373,6 +380,8 @@ public abstract class AbstractITCase {
scimConfService = adminClient.getService(SCIMConfService.class);
clientAppService = adminClient.getService(ClientAppService.class);
authModuleService = adminClient.getService(AuthModuleService.class);
+ saml2IdPMetadataService = adminClient.getService(SAML2IdPMetadataService.class);
+ saml2IdPMetadataConfService = adminClient.getService(SAML2IdPMetadataConfService.class);
}
@Autowired
@@ -587,6 +596,18 @@ public abstract class AbstractITCase {
return getObject(response.getLocation(), AuthModuleService.class, authModule.getClass());
}
+ @SuppressWarnings("unchecked")
+ protected SAML2IdPMetadataTO createSAML2IdPMetadata(final SAML2IdPMetadataTO saml2IdPMetadata) {
+ Response response = saml2IdPMetadataService.set(saml2IdPMetadata);
+ if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+ Exception ex = clientFactory.getExceptionMapper().fromResponse(response);
+ if (ex != null) {
+ throw (RuntimeException) ex;
+ }
+ }
+ return getObject(response.getLocation(), SAML2IdPMetadataService.class, saml2IdPMetadata.getClass());
+ }
+
protected ResourceTO createResource(final ResourceTO resourceTO) {
Response response = resourceService.create(resourceTO);
if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2IdPMetadataITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2IdPMetadataITCase.java
new file mode 100644
index 0000000..58facb8
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/SAML2IdPMetadataITCase.java
@@ -0,0 +1,113 @@
+/*
+ * 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.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.junit.platform.commons.util.StringUtils;
+
+public class SAML2IdPMetadataITCase extends AbstractITCase {
+
+ private static final String APPLIES_TO = "Syncope";
+
+ private SAML2IdPMetadataTO createSAML2IdPMetadata() {
+ SAML2IdPMetadataTO result = createSAML2IdPMetadata(new SAML2IdPMetadataTO.Builder().
+ appliesTo(APPLIES_TO).
+ metadata("testMetadata").
+ encryptionCertificate("testEncryptionCert").
+ encryptionKey("testEncryptionKey").
+ signingCertificate("testSigningCert").
+ signingKey("testSigningKey").
+ build());
+ assertNotNull(result);
+ testIsValid(result);
+
+ return result;
+ }
+
+ private void testIsValid(final SAML2IdPMetadataTO saml2IdPMetadataTO) {
+ assertFalse(StringUtils.isBlank(saml2IdPMetadataTO.getAppliesTo()));
+ assertFalse(StringUtils.isBlank(saml2IdPMetadataTO.getMetadata()));
+ assertFalse(StringUtils.isBlank(saml2IdPMetadataTO.getEncryptionKey()));
+ assertFalse(StringUtils.isBlank(saml2IdPMetadataTO.getEncryptionCertificate()));
+ assertFalse(StringUtils.isBlank(saml2IdPMetadataTO.getSigningCertificate()));
+ assertFalse(StringUtils.isBlank(saml2IdPMetadataTO.getSigningKey()));
+ }
+
+ @Test
+ public void read() {
+ SAML2IdPMetadataTO saml2IdPMetadataTO = null;
+ try {
+ saml2IdPMetadataTO = saml2IdPMetadataService.get(APPLIES_TO);
+ } catch (SyncopeClientException e) {
+ saml2IdPMetadataTO = createSAML2IdPMetadata();
+ }
+
+ assertNotNull(saml2IdPMetadataTO);
+ assertEquals(APPLIES_TO, saml2IdPMetadataTO.getAppliesTo());
+ testIsValid(saml2IdPMetadataTO);
+ }
+
+ @Test
+ public void create() {
+ try {
+ saml2IdPMetadataService.get(APPLIES_TO);
+ } catch (SyncopeClientException e) {
+ createSAML2IdPMetadata();
+ }
+
+ try {
+ createSAML2IdPMetadata(new SAML2IdPMetadataTO.Builder().
+ appliesTo(APPLIES_TO).
+ metadata("testMetadata").
+ build());
+ fail("This should not happen");
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.EntityExists, e.getType());
+ }
+ }
+
+ @Test
+ public void update() {
+ SAML2IdPMetadataTO saml2IdPMetadataTO = null;
+ try {
+ saml2IdPMetadataTO = saml2IdPMetadataService.get(APPLIES_TO);
+ } catch (NotFoundException e) {
+ saml2IdPMetadataTO = createSAML2IdPMetadata();
+ }
+
+ assertNotNull(saml2IdPMetadataTO);
+ saml2IdPMetadataTO.setEncryptionKey("newKey");
+ saml2IdPMetadataConfService.update(saml2IdPMetadataTO);
+ saml2IdPMetadataTO = saml2IdPMetadataService.get(saml2IdPMetadataTO.getAppliesTo());
+ assertNotNull(saml2IdPMetadataTO);
+
+ assertEquals("newKey", saml2IdPMetadataTO.getEncryptionKey());
+ }
+
+}
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java
index ca2d048..aa323d4 100644
--- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java
@@ -19,7 +19,6 @@
package org.apache.syncope.wa;
import org.apereo.cas.util.spring.ApplicationContextProvider;
-
import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
import org.apache.syncope.client.lib.SyncopeClient;
import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
@@ -29,7 +28,6 @@ import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
-
import java.util.Collection;
public class WARestClient {
@@ -45,9 +43,9 @@ public class WARestClient {
private SyncopeClient client;
public WARestClient(
- final String anonymousUser,
- final String anonymousKey,
- final boolean useGZIPCompression) {
+ final String anonymousUser,
+ final String anonymousKey,
+ final boolean useGZIPCompression) {
this.anonymousUser = anonymousUser;
this.anonymousKey = anonymousKey;
@@ -59,9 +57,9 @@ public class WARestClient {
if (client == null && isReady()) {
try {
client = new SyncopeClientFactoryBean().
- setAddress(getCore().getAddress()).
- setUseCompression(useGZIPCompression).
- create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey));
+ setAddress(getCore().getAddress()).
+ setUseCompression(useGZIPCompression).
+ create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey));
} catch (Exception e) {
LOG.error("Could not init SyncopeClient", e);
}
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java
index 2330c3f..15498fe 100644
--- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java
@@ -30,6 +30,7 @@ import org.springframework.context.annotation.PropertySource;
@PropertySource("classpath:wa.properties")
@PropertySource(value = "file:${conf.directory}/wa.properties", ignoreResourceNotFound = true)
public class SyncopeWABootstrapConfiguration {
+
@Value("${anonymousUser}")
private String anonymousUser;
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
index 875ac90..94e50c8 100644
--- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
@@ -16,7 +16,6 @@
* specific language governing permissions and limitations
* under the License.
*/
-
package org.apache.syncope.wa.bootstrap;
import org.apereo.cas.configuration.CasConfigurationProperties;
@@ -62,6 +61,7 @@ import java.util.stream.Collectors;
@Order
public class SyncopeWAPropertySourceLocator implements PropertySourceLocator {
+
private static final Logger LOG = LoggerFactory.getLogger(SyncopeWABootstrapConfiguration.class);
private final WARestClient waRestClient;
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java b/wa/starter/src/main/java/org/apache/syncope/wa/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java
new file mode 100644
index 0000000..74bcb04
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/saml/idp/metadata/RestfulSamlIdPMetadataGenerator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.saml.idp.metadata;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.rest.api.service.SAML2IdPMetadataService;
+import org.apache.syncope.wa.WARestClient;
+import org.apereo.cas.support.saml.idp.metadata.generator.BaseSamlIdPMetadataGenerator;
+import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGeneratorConfigurationContext;
+import org.apereo.cas.support.saml.services.SamlRegisteredService;
+import org.apereo.cas.support.saml.services.idp.metadata.SamlIdPMetadataDocument;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpStatus;
+import javax.ws.rs.core.Response;
+import java.util.Optional;
+
+public class RestfulSamlIdPMetadataGenerator extends BaseSamlIdPMetadataGenerator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestfulSamlIdPMetadataGenerator.class);
+
+ public static final String DEFAULT_APPLIES_FOR = "Syncope";
+
+ private final WARestClient restClient;
+
+ public RestfulSamlIdPMetadataGenerator(
+ final SamlIdPMetadataGeneratorConfigurationContext samlIdPMetadataGeneratorConfigurationContext,
+ final WARestClient restClient) {
+
+ super(samlIdPMetadataGeneratorConfigurationContext);
+ this.restClient = restClient;
+ }
+
+ @Override
+ protected SamlIdPMetadataDocument finalizeMetadataDocument(
+ final SamlIdPMetadataDocument doc,
+ final Optional<SamlRegisteredService> registeredService) {
+
+ LOG.info("Generating new SAML2 IdP metadata document");
+ doc.setAppliesTo(DEFAULT_APPLIES_FOR);
+ SAML2IdPMetadataTO metadataTO = new SAML2IdPMetadataTO.Builder().
+ metadata(doc.getMetadata()).
+ encryptionKey(doc.getEncryptionKey()).
+ encryptionCertificate(doc.getEncryptionCertificate()).
+ signingCertificate(doc.getSigningCertificate()).
+ signingKey(doc.getSigningKey()).
+ appliesTo(doc.getAppliesTo()).
+ build();
+
+ SyncopeClient client = getSyncopeClient();
+ Response response = null;
+ try {
+ response = client.getService(SAML2IdPMetadataService.class).set(metadataTO);
+ } catch (Exception ex) {
+ LOG.warn("While generating SAML2 IdP metadata document", ex);
+ }
+
+ return response != null && HttpStatus.valueOf(response.getStatus()).is2xxSuccessful() ? doc : null;
+ }
+
+ @Override
+ public Pair<String, String> buildSelfSignedEncryptionCert(final Optional<SamlRegisteredService> registeredService) {
+ return generateCertificateAndKey();
+ }
+
+ @Override
+ public Pair<String, String> buildSelfSignedSigningCert(final Optional<SamlRegisteredService> registeredService) {
+ return generateCertificateAndKey();
+ }
+
+ private SyncopeClient getSyncopeClient() {
+ if (!WARestClient.isReady()) {
+ LOG.info("Syncope client is not yet ready");
+ throw new RuntimeException("Syncope core is not yet ready to access requests");
+ }
+ return restClient.getSyncopeClient();
+ }
+
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java b/wa/starter/src/main/java/org/apache/syncope/wa/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java
new file mode 100644
index 0000000..57592e5
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/saml/idp/metadata/RestfulSamlIdPMetadataLocator.java
@@ -0,0 +1,97 @@
+/*
+ * 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.saml.idp.metadata;
+
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.rest.api.service.SAML2IdPMetadataService;
+import org.apache.syncope.wa.WARestClient;
+import org.apereo.cas.support.saml.idp.metadata.locator.AbstractSamlIdPMetadataLocator;
+import org.apereo.cas.support.saml.services.SamlRegisteredService;
+import org.apereo.cas.support.saml.services.idp.metadata.SamlIdPMetadataDocument;
+import org.apereo.cas.util.crypto.CipherExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.util.Optional;
+
+public class RestfulSamlIdPMetadataLocator extends AbstractSamlIdPMetadataLocator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(RestfulSamlIdPMetadataLocator.class);
+
+ private final WARestClient restClient;
+
+ public RestfulSamlIdPMetadataLocator(
+ final CipherExecutor<String, String> metadataCipherExecutor,
+ final WARestClient restClient) {
+
+ super(metadataCipherExecutor);
+ this.restClient = restClient;
+ }
+
+ private static String getAppliesToFor(final Optional<SamlRegisteredService> result) {
+ if (result.isPresent()) {
+ SamlRegisteredService registeredService = result.get();
+ return registeredService.getName() + '-' + registeredService.getId();
+ }
+ return RestfulSamlIdPMetadataGenerator.DEFAULT_APPLIES_FOR;
+ }
+
+ @Override
+ public SamlIdPMetadataDocument fetchInternal(final Optional<SamlRegisteredService> registeredService) {
+ try {
+ LOG.info("Locating SAML2 IdP metadata document");
+ SAML2IdPMetadataTO saml2IdPMetadataTO = getSyncopeClient().getService(SAML2IdPMetadataService.class).
+ get(getAppliesToFor(registeredService));
+
+ if (saml2IdPMetadataTO == null) {
+ LOG.warn("No SAML2 IdP metadata document obtained from core");
+ } else {
+ SamlIdPMetadataDocument document = new SamlIdPMetadataDocument();
+ document.setMetadata(saml2IdPMetadataTO.getMetadata());
+ document.setEncryptionCertificate(saml2IdPMetadataTO.getEncryptionCertificate());
+ document.setEncryptionKey(saml2IdPMetadataTO.getEncryptionKey());
+ document.setSigningKey(saml2IdPMetadataTO.getSigningKey());
+ document.setSigningCertificate(saml2IdPMetadataTO.getSigningCertificate());
+ document.setAppliesTo(saml2IdPMetadataTO.getAppliesTo());
+ if (document.isValid()) {
+ LOG.debug("Found SAML2 IdP metadata document: {}", document.getId());
+ return document;
+ }
+ LOG.warn("Not a valid SAML2 IdP metadata document");
+ }
+
+ return null;
+ } catch (SyncopeClientException ex) {
+ if (ex.getType() == ClientExceptionType.NotFound) {
+ LOG.debug("No SAML2 IdP metadata document is available");
+ }
+ }
+ return null;
+ }
+
+ private SyncopeClient getSyncopeClient() {
+ if (!WARestClient.isReady()) {
+ LOG.info("Syncope client is not yet ready");
+ throw new RuntimeException("Syncope core is not yet ready to access requests");
+ }
+ return restClient.getSyncopeClient();
+ }
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
index 1b43386..058274a 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
@@ -18,18 +18,13 @@
*/
package org.apache.syncope.wa.starter;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.CasConfigurationPropertiesValidator;
import org.apereo.cas.util.AsciiArtUtils;
import org.apereo.cas.util.DateTimeUtils;
-import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
-import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
@@ -55,7 +50,6 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
-import org.springframework.cloud.context.refresh.ContextRefresher;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.event.EventListener;
@@ -63,6 +57,9 @@ import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
@PropertySource("classpath:wa.properties")
@PropertySource(value = "file:${conf.directory}/wa.properties", ignoreResourceNotFound = true)
@@ -90,9 +87,6 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
private static final Logger LOG = LoggerFactory.getLogger(SyncopeWAApplication.class);
@Autowired
- private ContextRefresher contextRefresher;
-
- @Autowired
private SchedulerFactoryBean scheduler;
@Value("${contextRefreshDelay:15}")
@@ -134,22 +128,14 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
Trigger trigger = TriggerBuilder.newTrigger().startAt(date).build();
JobKey jobKey = new JobKey(getClass().getSimpleName());
- JobDetail job = JobBuilder.newJob(RefreshApplicationContextJob.class).withIdentity(jobKey).build();
- scheduler.getScheduler().scheduleJob(job, trigger);
- } catch (SchedulerException e) {
- throw new RuntimeException("Could not schedule refresh job", e);
- }
- }
+ JobDetail job = JobBuilder.newJob(SyncopeWARefreshContextJob.class).
+ withIdentity(jobKey).
+ build();
- private class RefreshApplicationContextJob implements Job {
+ scheduler.getScheduler().scheduleJob(job, trigger);
- @Override
- public void execute(final JobExecutionContext jobExecutionContext) {
- try {
- LOG.debug("Refreshed context: {}", contextRefresher.refresh());
- } catch (final Exception e) {
- LOG.error(e.getMessage(), e);
- }
+ } catch (final SchedulerException e) {
+ throw new RuntimeException("Could not schedule refresh job", e);
}
}
}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
index 8b61f58..9ff7c55 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
@@ -21,22 +21,44 @@ package org.apache.syncope.wa.starter;
import org.apereo.cas.audit.AuditTrailExecutionPlanConfigurer;
import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer;
import org.apereo.cas.services.ServiceRegistryListener;
-
import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
import org.apache.syncope.wa.WARestClient;
+import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGenerator;
+import org.apereo.cas.support.saml.idp.metadata.locator.SamlIdPMetadataLocator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-
import java.util.Collection;
+import org.apache.syncope.wa.saml.idp.metadata.RestfulSamlIdPMetadataGenerator;
+import org.apache.syncope.wa.saml.idp.metadata.RestfulSamlIdPMetadataLocator;
+import org.apereo.cas.configuration.CasConfigurationProperties;
+import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGeneratorConfigurationContext;
+import org.apereo.cas.support.saml.idp.metadata.writer.SamlIdPCertificateAndKeyWriter;
+import org.apereo.cas.util.crypto.CipherExecutor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.core.io.ResourceLoader;
@Configuration
public class SyncopeWAConfiguration {
+ private static final Logger LOG = LoggerFactory.getLogger(SyncopeWAConfiguration.class);
+
+ @Autowired
+ private CasConfigurationProperties casProperties;
+
+ @Autowired
+ private ResourceLoader resourceLoader;
+
+ @Autowired
+ @Qualifier("samlSelfSignedCertificateWriter")
+ private ObjectProvider<SamlIdPCertificateAndKeyWriter> samlSelfSignedCertificateWriter;
+
@Autowired
private ConfigurableApplicationContext applicationContext;
@@ -48,10 +70,30 @@ public class SyncopeWAConfiguration {
@Bean
public ServiceRegistryExecutionPlanConfigurer syncopeServiceRegistryConfigurer(final WARestClient restClient) {
SyncopeServiceRegistry registry =
- new SyncopeServiceRegistry(restClient, applicationContext, serviceRegistryListeners);
+ new SyncopeServiceRegistry(restClient, applicationContext, serviceRegistryListeners);
return plan -> plan.registerServiceRegistry(registry);
}
+ @Autowired
+ @Bean
+ public SamlIdPMetadataGenerator samlIdPMetadataGenerator(final WARestClient restClient) {
+ SamlIdPMetadataGeneratorConfigurationContext context =
+ SamlIdPMetadataGeneratorConfigurationContext.builder().
+ samlIdPMetadataLocator(samlIdPMetadataLocator(restClient)).
+ samlIdPCertificateAndKeyWriter(samlSelfSignedCertificateWriter.getObject()).
+ resourceLoader(resourceLoader).
+ casProperties(casProperties).
+ metadataCipherExecutor(CipherExecutor.noOpOfStringToString()).
+ build();
+ return new RestfulSamlIdPMetadataGenerator(context, restClient);
+ }
+
+ @Autowired
+ @Bean
+ public SamlIdPMetadataLocator samlIdPMetadataLocator(final WARestClient restClient) {
+ return new RestfulSamlIdPMetadataLocator(CipherExecutor.noOpOfStringToString(), restClient);
+ }
+
@Bean
@Autowired
public AuditTrailExecutionPlanConfigurer auditConfigurer(final WARestClient restClient) {
@@ -67,4 +109,5 @@ public class SyncopeWAConfiguration {
public KeymasterStop keymasterStop() {
return new KeymasterStop(NetworkService.Type.WA);
}
+
}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWARefreshContextJob.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWARefreshContextJob.java
new file mode 100644
index 0000000..8b3bae3
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWARefreshContextJob.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import org.apache.syncope.wa.WARestClient;
+import org.apereo.cas.support.saml.idp.metadata.generator.SamlIdPMetadataGenerator;
+import org.apereo.cas.support.saml.services.idp.metadata.SamlIdPMetadataDocument;
+import org.quartz.Job;
+import org.quartz.JobExecutionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.context.refresh.ContextRefresher;
+import java.util.Optional;
+import org.quartz.JobExecutionException;
+
+public class SyncopeWARefreshContextJob implements Job {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SyncopeWARefreshContextJob.class);
+
+ @Autowired
+ private ContextRefresher contextRefresher;
+
+ @Autowired
+ private SamlIdPMetadataGenerator metadataGenerator;
+
+ public SyncopeWARefreshContextJob() {
+ }
+
+ @Override
+ public void execute(final JobExecutionContext jobExecutionContext) throws JobExecutionException {
+ try {
+ LOG.debug("Refreshing WA application context");
+ if (!WARestClient.isReady()) {
+ LOG.debug("Syncope client is not yet ready");
+ throw new RuntimeException("Syncope core is not yet ready to access requests");
+ }
+ contextRefresher.refresh();
+
+ LOG.info("Generating SAML2 IdP metadata metadata");
+ SamlIdPMetadataDocument document = metadataGenerator.generate(Optional.empty());
+ LOG.info("Generated SAML2 IdP metadata for {}", document.getAppliesTo());
+
+ } catch (RuntimeException e) {
+ throw new JobExecutionException("While generating SAML2 IdP metadata", e);
+ }
+ }
+}