You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2020/04/09 14:24:43 UTC
[syncope] 03/07: [SYNCOPE-1545] ClientApp service
This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch SYNCOPE-160
in repository https://gitbox.apache.org/repos/asf/syncope.git
commit e2982772ab45ca4b5c4077e2678f2d16e0251eb2
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu Apr 9 13:35:40 2020 +0200
[SYNCOPE-1545] ClientApp service
---
.../syncope/common/lib/to/client/ClientAppTO.java | 154 ++++++++++++
.../syncope/common/lib/to/client/OIDCRPTO.java | 168 +++++++++++++
.../syncope/common/lib/to/client/SAML2SPTO.java | 225 +++++++++++++++++
.../syncope/common/lib/types/ClientAppType.java | 28 +++
.../syncope/common/lib/types/SAML2SPNameId.java | 42 ++++
.../common/rest/api/service/ClientAppService.java | 134 +++++++++++
.../apache/syncope/core/logic/ClientAppLogic.java | 204 ++++++++++++++++
.../rest/cxf/service/ClientAppServiceImpl.java | 66 +++++
.../core/persistence/api/dao/auth/OIDCRPDAO.java | 45 ++++
.../core/persistence/api/dao/auth/SAML2SPDAO.java | 45 ++++
.../persistence/api/entity/auth/ClientApp.java | 56 +++++
.../api/entity/auth/ClientAppUtils.java | 28 +++
.../api/entity/auth/ClientAppUtilsFactory.java | 33 +++
.../core/persistence/api/entity/auth/OIDCRP.java | 52 ++++
.../core/persistence/api/entity/auth/SAML2SP.java | 76 ++++++
.../persistence/jpa/dao/auth/JPAOIDCRPDAO.java | 107 ++++++++
.../persistence/jpa/dao/auth/JPASAML2SPDAO.java | 107 ++++++++
.../jpa/entity/auth/AbstractClientApp.java | 131 ++++++++++
.../jpa/entity/auth/JPAClientAppUtils.java | 51 ++++
.../jpa/entity/auth/JPAClientAppUtilsFactory.java | 72 ++++++
.../persistence/jpa/entity/auth/JPAOIDCRP.java | 143 +++++++++++
.../persistence/jpa/entity/auth/JPASAML2SP.java | 204 ++++++++++++++++
.../jpa/inner/AbstractClientAppTest.java | 109 +++++++++
.../core/persistence/jpa/inner/OIDCRPTest.java | 82 +++++++
.../core/persistence/jpa/inner/SAML2SPTest.java | 80 ++++++
.../provisioning/api/data/ClientAppDataBinder.java | 31 +++
.../java/data/ClientAppDataBinderImpl.java | 268 +++++++++++++++++++++
.../apache/syncope/fit/core/ClientAppITCase.java | 155 ++++++++++++
28 files changed, 2896 insertions(+)
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java
new file mode 100644
index 0000000..ed8a6f0
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java
@@ -0,0 +1,154 @@
+/*
+ * 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.client;
+
+import com.fasterxml.jackson.annotation.JsonPropertyOrder;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.BaseBean;
+import org.apache.syncope.common.lib.to.EntityTO;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "clientApp")
+@XmlType
+@XmlSeeAlso({ OIDCRPTO.class, SAML2SPTO.class })
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "@class")
+@JsonPropertyOrder(value = { "@class", "key", "name", "description", "authPolicy", "accessPolicy", "attReleasePolicy" })
+@Schema(subTypes = { OIDCRPTO.class, SAML2SPTO.class }, discriminatorProperty = "@class")
+public abstract class ClientAppTO extends BaseBean implements EntityTO {
+
+ private static final long serialVersionUID = 6577639976115661357L;
+
+ private String key;
+
+ private String name;
+
+ private Long clientAppId;
+
+ private String description;
+
+ private String authPolicy;
+
+ private String accessPolicy;
+
+ private String attrReleasePolicy;
+
+ public String getAttrReleasePolicy() {
+ return attrReleasePolicy;
+ }
+
+ public void setAttrReleasePolicy(final String attrReleasePolicy) {
+ this.attrReleasePolicy = attrReleasePolicy;
+ }
+
+ public String getAccessPolicy() {
+ return accessPolicy;
+ }
+
+ public void setAccessPolicy(final String accessPolicy) {
+ this.accessPolicy = accessPolicy;
+ }
+
+ public String getAuthPolicy() {
+ return authPolicy;
+ }
+
+ public void setAuthPolicy(final String authPolicy) {
+ this.authPolicy = authPolicy;
+ }
+
+ @Override
+ public String getKey() {
+ return key;
+ }
+
+ @Override
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public Long getClientAppId() {
+ return clientAppId;
+ }
+
+ public void setClientAppId(final Long clientAppId) {
+ this.clientAppId = clientAppId;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ @Schema(name = "@class", required = true)
+ public abstract String getDiscriminator();
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .appendSuper(super.hashCode())
+ .append(key)
+ .append(clientAppId)
+ .append(name)
+ .append(description)
+ .append(authPolicy)
+ .append(accessPolicy)
+ .append(attrReleasePolicy)
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ ClientAppTO rhs = (ClientAppTO) obj;
+ return new EqualsBuilder()
+ .appendSuper(super.equals(obj))
+ .append(this.key, rhs.key)
+ .append(this.clientAppId, rhs.clientAppId)
+ .append(this.name, rhs.name)
+ .append(this.description, rhs.description)
+ .append(this.authPolicy, rhs.authPolicy)
+ .append(this.accessPolicy, rhs.accessPolicy)
+ .append(this.attrReleasePolicy, rhs.attrReleasePolicy)
+ .isEquals();
+ }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/OIDCRPTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/OIDCRPTO.java
new file mode 100644
index 0000000..987c534
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/OIDCRPTO.java
@@ -0,0 +1,168 @@
+/*
+ * 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.client;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.ArrayList;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.apache.syncope.common.lib.types.OIDCSubjectType;
+
+@XmlRootElement(name = "oidcrp")
+@XmlType
+@Schema(allOf = { ClientAppTO.class })
+public class OIDCRPTO extends ClientAppTO {
+
+ private static final long serialVersionUID = -6370888503924521351L;
+
+ private String clientId;
+
+ private String clientSecret;
+
+ private boolean signIdToken;
+
+ private String jwks;
+
+ private OIDCSubjectType subjectType;
+
+ private final List<String> redirectUris = new ArrayList<>();
+
+ private final Set<String> supportedGrantTypes = new HashSet<>();
+
+ private final Set<String> supportedResponseTypes = new HashSet<>();
+
+ @XmlTransient
+ @JsonProperty("@class")
+ @Schema(name = "@class", required = true,
+ example = "org.apache.syncope.common.lib.to.client.OIDCRPTO")
+ @Override
+ public String getDiscriminator() {
+ return getClass().getName();
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(final String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ public void setClientSecret(final String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+
+ @XmlElementWrapper(name = "redirectUris")
+ @XmlElement(name = "redirectUri")
+ @JsonProperty("redirectUris")
+ public List<String> getRedirectUris() {
+ return redirectUris;
+ }
+
+ @XmlElementWrapper(name = "supportedGrantTypes")
+ @XmlElement(name = "supportedGrantType")
+ @JsonProperty("supportedGrantTypes")
+ public Set<String> getSupportedGrantTypes() {
+ return supportedGrantTypes;
+ }
+
+ @XmlElementWrapper(name = "supportedResponseTypes")
+ @XmlElement(name = "supportedResponseType")
+ @JsonProperty("supportedResponseTypes")
+ public Set<String> getSupportedResponseTypes() {
+ return supportedResponseTypes;
+ }
+
+ public boolean isSignIdToken() {
+ return signIdToken;
+ }
+
+ public void setSignIdToken(final boolean signIdToken) {
+ this.signIdToken = signIdToken;
+ }
+
+ public String getJwks() {
+ return jwks;
+ }
+
+ public void setJwks(final String jwks) {
+ this.jwks = jwks;
+ }
+
+ public OIDCSubjectType getSubjectType() {
+ return subjectType;
+ }
+
+ public void setSubjectType(final OIDCSubjectType subjectType) {
+ this.subjectType = subjectType;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ OIDCRPTO rhs = (OIDCRPTO) obj;
+ return new EqualsBuilder()
+ .appendSuper(super.equals(obj))
+ .append(this.clientId, rhs.clientId)
+ .append(this.clientSecret, rhs.clientSecret)
+ .append(this.redirectUris, rhs.redirectUris)
+ .append(this.supportedGrantTypes, rhs.supportedGrantTypes)
+ .append(this.supportedResponseTypes, rhs.supportedResponseTypes)
+ .append(this.signIdToken, rhs.signIdToken)
+ .append(this.jwks, rhs.jwks)
+ .append(this.subjectType, rhs.subjectType)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .appendSuper(super.hashCode())
+ .append(clientId)
+ .append(clientSecret)
+ .append(redirectUris)
+ .append(supportedGrantTypes)
+ .append(supportedResponseTypes)
+ .append(signIdToken)
+ .append(jwks)
+ .append(subjectType)
+ .toHashCode();
+ }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/SAML2SPTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/SAML2SPTO.java
new file mode 100644
index 0000000..d2cfac5
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/SAML2SPTO.java
@@ -0,0 +1,225 @@
+/*
+ * 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.client;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
+
+@XmlRootElement(name = "saml2SP")
+@XmlType
+@Schema(allOf = { ClientAppTO.class })
+public class SAML2SPTO extends ClientAppTO {
+
+ private static final long serialVersionUID = -6370888503924521351L;
+
+ private String entityId;
+
+ private String metadataLocation;
+
+ private String metadataSignatureLocation;
+
+ private boolean signAssertions;
+
+ private boolean signResponses;
+
+ private boolean encryptionOptional;
+
+ private boolean encryptAssertions;
+
+ private String requiredAuthenticationContextClass;
+
+ private SAML2SPNameId requiredNameIdFormat;
+
+ private Integer skewAllowance;
+
+ private String nameIdQualifier;
+
+ private String assertionAudiences;
+
+ private String serviceProviderNameIdQualifier;
+
+ @XmlTransient
+ @JsonProperty("@class")
+ @Schema(name = "@class", required = true,
+ example = "org.apache.syncope.common.lib.to.client.SAML2SPTO")
+ @Override
+ public String getDiscriminator() {
+ return getClass().getName();
+ }
+
+ public String getEntityId() {
+ return entityId;
+ }
+
+ public void setEntityId(final String entityId) {
+ this.entityId = entityId;
+ }
+
+ public String getMetadataLocation() {
+ return metadataLocation;
+ }
+
+ public void setMetadataLocation(final String metadataLocation) {
+ this.metadataLocation = metadataLocation;
+ }
+
+ public String getMetadataSignatureLocation() {
+ return metadataSignatureLocation;
+ }
+
+ public void setMetadataSignatureLocation(final String metadataSignatureLocation) {
+ this.metadataSignatureLocation = metadataSignatureLocation;
+ }
+
+ public boolean isSignAssertions() {
+ return signAssertions;
+ }
+
+ public void setSignAssertions(final boolean signAssertions) {
+ this.signAssertions = signAssertions;
+ }
+
+ public boolean isSignResponses() {
+ return signResponses;
+ }
+
+ public void setSignResponses(final boolean signResponses) {
+ this.signResponses = signResponses;
+ }
+
+ public boolean isEncryptionOptional() {
+ return encryptionOptional;
+ }
+
+ public void setEncryptionOptional(final boolean encryptionOptional) {
+ this.encryptionOptional = encryptionOptional;
+ }
+
+ public boolean isEncryptAssertions() {
+ return encryptAssertions;
+ }
+
+ public void setEncryptAssertions(final boolean encryptAssertions) {
+ this.encryptAssertions = encryptAssertions;
+ }
+
+ public String getRequiredAuthenticationContextClass() {
+ return requiredAuthenticationContextClass;
+ }
+
+ public void setRequiredAuthenticationContextClass(final String requiredAuthenticationContextClass) {
+ this.requiredAuthenticationContextClass = requiredAuthenticationContextClass;
+ }
+
+ public SAML2SPNameId getRequiredNameIdFormat() {
+ return requiredNameIdFormat;
+ }
+
+ public void setRequiredNameIdFormat(final SAML2SPNameId requiredNameIdFormat) {
+ this.requiredNameIdFormat = requiredNameIdFormat;
+ }
+
+ public Integer getSkewAllowance() {
+ return skewAllowance;
+ }
+
+ public void setSkewAllowance(final Integer skewAllowance) {
+ this.skewAllowance = skewAllowance;
+ }
+
+ public String getNameIdQualifier() {
+ return nameIdQualifier;
+ }
+
+ public void setNameIdQualifier(final String nameIdQualifier) {
+ this.nameIdQualifier = nameIdQualifier;
+ }
+
+ public String getAssertionAudiences() {
+ return assertionAudiences;
+ }
+
+ public void setAssertionAudiences(final String assertionAudiences) {
+ this.assertionAudiences = assertionAudiences;
+ }
+
+ public String getServiceProviderNameIdQualifier() {
+ return serviceProviderNameIdQualifier;
+ }
+
+ public void setServiceProviderNameIdQualifier(final String serviceProviderNameIdQualifier) {
+ this.serviceProviderNameIdQualifier = serviceProviderNameIdQualifier;
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ SAML2SPTO rhs = (SAML2SPTO) obj;
+ return new EqualsBuilder()
+ .appendSuper(super.equals(obj))
+ .append(this.entityId, rhs.entityId)
+ .append(this.metadataLocation, rhs.metadataLocation)
+ .append(this.metadataSignatureLocation, rhs.metadataSignatureLocation)
+ .append(this.signAssertions, rhs.signAssertions)
+ .append(this.signResponses, rhs.signResponses)
+ .append(this.encryptionOptional, rhs.encryptionOptional)
+ .append(this.encryptAssertions, rhs.encryptAssertions)
+ .append(this.requiredAuthenticationContextClass, rhs.requiredAuthenticationContextClass)
+ .append(this.requiredNameIdFormat, rhs.requiredNameIdFormat)
+ .append(this.skewAllowance, rhs.skewAllowance)
+ .append(this.nameIdQualifier, rhs.nameIdQualifier)
+ .append(this.assertionAudiences, rhs.assertionAudiences)
+ .append(this.serviceProviderNameIdQualifier, rhs.serviceProviderNameIdQualifier)
+ .isEquals();
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .appendSuper(super.hashCode())
+ .append(entityId)
+ .append(metadataLocation)
+ .append(metadataSignatureLocation)
+ .append(signAssertions)
+ .append(signResponses)
+ .append(encryptionOptional)
+ .append(encryptAssertions)
+ .append(requiredAuthenticationContextClass)
+ .append(requiredNameIdFormat)
+ .append(skewAllowance)
+ .append(nameIdQualifier)
+ .append(assertionAudiences)
+ .append(serviceProviderNameIdQualifier)
+ .toHashCode();
+ }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
new file mode 100644
index 0000000..7f90159
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
@@ -0,0 +1,28 @@
+/*
+ * 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.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum ClientAppType {
+ SAML2SP,
+ OIDCRP;
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/SAML2SPNameId.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/SAML2SPNameId.java
new file mode 100644
index 0000000..d75a68d
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/SAML2SPNameId.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum SAML2SPNameId {
+
+ EMAIL_ADDRESS("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"),
+ UNSPECIFIED("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"),
+ ENTITY("urn:oasis:names:tc:SAML:2.0:nameid-format:entity"),
+ PERSISTENT("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"),
+ TRANSIENT("urn:oasis:names:tc:SAML:2.0:nameid-format:transient"),
+ ENCRYPTED("urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted");
+
+ private final String nameId;
+
+ SAML2SPNameId(final String nameId) {
+ this.nameId = nameId;
+ }
+
+ public String getNameId() {
+ return nameId;
+ }
+}
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ClientAppService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ClientAppService.java
new file mode 100644
index 0000000..c2e2398
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ClientAppService.java
@@ -0,0 +1,134 @@
+/*
+ * 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.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 java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+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.client.ClientAppTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for client applications.
+ */
+@Tag(name = "ClientApps")
+@SecurityRequirements({
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer") })
+@Path("clientApps")
+public interface ClientAppService extends JAXRSService {
+
+ /**
+ * Returns the client app matching the given key.
+ *
+ * @param type client app type
+ * @param key key of requested client app
+ * @param <T> response type (extending ClientAppTO)
+ * @return client app with matching id
+ */
+ @GET
+ @Path("{type}/{key}")
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ <T extends ClientAppTO> T read(
+ @NotNull @PathParam("type") ClientAppType type,
+ @NotNull @PathParam("key") String key);
+
+ /**
+ * Returns a list of client apps of the matching type.
+ *
+ * @param type Type selector for requested client apps
+ * @param <T> response type (extending ClientAppTO)
+ * @return list of client apps with matching type
+ */
+ @GET
+ @Path("{type}")
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ <T extends ClientAppTO> List<T> list(@NotNull @PathParam("type") ClientAppType type);
+
+ /**
+ * Create a new client app.
+ *
+ * @param type client app type
+ * @param clientAppTO ClientApp to be created (needs to match type)
+ * @return Response object featuring Location header of created client app
+ */
+ @ApiResponses(
+ @ApiResponse(responseCode = "201",
+ description = "ClientApp 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") }))
+ @POST
+ @Path("{type}")
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ Response create(@NotNull @PathParam("type") ClientAppType type, @NotNull ClientAppTO clientAppTO);
+
+ /**
+ * Updates client app matching the given key.
+ *
+ * @param type client app type
+ * @param clientAppTO ClientApp to replace existing client app
+ */
+ @Parameter(name = "key", description = "ClientApp's key", in = ParameterIn.PATH, schema =
+ @Schema(type = "string"))
+ @ApiResponses(
+ @ApiResponse(responseCode = "204", description = "Operation was successful"))
+ @PUT
+ @Path("{type}/{key}")
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ void update(@NotNull @PathParam("type") ClientAppType type, @NotNull ClientAppTO clientAppTO);
+
+ /**
+ * Delete client app matching the given key.
+ *
+ * @param type client app type
+ * @param key key of client app to be deleted
+ */
+ @ApiResponses(
+ @ApiResponse(responseCode = "204", description = "Operation was successful"))
+ @DELETE
+ @Path("{type}/{key}")
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+ void delete(@NotNull @PathParam("type") ClientAppType type, @NotNull @PathParam("key") String key);
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java
new file mode 100644
index 0000000..6e27383
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java
@@ -0,0 +1,204 @@
+/*
+ * 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 java.lang.reflect.Method;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.client.ClientAppTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.OIDCRPDAO;
+import org.apache.syncope.core.persistence.api.dao.auth.SAML2SPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtils;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder;
+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 ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
+
+ @Autowired
+ private ClientAppUtilsFactory clientAppUtilsFactory;
+
+ @Autowired
+ private ClientAppDataBinder binder;
+
+ @Autowired
+ private SAML2SPDAO saml2spDAO;
+
+ @Autowired
+ private OIDCRPDAO oidcrpDAO;
+
+ @PreAuthorize("hasRole('" + AMEntitlement.CLIENTAPP_LIST + "')")
+ public <T extends ClientAppTO> List<T> list(final ClientAppType type) {
+ Stream<T> stream;
+
+ switch (type) {
+ case OIDCRP:
+ stream = oidcrpDAO.findAll().stream().map(binder::getClientAppTO);
+ break;
+
+ case SAML2SP:
+ default:
+ stream = saml2spDAO.findAll().stream().map(binder::getClientAppTO);
+ }
+
+ return stream.collect(Collectors.toList());
+ }
+
+ private void checkType(final ClientAppType type, final ClientAppUtils clientAppUtils) {
+ if (clientAppUtils.getType() != type) {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidRequest);
+ sce.getElements().add("Found " + type + ", expected " + clientAppUtils.getType());
+ throw sce;
+ }
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.CLIENTAPP_READ + "')")
+ @Transactional(readOnly = true)
+ public <T extends ClientAppTO> T read(final ClientAppType type, final String key) {
+ switch (type) {
+ case OIDCRP:
+ OIDCRP oidcrp = oidcrpDAO.find(key);
+ if (oidcrp == null) {
+ throw new NotFoundException("Client app " + key + " not found");
+ }
+
+ checkType(type, clientAppUtilsFactory.getInstance(oidcrp));
+
+ return binder.getClientAppTO(oidcrp);
+
+ case SAML2SP:
+ default:
+ SAML2SP saml2sp = saml2spDAO.find(key);
+ if (saml2sp == null) {
+ throw new NotFoundException("Client app " + key + " not found");
+ }
+
+ checkType(type, clientAppUtilsFactory.getInstance(saml2sp));
+
+ return binder.getClientAppTO(saml2sp);
+ }
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.CLIENTAPP_CREATE + "')")
+ public ClientAppTO create(final ClientAppType type, final ClientAppTO clientAppTO) {
+ checkType(type, clientAppUtilsFactory.getInstance(clientAppTO));
+
+ switch (type) {
+ case OIDCRP:
+ return binder.getClientAppTO(oidcrpDAO.save(binder.create(clientAppTO)));
+
+ case SAML2SP:
+ default:
+ return binder.getClientAppTO(saml2spDAO.save(binder.create(clientAppTO)));
+ }
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.CLIENTAPP_UPDATE + "')")
+ public void update(final ClientAppType type, final ClientAppTO clientAppTO) {
+ checkType(type, clientAppUtilsFactory.getInstance(clientAppTO));
+
+ switch (type) {
+ case OIDCRP:
+ OIDCRP oidcrp = oidcrpDAO.find(clientAppTO.getKey());
+ if (oidcrp == null) {
+ throw new NotFoundException("Client app " + clientAppTO.getKey() + " not found");
+ }
+ binder.update(oidcrp, clientAppTO);
+ oidcrpDAO.save(oidcrp);
+ break;
+
+ case SAML2SP:
+ default:
+ SAML2SP saml2sp = saml2spDAO.find(clientAppTO.getKey());
+ if (saml2sp == null) {
+ throw new NotFoundException("Client app " + clientAppTO.getKey() + " not found");
+ }
+ binder.update(saml2sp, clientAppTO);
+ saml2spDAO.save(saml2sp);
+ }
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.CLIENTAPP_DELETE + "')")
+ public void delete(final ClientAppType type, final String key) {
+ switch (type) {
+ case OIDCRP:
+ OIDCRP oidcrp = oidcrpDAO.find(key);
+ if (oidcrp == null) {
+ throw new NotFoundException("Client app " + key + " not found");
+ }
+ oidcrpDAO.delete(oidcrp);
+ break;
+
+ case SAML2SP:
+ default:
+ SAML2SP saml2sp = saml2spDAO.find(key);
+ if (saml2sp == null) {
+ throw new NotFoundException("Client app " + key + " not found");
+ }
+ saml2spDAO.delete(saml2sp);
+ }
+ }
+
+ @Override
+ protected ClientAppTO resolveReference(final Method method, final Object... args)
+ throws UnresolvedReferenceException {
+
+ String key = null;
+
+ if (ArrayUtils.isNotEmpty(args)) {
+ for (int i = 0; key == null && i < args.length; i++) {
+ if (args[i] instanceof String) {
+ key = (String) args[i];
+ } else if (args[i] instanceof ClientAppTO) {
+ key = ((ClientAppTO) args[i]).getKey();
+ }
+ }
+ }
+
+ if (key != null) {
+ try {
+ ClientApp clientApp = saml2spDAO.find(key);
+ if (clientApp == null) {
+ clientApp = oidcrpDAO.find(key);
+ }
+
+ return binder.getClientAppTO(clientApp);
+ } 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/ClientAppServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ClientAppServiceImpl.java
new file mode 100644
index 0000000..e2e461e
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ClientAppServiceImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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 java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.client.ClientAppTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.ClientAppService;
+import org.apache.syncope.core.logic.ClientAppLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class ClientAppServiceImpl extends AbstractServiceImpl implements ClientAppService {
+
+ @Autowired
+ private ClientAppLogic logic;
+
+ @Override
+ public Response create(final ClientAppType type, final ClientAppTO clientAppTO) {
+ ClientAppTO appTO = logic.create(type, clientAppTO);
+ URI location = uriInfo.getAbsolutePathBuilder().path(appTO.getKey()).build();
+ return Response.created(location).
+ header(RESTHeaders.RESOURCE_KEY, appTO.getKey()).
+ build();
+ }
+
+ @Override
+ public <T extends ClientAppTO> List<T> list(final ClientAppType type) {
+ return logic.list(type);
+ }
+
+ @Override
+ public <T extends ClientAppTO> T read(final ClientAppType type, final String key) {
+ return logic.read(type, key);
+ }
+
+ @Override
+ public void update(final ClientAppType type, final ClientAppTO clientAppTO) {
+ logic.update(type, clientAppTO);
+ }
+
+ @Override
+ public void delete(final ClientAppType type, final String key) {
+ logic.delete(type, key);
+ }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCRPDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCRPDAO.java
new file mode 100644
index 0000000..517c92b
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCRPDAO.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.core.persistence.api.dao.auth;
+
+import org.apache.syncope.core.persistence.api.dao.DAO;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+
+public interface OIDCRPDAO extends DAO<OIDCRP> {
+
+ OIDCRP find(String key);
+
+ OIDCRP findByClientAppId(Long clientAppId);
+
+ OIDCRP findByName(String name);
+
+ OIDCRP findByClientId(String clientId);
+
+ List<OIDCRP> findAll();
+
+ OIDCRP save(OIDCRP clientApp);
+
+ void delete(String key);
+
+ void deleteByClientId(String clientId);
+
+ void delete(OIDCRP clientApp);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2SPDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2SPDAO.java
new file mode 100644
index 0000000..2d2fdbd
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2SPDAO.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.core.persistence.api.dao.auth;
+
+import org.apache.syncope.core.persistence.api.dao.DAO;
+
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+
+public interface SAML2SPDAO extends DAO<SAML2SP> {
+
+ SAML2SP find(String key);
+
+ SAML2SP findByClientAppId(Long clientAppId);
+
+ SAML2SP findByName(String name);
+
+ SAML2SP findByEntityId(String clientId);
+
+ List<SAML2SP> findAll();
+
+ SAML2SP save(SAML2SP clientApp);
+
+ void delete(String key);
+
+ void deleteByEntityId(String entityId);
+
+ void delete(SAML2SP clientApp);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
new file mode 100644
index 0000000..70bf7b7
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+
+public interface ClientApp extends Entity {
+
+ String getName();
+
+ void setName(String name);
+
+ Long getClientAppId();
+
+ void setClientAppId(Long clientAppId);
+
+ String getDescription();
+
+ void setDescription(String description);
+
+ AuthPolicy getAuthPolicy();
+
+ void setAuthPolicy(AuthPolicy policy);
+
+ AccessPolicy getAccessPolicy();
+
+ void setAccessPolicy(AccessPolicy policy);
+
+ AttrReleasePolicy getAttrReleasePolicy();
+
+ void setAttrReleasePolicy(AttrReleasePolicy policy);
+
+ Realm getRealm();
+
+ void setRealm(Realm realm);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtils.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtils.java
new file mode 100644
index 0000000..7db5f11
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtils.java
@@ -0,0 +1,28 @@
+/*
+ * 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.common.lib.types.ClientAppType;
+
+public interface ClientAppUtils {
+
+ ClientAppType getType();
+
+ Class<? extends ClientApp> clientAppClass();
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtilsFactory.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtilsFactory.java
new file mode 100644
index 0000000..6777d9d
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtilsFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity.auth;
+
+import org.apache.syncope.common.lib.to.client.ClientAppTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+
+public interface ClientAppUtilsFactory {
+
+ ClientAppUtils getInstance(ClientAppType type);
+
+ ClientAppUtils getInstance(ClientApp clientApp);
+
+ ClientAppUtils getInstance(Class<? extends ClientAppTO> clientAppClass);
+
+ ClientAppUtils getInstance(ClientAppTO clientAppTO);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCRP.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCRP.java
new file mode 100644
index 0000000..0a72461
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCRP.java
@@ -0,0 +1,52 @@
+/*
+ * 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 java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.OIDCSubjectType;
+
+public interface OIDCRP extends ClientApp {
+
+ void setClientId(String id);
+
+ String getClientId();
+
+ void setClientSecret(String secret);
+
+ String getClientSecret();
+
+ List<String> getRedirectUris();
+
+ Set<String> getSupportedGrantTypes();
+
+ Set<String> getSupportedResponseTypes();
+
+ boolean isSignIdToken();
+
+ void setSignIdToken(boolean signIdToken);
+
+ String getJwks();
+
+ void setJwks(String jwks);
+
+ OIDCSubjectType getSubjectType();
+
+ void setSubjectType(OIDCSubjectType subjectType);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2SP.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2SP.java
new file mode 100644
index 0000000..2103b5d
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2SP.java
@@ -0,0 +1,76 @@
+/*
+ * 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.common.lib.types.SAML2SPNameId;
+
+public interface SAML2SP extends ClientApp {
+
+ String getEntityId();
+
+ void setEntityId(String id);
+
+ String getMetadataLocation();
+
+ void setMetadataLocation(String location);
+
+ void setMetadataSignatureLocation(String location);
+
+ String getMetadataSignatureLocation();
+
+ void setSignAssertions(boolean location);
+
+ boolean isSignAssertions();
+
+ void setSignResponses(boolean location);
+
+ boolean isSignResponses();
+
+ void setEncryptionOptional(boolean location);
+
+ boolean isEncryptionOptional();
+
+ void setEncryptAssertions(boolean location);
+
+ boolean isEncryptAssertions();
+
+ void setRequiredAuthenticationContextClass(String location);
+
+ String getRequiredAuthenticationContextClass();
+
+ void setRequiredNameIdFormat(SAML2SPNameId location);
+
+ SAML2SPNameId getRequiredNameIdFormat();
+
+ void setSkewAllowance(Integer location);
+
+ Integer getSkewAllowance();
+
+ void setNameIdQualifier(String location);
+
+ String getNameIdQualifier();
+
+ void setAssertionAudiences(String location);
+
+ String getAssertionAudiences();
+
+ void setServiceProviderNameIdQualifier(String location);
+
+ String getServiceProviderNameIdQualifier();
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCRPDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCRPDAO.java
new file mode 100644
index 0000000..bf6b32a
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCRPDAO.java
@@ -0,0 +1,107 @@
+/*
+ * 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 java.util.List;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCRP;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.persistence.api.dao.auth.OIDCRPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+
+@Repository
+public class JPAOIDCRPDAO extends AbstractDAO<OIDCRP> implements OIDCRPDAO {
+
+ @Override
+ public OIDCRP find(final String key) {
+ return entityManager().find(JPAOIDCRP.class, key);
+ }
+
+ private OIDCRP find(final String column, final Object value) {
+ TypedQuery<OIDCRP> query = entityManager().createQuery(
+ "SELECT e FROM " + JPAOIDCRP.class.getSimpleName() + " e WHERE e." + column + "=:value",
+ OIDCRP.class);
+ query.setParameter("value", value);
+
+ OIDCRP result = null;
+ try {
+ result = query.getSingleResult();
+ } catch (final NoResultException e) {
+ LOG.debug("No OIDCRP found with " + column + " {}", value, e);
+ }
+
+ return result;
+ }
+
+ @Override
+ public OIDCRP findByClientAppId(final Long clientAppId) {
+ return find("clientAppId", clientAppId);
+ }
+
+ @Override
+ public OIDCRP findByName(final String name) {
+ return find("name", name);
+ }
+
+ @Override
+ public OIDCRP findByClientId(final String clientId) {
+ return find("clientId", clientId);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<OIDCRP> findAll() {
+ TypedQuery<OIDCRP> query = entityManager().createQuery(
+ "SELECT e FROM " + JPAOIDCRP.class.getSimpleName() + " e", OIDCRP.class);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public OIDCRP save(final OIDCRP clientApp) {
+ return entityManager().merge(clientApp);
+ }
+
+ @Override
+ public void delete(final String key) {
+ OIDCRP rpTO = find(key);
+ if (rpTO == null) {
+ return;
+ }
+
+ delete(rpTO);
+ }
+
+ @Override
+ public void deleteByClientId(final String clientId) {
+ OIDCRP rpTO = findByClientId(clientId);
+ if (rpTO == null) {
+ return;
+ }
+ delete(rpTO);
+ }
+
+ @Override
+ public void delete(final OIDCRP clientApp) {
+ entityManager().remove(clientApp);
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2SPDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2SPDAO.java
new file mode 100644
index 0000000..e8ce293
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2SPDAO.java
@@ -0,0 +1,107 @@
+/*
+ * 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 org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SP;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.auth.SAML2SPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+
+@Repository
+public class JPASAML2SPDAO extends AbstractDAO<SAML2SP> implements SAML2SPDAO {
+
+ @Override
+ public SAML2SP find(final String key) {
+ return entityManager().find(JPASAML2SP.class, key);
+ }
+
+ private SAML2SP find(final String column, final Object value) {
+ TypedQuery<SAML2SP> query = entityManager().createQuery(
+ "SELECT e FROM " + JPASAML2SP.class.getSimpleName() + " e WHERE e." + column + "=:value",
+ SAML2SP.class);
+ query.setParameter("value", value);
+
+ SAML2SP result = null;
+ try {
+ result = query.getSingleResult();
+ } catch (final NoResultException e) {
+ LOG.debug("No SAML2SP found with " + column + " {}", value, e);
+ }
+
+ return result;
+ }
+
+ @Override
+ public SAML2SP findByClientAppId(final Long clientAppId) {
+ return find("clientAppId", clientAppId);
+ }
+
+ @Override
+ public SAML2SP findByName(final String name) {
+ return find("name", name);
+ }
+
+ @Override
+ public SAML2SP findByEntityId(final String entityId) {
+ return find("entityId", entityId);
+ }
+
+ @Transactional(readOnly = true)
+ @Override
+ public List<SAML2SP> findAll() {
+ TypedQuery<SAML2SP> query = entityManager().createQuery(
+ "SELECT e FROM " + JPASAML2SP.class.getSimpleName() + " e", SAML2SP.class);
+
+ return query.getResultList();
+ }
+
+ @Override
+ public SAML2SP save(final SAML2SP clientApp) {
+ return entityManager().merge(clientApp);
+ }
+
+ @Override
+ public void delete(final String key) {
+ SAML2SP policy = find(key);
+ if (policy == null) {
+ return;
+ }
+
+ delete(policy);
+ }
+
+ @Override
+ public void deleteByEntityId(final String entityId) {
+ SAML2SP app = findByEntityId(entityId);
+ if (app == null) {
+ return;
+ }
+ delete(app);
+ }
+
+ @Override
+ public void delete(final SAML2SP clientApp) {
+ entityManager().remove(clientApp);
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
new file mode 100644
index 0000000..e815c0f
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
@@ -0,0 +1,131 @@
+/*
+ * 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 org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
+import org.apache.syncope.core.persistence.jpa.entity.JPARealm;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccessPolicy;
+import javax.persistence.Column;
+import javax.persistence.FetchType;
+import javax.persistence.ManyToOne;
+import javax.persistence.MappedSuperclass;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+
+@MappedSuperclass
+public class AbstractClientApp extends AbstractGeneratedKeyEntity implements ClientApp {
+
+ private static final long serialVersionUID = 7422422526695279794L;
+
+ @Column(unique = true, nullable = false)
+ private String name;
+
+ @Column(unique = true, nullable = false)
+ private Long clientAppId;
+
+ @Column
+ private String description;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ private JPARealm realm;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ private JPAAuthPolicy authPolicy;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ private JPAAccessPolicy accessPolicy;
+
+ @ManyToOne(fetch = FetchType.EAGER)
+ private JPAAttrReleasePolicy attrReleasePolicy;
+
+ public Long getClientAppId() {
+ return clientAppId;
+ }
+
+ public void setClientAppId(final Long clientAppId) {
+ this.clientAppId = clientAppId;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ @Override
+ public JPAAuthPolicy getAuthPolicy() {
+ return authPolicy;
+ }
+
+ @Override
+ public void setAuthPolicy(final AuthPolicy authPolicy) {
+ checkType(authPolicy, JPAAuthPolicy.class);
+ this.authPolicy = (JPAAuthPolicy) authPolicy;
+ }
+
+ public JPAAccessPolicy getAccessPolicy() {
+ return accessPolicy;
+ }
+
+ public void setAccessPolicy(final AccessPolicy accessPolicy) {
+ checkType(accessPolicy, JPAAccessPolicy.class);
+ this.accessPolicy = (JPAAccessPolicy) accessPolicy;
+ }
+
+ @Override
+ public AttrReleasePolicy getAttrReleasePolicy() {
+ return this.attrReleasePolicy;
+ }
+
+ @Override
+ public void setAttrReleasePolicy(final AttrReleasePolicy policy) {
+ checkType(policy, JPAAccessPolicy.class);
+ this.attrReleasePolicy = (JPAAttrReleasePolicy) policy;
+ }
+
+ @Override
+ public Realm getRealm() {
+ return realm;
+ }
+
+ @Override
+ public void setRealm(final Realm realm) {
+ checkType(realm, JPARealm.class);
+ this.realm = (JPARealm) realm;
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java
new file mode 100644
index 0000000..ced3c6a
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtils;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+
+public class JPAClientAppUtils implements ClientAppUtils {
+
+ private final ClientAppType type;
+
+ protected JPAClientAppUtils(final ClientAppType type) {
+ this.type = type;
+ }
+
+ @Override
+ public ClientAppType getType() {
+ return type;
+ }
+
+ @Override
+ public Class<? extends ClientApp> clientAppClass() {
+ switch (type) {
+ case OIDCRP:
+ return OIDCRP.class;
+
+ case SAML2SP:
+ default:
+ return SAML2SP.class;
+ }
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java
new file mode 100644
index 0000000..610cc63
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity.auth;
+
+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;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtils;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtilsFactory;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JPAClientAppUtilsFactory implements ClientAppUtilsFactory {
+
+ @Override
+ public ClientAppUtils getInstance(final ClientAppType type) {
+ return new JPAClientAppUtils(type);
+ }
+
+ @Override
+ public ClientAppUtils getInstance(final ClientApp clientApp) {
+ ClientAppType type;
+ if (clientApp instanceof SAML2SP) {
+ type = ClientAppType.SAML2SP;
+ } else if (clientApp instanceof OIDCRP) {
+ type = ClientAppType.OIDCRP;
+ } else {
+ throw new IllegalArgumentException("Invalid client app: " + clientApp);
+ }
+
+ return getInstance(type);
+ }
+
+ @Override
+ public ClientAppUtils getInstance(final Class<? extends ClientAppTO> clientAppClass) {
+ ClientAppType type;
+ if (clientAppClass == SAML2SPTO.class) {
+ type = ClientAppType.SAML2SP;
+ } else if (clientAppClass == OIDCRPTO.class) {
+ type = ClientAppType.OIDCRP;
+ } else {
+ throw new IllegalArgumentException("Invalid ClientAppTO app: " + clientAppClass.getName());
+ }
+
+ return getInstance(type);
+ }
+
+ @Override
+ public ClientAppUtils getInstance(final ClientAppTO clientAppTO) {
+ return getInstance(clientAppTO.getClass());
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCRP.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCRP.java
new file mode 100644
index 0000000..a4f9a7b
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCRP.java
@@ -0,0 +1,143 @@
+/*
+ * 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.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.types.OIDCSubjectType;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+
+@Entity
+@Table(name = JPAOIDCRP.TABLE)
+public class JPAOIDCRP extends AbstractClientApp implements OIDCRP {
+
+ private static final long serialVersionUID = 7422422526695279794L;
+
+ public static final String TABLE = "OIDCRP";
+
+ @Column(unique = true, nullable = false)
+ private String clientId;
+
+ @Column
+ private String clientSecret;
+
+ @Column
+ private boolean signIdToken;
+
+ @Column
+ private String jwks;
+
+ @Column
+ private OIDCSubjectType subjectType;
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @Column
+ @CollectionTable(name = "OIDCRP_RedirectUris",
+ joinColumns =
+ @JoinColumn(name = "client_id", referencedColumnName = "id"))
+ private List<String> redirectUris = new ArrayList<>();
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @Column
+ @CollectionTable(name = "OIDCRP_SupportedGrantTypes",
+ joinColumns =
+ @JoinColumn(name = "client_id", referencedColumnName = "id"))
+ private Set<String> supportedGrantTypes = new HashSet<>();
+
+ @ElementCollection(fetch = FetchType.EAGER)
+ @Column(name = "supportedResponseType")
+ @CollectionTable(name = "OIDCRP_SupportedResponseTypes",
+ joinColumns =
+ @JoinColumn(name = "client_id", referencedColumnName = "id"))
+ private Set<String> supportedResponseTypes = new HashSet<>();
+
+ @Override
+ public List<String> getRedirectUris() {
+ return redirectUris;
+ }
+
+ @Override
+ public String getClientId() {
+ return clientId;
+ }
+
+ @Override
+ public void setClientId(final String clientId) {
+ this.clientId = clientId;
+ }
+
+ @Override
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ @Override
+ public void setClientSecret(final String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+
+ @Override
+ public boolean isSignIdToken() {
+ return signIdToken;
+ }
+
+ @Override
+ public void setSignIdToken(final boolean signIdToken) {
+ this.signIdToken = signIdToken;
+ }
+
+ @Override
+ public String getJwks() {
+ return jwks;
+ }
+
+ @Override
+ public void setJwks(final String jwks) {
+ this.jwks = jwks;
+ }
+
+ @Override
+ public OIDCSubjectType getSubjectType() {
+ return subjectType;
+ }
+
+ @Override
+ public void setSubjectType(final OIDCSubjectType subjectType) {
+ this.subjectType = subjectType;
+ }
+
+ @Override
+ public Set<String> getSupportedGrantTypes() {
+ return supportedGrantTypes;
+ }
+
+ @Override
+ public Set<String> getSupportedResponseTypes() {
+ return supportedResponseTypes;
+ }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2SP.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2SP.java
new file mode 100644
index 0000000..a34f0e5
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2SP.java
@@ -0,0 +1,204 @@
+/*
+ * 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.Table;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+
+@Entity
+@Table(name = JPASAML2SP.TABLE)
+public class JPASAML2SP extends AbstractClientApp implements SAML2SP {
+
+ public static final String TABLE = "SAML2SP";
+
+ private static final long serialVersionUID = 6422422526695279794L;
+
+ @Column(unique = true, nullable = false)
+ private String entityId;
+
+ @Column(nullable = false)
+ private String metadataLocation;
+
+ @Column
+ private String metadataSignatureLocation;
+
+ @Column
+ private boolean signAssertions;
+
+ @Column
+ private boolean signResponses;
+
+ @Column
+ private boolean encryptionOptional;
+
+ @Column
+ private boolean encryptAssertions;
+
+ @Column(name = "reqAuthnContextClass")
+ private String requiredAuthenticationContextClass;
+
+ @Column
+ private SAML2SPNameId requiredNameIdFormat;
+
+ @Column
+ private Integer skewAllowance;
+
+ @Column
+ private String nameIdQualifier;
+
+ @Column
+ private String assertionAudiences;
+
+ @Column(name = "spNameIdQualifier")
+ private String serviceProviderNameIdQualifier;
+
+ @Override
+ public String getEntityId() {
+ return entityId;
+ }
+
+ @Override
+ public void setEntityId(final String entityId) {
+ this.entityId = entityId;
+ }
+
+ @Override
+ public String getMetadataLocation() {
+ return metadataLocation;
+ }
+
+ @Override
+ public void setMetadataLocation(final String metadataLocation) {
+ this.metadataLocation = metadataLocation;
+ }
+
+ @Override
+ public String getMetadataSignatureLocation() {
+ return metadataSignatureLocation;
+ }
+
+ @Override
+ public void setMetadataSignatureLocation(final String metadataSignatureLocation) {
+ this.metadataSignatureLocation = metadataSignatureLocation;
+ }
+
+ @Override
+ public boolean isSignAssertions() {
+ return signAssertions;
+ }
+
+ @Override
+ public void setSignAssertions(final boolean signAssertions) {
+ this.signAssertions = signAssertions;
+ }
+
+ @Override
+ public boolean isSignResponses() {
+ return signResponses;
+ }
+
+ @Override
+ public void setSignResponses(final boolean signResponses) {
+ this.signResponses = signResponses;
+ }
+
+ @Override
+ public boolean isEncryptionOptional() {
+ return encryptionOptional;
+ }
+
+ @Override
+ public void setEncryptionOptional(final boolean encryptionOptional) {
+ this.encryptionOptional = encryptionOptional;
+ }
+
+ @Override
+ public boolean isEncryptAssertions() {
+ return encryptAssertions;
+ }
+
+ @Override
+ public void setEncryptAssertions(final boolean encryptAssertions) {
+ this.encryptAssertions = encryptAssertions;
+ }
+
+ @Override
+ public String getRequiredAuthenticationContextClass() {
+ return requiredAuthenticationContextClass;
+ }
+
+ @Override
+ public void setRequiredAuthenticationContextClass(final String requiredAuthenticationContextClass) {
+ this.requiredAuthenticationContextClass = requiredAuthenticationContextClass;
+ }
+
+ @Override
+ public SAML2SPNameId getRequiredNameIdFormat() {
+ return requiredNameIdFormat;
+ }
+
+ @Override
+ public void setRequiredNameIdFormat(final SAML2SPNameId requiredNameIdFormat) {
+ this.requiredNameIdFormat = requiredNameIdFormat;
+ }
+
+ @Override
+ public Integer getSkewAllowance() {
+ return skewAllowance;
+ }
+
+ @Override
+ public void setSkewAllowance(final Integer skewAllowance) {
+ this.skewAllowance = skewAllowance;
+ }
+
+ @Override
+ public String getNameIdQualifier() {
+ return nameIdQualifier;
+ }
+
+ @Override
+ public void setNameIdQualifier(final String nameIdQualifier) {
+ this.nameIdQualifier = nameIdQualifier;
+ }
+
+ @Override
+ public String getAssertionAudiences() {
+ return assertionAudiences;
+ }
+
+ @Override
+ public void setAssertionAudiences(final String assertionAudiences) {
+ this.assertionAudiences = assertionAudiences;
+ }
+
+ @Override
+ public String getServiceProviderNameIdQualifier() {
+ return serviceProviderNameIdQualifier;
+ }
+
+ @Override
+ public void setServiceProviderNameIdQualifier(final String serviceProviderNameIdQualifier) {
+ this.serviceProviderNameIdQualifier = serviceProviderNameIdQualifier;
+ }
+
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java
new file mode 100644
index 0000000..d2eea8f
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
+import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.common.lib.types.ImplementationEngine;
+import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+
+public class AbstractClientAppTest extends AbstractTest {
+
+ @Autowired
+ protected PolicyDAO policyDAO;
+
+ @Autowired
+ protected ImplementationDAO implementationDAO;
+
+ protected AttrReleasePolicy buildAndSaveAttrRelPolicy() {
+ AttrReleasePolicy attrRelPolicy = entityFactory.newEntity(AttrReleasePolicy.class);
+ attrRelPolicy.setName("AttrRelPolicyTest");
+ attrRelPolicy.setDescription("This is a sample access policy");
+
+ AllowedAttrReleasePolicyConf conf = new AllowedAttrReleasePolicyConf();
+ conf.setName("Example Attr Rel Policy for an application");
+ conf.getAllowedAttributes().addAll(List.of("cn", "givenName"));
+
+ Implementation type = entityFactory.newEntity(Implementation.class);
+ type.setKey("AttrRelPolicyTest");
+ type.setEngine(ImplementationEngine.JAVA);
+ type.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+ type.setBody(POJOHelper.serialize(conf));
+ type = implementationDAO.save(type);
+ attrRelPolicy.setConfiguration(type);
+ return policyDAO.save(attrRelPolicy);
+
+ }
+
+ protected AccessPolicy buildAndSaveAccessPolicy() {
+ AccessPolicy accessPolicy = entityFactory.newEntity(AccessPolicy.class);
+ accessPolicy.setName("AccessPolicyTest");
+ accessPolicy.setDescription("This is a sample access policy");
+
+ DefaultAccessPolicyConf conf = new DefaultAccessPolicyConf();
+ conf.setEnabled(true);
+ conf.setName("Example Access Policy for an application");
+ conf.getRequiredAttributes().putAll(Map.of("attribute1", Set.of("value1", "value2")));
+ conf.setSsoEnabled(false);
+
+ Implementation type = entityFactory.newEntity(Implementation.class);
+ type.setKey("AccessPolicyConfKey");
+ type.setEngine(ImplementationEngine.JAVA);
+ type.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+ type.setBody(POJOHelper.serialize(conf));
+ type = implementationDAO.save(type);
+
+ accessPolicy.setConfiguration(type);
+ return policyDAO.save(accessPolicy);
+
+ }
+
+ protected AuthPolicy buildAndSaveAuthPolicy() {
+ AuthPolicy authPolicy = entityFactory.newEntity(AuthPolicy.class);
+ authPolicy.setName("AuthPolicyTest");
+ authPolicy.setDescription("This is a sample authentication policy");
+
+ DefaultAuthPolicyConf conf = new DefaultAuthPolicyConf();
+ conf.getAuthModules().addAll(List.of("LdapAuthentication1", "DatabaseAuthentication2"));
+
+ Implementation type = entityFactory.newEntity(Implementation.class);
+ type.setKey("AuthPolicyConfKey");
+ type.setEngine(ImplementationEngine.JAVA);
+ type.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+ type.setBody(POJOHelper.serialize(conf));
+ type = implementationDAO.save(type);
+
+ authPolicy.setConfiguration(type);
+ return policyDAO.save(authPolicy);
+ }
+
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCRPTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCRPTest.java
new file mode 100644
index 0000000..d79677a
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCRPTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.common.lib.types.OIDCSubjectType;
+import org.apache.syncope.core.persistence.api.dao.auth.OIDCRPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional("Master")
+public class OIDCRPTest extends AbstractClientAppTest {
+
+ @Autowired
+ private OIDCRPDAO oidcrpDAO;
+
+ @Test
+ public void find() {
+ int beforeCount = oidcrpDAO.findAll().size();
+
+ OIDCRP rp = entityFactory.newEntity(OIDCRP.class);
+ rp.setName("OIDC");
+ rp.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+ rp.setDescription("This is a sample OIDC RP");
+ rp.setClientId("clientid");
+ rp.setClientSecret("secret");
+ rp.setSubjectType(OIDCSubjectType.PUBLIC);
+ rp.getSupportedGrantTypes().add("something");
+ rp.getSupportedResponseTypes().add("something");
+
+ AccessPolicy accessPolicy = buildAndSaveAccessPolicy();
+ rp.setAccessPolicy(accessPolicy);
+
+ AuthPolicy authPolicy = buildAndSaveAuthPolicy();
+ rp.setAuthPolicy(authPolicy);
+
+ oidcrpDAO.save(rp);
+
+ assertNotNull(rp);
+ assertNotNull(rp.getKey());
+
+ int afterCount = oidcrpDAO.findAll().size();
+ assertEquals(afterCount, beforeCount + 1);
+
+ rp = oidcrpDAO.findByClientId("clientid");
+ assertNotNull(rp);
+ assertNotNull(rp.getAuthPolicy());
+
+ rp = oidcrpDAO.findByName("OIDC");
+ assertNotNull(rp);
+
+ rp = oidcrpDAO.findByClientAppId(rp.getClientAppId());
+ assertNotNull(rp);
+
+ oidcrpDAO.deleteByClientId("clientid");
+ assertNull(oidcrpDAO.findByName("OIDC"));
+ }
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java
new file mode 100644
index 0000000..a5e50c7
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.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.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.entity.policy.AccessPolicy;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.dao.auth.SAML2SPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+
+@Transactional("Master")
+public class SAML2SPTest extends AbstractClientAppTest {
+
+ @Autowired
+ private SAML2SPDAO saml2spDAO;
+
+ @Test
+ public void find() {
+ int beforeCount = saml2spDAO.findAll().size();
+ SAML2SP sp = entityFactory.newEntity(SAML2SP.class);
+ sp.setName("SAML2");
+ sp.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+ sp.setDescription("This is a sample SAML2 SP");
+ sp.setEntityId("urn:example:saml2:sp");
+ sp.setMetadataLocation("https://example.org/metadata.xml");
+ sp.setRequiredNameIdFormat(SAML2SPNameId.EMAIL_ADDRESS);
+ sp.setEncryptionOptional(true);
+ sp.setEncryptAssertions(true);
+
+ AccessPolicy accessPolicy = buildAndSaveAccessPolicy();
+ sp.setAccessPolicy(accessPolicy);
+
+ AuthPolicy authnPolicy = buildAndSaveAuthPolicy();
+ sp.setAuthPolicy(authnPolicy);
+
+ saml2spDAO.save(sp);
+
+ assertNotNull(sp);
+ assertNotNull(sp.getKey());
+
+ int afterCount = saml2spDAO.findAll().size();
+ assertEquals(afterCount, beforeCount + 1);
+
+ sp = saml2spDAO.findByEntityId(sp.getEntityId());
+ assertNotNull(sp);
+
+ sp = saml2spDAO.findByName(sp.getName());
+ assertNotNull(sp);
+
+ sp = saml2spDAO.findByClientAppId(sp.getClientAppId());
+ assertNotNull(sp);
+
+ saml2spDAO.deleteByEntityId(sp.getEntityId());
+ assertNull(saml2spDAO.findByName(sp.getName()));
+ }
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ClientAppDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ClientAppDataBinder.java
new file mode 100644
index 0000000..0590122
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ClientAppDataBinder.java
@@ -0,0 +1,31 @@
+/*
+ * 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.client.ClientAppTO;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+
+public interface ClientAppDataBinder {
+
+ <T extends ClientApp> T create(ClientAppTO clientAppTO);
+
+ <T extends ClientApp> void update(T clientApp, ClientAppTO clientAppTO);
+
+ <T extends ClientAppTO> T getClientAppTO(ClientApp clientApp);
+}
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
new file mode 100644
index 0000000..67db455
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
@@ -0,0 +1,268 @@
+/*
+ * 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.common.lib.SyncopeClientException;
+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;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+
+@Component
+public class ClientAppDataBinderImpl implements ClientAppDataBinder {
+
+ @Autowired
+ private PolicyDAO policyDAO;
+
+ @Autowired
+ private EntityFactory entityFactory;
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends ClientApp> T create(final ClientAppTO clientAppTO) {
+ if (clientAppTO instanceof SAML2SPTO) {
+ return (T) doCreate((SAML2SPTO) clientAppTO);
+ } else if (clientAppTO instanceof OIDCRPTO) {
+ return (T) doCreate((OIDCRPTO) clientAppTO);
+ } else {
+ throw new IllegalArgumentException("Unsupported client app: " + clientAppTO.getClass().getName());
+ }
+ }
+
+ @Override
+ public <T extends ClientApp> void update(final T clientApp, final ClientAppTO clientAppTO) {
+ if (clientAppTO instanceof SAML2SPTO) {
+ doUpdate((SAML2SP) clientApp, (SAML2SPTO) clientAppTO);
+ } else if (clientAppTO instanceof OIDCRPTO) {
+ doUpdate((OIDCRP) clientApp, (OIDCRPTO) clientAppTO);
+ } else {
+ throw new IllegalArgumentException("Unsupported client app: " + clientAppTO.getClass().getName());
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T extends ClientAppTO> T getClientAppTO(final ClientApp clientApp) {
+ if (clientApp instanceof SAML2SP) {
+ return (T) getClientAppTO((SAML2SP) clientApp);
+ } else if (clientApp instanceof OIDCRP) {
+ return (T) getClientAppTO((OIDCRP) clientApp);
+ } else {
+ throw new IllegalArgumentException("Unsupported client app: " + clientApp.getClass().getName());
+ }
+ }
+
+ private SAML2SP doCreate(final SAML2SPTO clientAppTO) {
+ SAML2SP saml2sp = entityFactory.newEntity(SAML2SP.class);
+ update(saml2sp, clientAppTO);
+ return saml2sp;
+ }
+
+ private void doUpdate(final SAML2SP clientApp, final SAML2SPTO clientAppTO) {
+ clientApp.setDescription(clientAppTO.getDescription());
+ clientApp.setName(clientAppTO.getName());
+ clientApp.setClientAppId(clientAppTO.getClientAppId());
+ clientApp.setEntityId(clientAppTO.getEntityId());
+ clientApp.setMetadataLocation(clientAppTO.getMetadataLocation());
+ clientApp.setMetadataSignatureLocation(clientAppTO.getMetadataLocation());
+ clientApp.setSignAssertions(clientAppTO.isSignAssertions());
+ clientApp.setSignResponses(clientAppTO.isSignResponses());
+ clientApp.setEncryptionOptional(clientAppTO.isEncryptionOptional());
+ clientApp.setEncryptAssertions(clientAppTO.isEncryptAssertions());
+ clientApp.setRequiredAuthenticationContextClass(clientAppTO.getRequiredAuthenticationContextClass());
+ clientApp.setRequiredNameIdFormat(clientAppTO.getRequiredNameIdFormat());
+ clientApp.setSkewAllowance(clientAppTO.getSkewAllowance());
+ clientApp.setNameIdQualifier(clientAppTO.getNameIdQualifier());
+ clientApp.setAssertionAudiences(clientAppTO.getAssertionAudiences());
+ clientApp.setServiceProviderNameIdQualifier(clientAppTO.getServiceProviderNameIdQualifier());
+
+ if (clientAppTO.getAuthPolicy() == null) {
+ clientApp.setAuthPolicy(null);
+ } else {
+ Policy policy = policyDAO.find(clientAppTO.getAuthPolicy());
+ if (policy instanceof AuthPolicy) {
+ clientApp.setAuthPolicy((AuthPolicy) policy);
+ } else {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+ sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
+ + ", found " + policy.getClass().getSimpleName());
+ throw sce;
+ }
+ }
+
+ if (clientAppTO.getAccessPolicy() == null) {
+ clientApp.setAccessPolicy(null);
+ } else {
+ Policy policy = policyDAO.find(clientAppTO.getAccessPolicy());
+ if (policy instanceof AccessPolicy) {
+ clientApp.setAccessPolicy((AccessPolicy) policy);
+ } else {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+ sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
+ + ", found " + policy.getClass().getSimpleName());
+ throw sce;
+ }
+ }
+
+ if (clientAppTO.getAttrReleasePolicy() == null) {
+ clientApp.setAttrReleasePolicy(null);
+ } else {
+ Policy policy = policyDAO.find(clientAppTO.getAttrReleasePolicy());
+ if (policy instanceof AttrReleasePolicy) {
+ clientApp.setAttrReleasePolicy((AttrReleasePolicy) policy);
+ } else {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+ sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
+ + ", found " + policy.getClass().getSimpleName());
+ throw sce;
+ }
+ }
+ }
+
+ private SAML2SPTO getClientAppTO(final SAML2SP clientApp) {
+ SAML2SPTO clientAppTO = new SAML2SPTO();
+
+ clientAppTO.setName(clientApp.getName());
+ clientAppTO.setKey(clientApp.getKey());
+ clientAppTO.setDescription(clientApp.getDescription());
+ clientAppTO.setClientAppId(clientApp.getClientAppId());
+ clientAppTO.setEntityId(clientApp.getEntityId());
+ clientAppTO.setMetadataLocation(clientApp.getMetadataLocation());
+ clientAppTO.setMetadataSignatureLocation(clientApp.getMetadataLocation());
+ clientAppTO.setSignAssertions(clientApp.isSignAssertions());
+ clientAppTO.setSignResponses(clientApp.isSignResponses());
+ clientAppTO.setEncryptionOptional(clientApp.isEncryptionOptional());
+ clientAppTO.setEncryptAssertions(clientApp.isEncryptAssertions());
+ clientAppTO.setRequiredAuthenticationContextClass(clientApp.getRequiredAuthenticationContextClass());
+ clientAppTO.setRequiredNameIdFormat(clientApp.getRequiredNameIdFormat());
+ clientAppTO.setSkewAllowance(clientApp.getSkewAllowance());
+ clientAppTO.setNameIdQualifier(clientApp.getNameIdQualifier());
+ clientAppTO.setAssertionAudiences(clientApp.getAssertionAudiences());
+ clientAppTO.setServiceProviderNameIdQualifier(clientApp.getServiceProviderNameIdQualifier());
+
+ clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
+ ? null : clientApp.getAuthPolicy().getKey());
+ clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
+ ? null : clientApp.getAccessPolicy().getKey());
+ clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
+ ? null : clientApp.getAttrReleasePolicy().getKey());
+
+ return clientAppTO;
+ }
+
+ private OIDCRP doCreate(final OIDCRPTO clientAppTO) {
+ OIDCRP oidcrp = entityFactory.newEntity(OIDCRP.class);
+ update(oidcrp, clientAppTO);
+ return oidcrp;
+ }
+
+ private void doUpdate(final OIDCRP clientApp, final OIDCRPTO clientAppTO) {
+ clientApp.setName(clientAppTO.getName());
+ clientApp.setClientAppId(clientAppTO.getClientAppId());
+ clientApp.setDescription(clientAppTO.getDescription());
+ clientApp.setClientSecret(clientAppTO.getClientSecret());
+ clientApp.setClientId(clientAppTO.getClientId());
+ clientApp.setSignIdToken(clientAppTO.isSignIdToken());
+ clientApp.setJwks(clientAppTO.getJwks());
+ clientApp.setSubjectType(clientAppTO.getSubjectType());
+ clientApp.getRedirectUris().addAll(clientAppTO.getRedirectUris());
+ clientApp.getSupportedGrantTypes().addAll(clientAppTO.getSupportedGrantTypes());
+ clientApp.getSupportedResponseTypes().addAll(clientAppTO.getSupportedResponseTypes());
+
+ if (clientAppTO.getAuthPolicy() == null) {
+ clientApp.setAuthPolicy(null);
+ } else {
+ Policy policy = policyDAO.find(clientAppTO.getAuthPolicy());
+ if (policy instanceof AuthPolicy) {
+ clientApp.setAuthPolicy((AuthPolicy) policy);
+ } else {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+ sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
+ + ", found " + policy.getClass().getSimpleName());
+ throw sce;
+ }
+ }
+
+ if (clientAppTO.getAccessPolicy() == null) {
+ clientApp.setAccessPolicy(null);
+ } else {
+ Policy policy = policyDAO.find(clientAppTO.getAccessPolicy());
+ if (policy instanceof AccessPolicy) {
+ clientApp.setAccessPolicy((AccessPolicy) policy);
+ } else {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+ sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
+ + ", found " + policy.getClass().getSimpleName());
+ throw sce;
+ }
+ }
+
+ if (clientAppTO.getAttrReleasePolicy() == null) {
+ clientApp.setAttrReleasePolicy(null);
+ } else {
+ Policy policy = policyDAO.find(clientAppTO.getAttrReleasePolicy());
+ if (policy instanceof AttrReleasePolicy) {
+ clientApp.setAttrReleasePolicy((AttrReleasePolicy) policy);
+ } else {
+ SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+ sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
+ + ", found " + policy.getClass().getSimpleName());
+ throw sce;
+ }
+ }
+ }
+
+ private OIDCRPTO getClientAppTO(final OIDCRP clientApp) {
+ OIDCRPTO clientAppTO = new OIDCRPTO();
+
+ clientAppTO.setName(clientApp.getName());
+ clientAppTO.setKey(clientApp.getKey());
+ clientAppTO.setDescription(clientApp.getDescription());
+ clientAppTO.setClientAppId(clientApp.getClientAppId());
+ clientAppTO.setClientId(clientApp.getClientId());
+ clientAppTO.setClientSecret(clientApp.getClientSecret());
+ clientAppTO.setSignIdToken(clientApp.isSignIdToken());
+ clientAppTO.setJwks(clientApp.getJwks());
+ clientAppTO.setSubjectType(clientApp.getSubjectType());
+ clientAppTO.getRedirectUris().addAll(clientApp.getRedirectUris());
+ clientAppTO.getSupportedGrantTypes().addAll(clientApp.getSupportedGrantTypes());
+ clientAppTO.getSupportedResponseTypes().addAll(clientApp.getSupportedResponseTypes());
+
+ clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
+ ? null : clientApp.getAuthPolicy().getKey());
+ clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
+ ? null : clientApp.getAccessPolicy().getKey());
+ clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
+ ? null : clientApp.getAttrReleasePolicy().getKey());
+
+ return clientAppTO;
+ }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
new file mode 100644
index 0000000..3ee1586
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
@@ -0,0 +1,155 @@
+/*
+ * 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.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.AccessPolicyTO;
+import org.apache.syncope.common.lib.to.client.OIDCRPTO;
+import org.apache.syncope.common.lib.to.client.SAML2SPTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+
+public class ClientAppITCase extends AbstractITCase {
+
+ @Test
+ public void createSAML2SP() {
+ createClientApp(ClientAppType.SAML2SP, buildSAML2SP());
+ }
+
+ @Test
+ public void readSAML2SP() {
+ SAML2SPTO samlSpTO = buildSAML2SP();
+ samlSpTO = createClientApp(ClientAppType.SAML2SP, samlSpTO);
+
+ SAML2SPTO found = clientAppService.read(ClientAppType.SAML2SP, samlSpTO.getKey());
+ assertNotNull(found);
+ assertFalse(StringUtils.isBlank(found.getEntityId()));
+ assertFalse(StringUtils.isBlank(found.getMetadataLocation()));
+ assertTrue(found.isEncryptAssertions());
+ assertTrue(found.isEncryptionOptional());
+ assertNotNull(found.getRequiredNameIdFormat());
+ assertNotNull(found.getAccessPolicy());
+ assertNotNull(found.getAuthPolicy());
+ }
+
+ @Test
+ public void updateSAML2SP() {
+ SAML2SPTO samlSpTO = buildSAML2SP();
+ samlSpTO = createClientApp(ClientAppType.SAML2SP, samlSpTO);
+
+ AccessPolicyTO accessPolicyTO = new AccessPolicyTO();
+ accessPolicyTO.setKey("NewAccessPolicyTest_" + getUUIDString());
+ accessPolicyTO.setDescription("New Access policy");
+ accessPolicyTO = createPolicy(PolicyType.ACCESS, accessPolicyTO);
+ assertNotNull(accessPolicyTO);
+
+ samlSpTO.setEntityId("newEntityId");
+ samlSpTO.setAccessPolicy(accessPolicyTO.getKey());
+
+ clientAppService.update(ClientAppType.SAML2SP, samlSpTO);
+ SAML2SPTO updated = clientAppService.read(ClientAppType.SAML2SP, samlSpTO.getKey());
+
+ assertNotNull(updated);
+ assertEquals("newEntityId", updated.getEntityId());
+ assertNotNull(updated.getAccessPolicy());
+ }
+
+ @Test
+ public void deleteSAML2SP() {
+ SAML2SPTO samlSpTO = buildSAML2SP();
+ samlSpTO = createClientApp(ClientAppType.SAML2SP, samlSpTO);
+
+ clientAppService.delete(ClientAppType.SAML2SP, samlSpTO.getKey());
+
+ try {
+ clientAppService.read(ClientAppType.SAML2SP, samlSpTO.getKey());
+ fail("This should not happen");
+ } catch (SyncopeClientException e) {
+ assertNotNull(e);
+ }
+ }
+
+ @Test
+ public void createOIDCRP() {
+ createClientApp(ClientAppType.OIDCRP, buildOIDCRP());
+ }
+
+ @Test
+ public void readOIDCRP() {
+ OIDCRPTO oidcrpTO = buildOIDCRP();
+ oidcrpTO = createClientApp(ClientAppType.OIDCRP, oidcrpTO);
+
+ OIDCRPTO found = clientAppService.read(ClientAppType.OIDCRP, oidcrpTO.getKey());
+ assertNotNull(found);
+ assertFalse(StringUtils.isBlank(found.getClientId()));
+ assertFalse(StringUtils.isBlank(found.getClientSecret()));
+ assertNotNull(found.getSubjectType());
+ assertFalse(found.getSupportedGrantTypes().isEmpty());
+ assertFalse(found.getSupportedResponseTypes().isEmpty());
+ assertNotNull(found.getAccessPolicy());
+ assertNotNull(found.getAuthPolicy());
+ }
+
+ @Test
+ public void updateOIDCRP() {
+ OIDCRPTO oidcrpTO = buildOIDCRP();
+ oidcrpTO = createClientApp(ClientAppType.OIDCRP, oidcrpTO);
+
+ AccessPolicyTO accessPolicyTO = new AccessPolicyTO();
+ accessPolicyTO.setKey("NewAccessPolicyTest_" + getUUIDString());
+ accessPolicyTO.setDescription("New Access policy");
+ accessPolicyTO = createPolicy(PolicyType.ACCESS, accessPolicyTO);
+ assertNotNull(accessPolicyTO);
+
+ oidcrpTO.setClientId("newClientId");
+ oidcrpTO.setAccessPolicy(accessPolicyTO.getKey());
+
+ clientAppService.update(ClientAppType.OIDCRP, oidcrpTO);
+ OIDCRPTO updated = clientAppService.read(ClientAppType.OIDCRP, oidcrpTO.getKey());
+
+ assertNotNull(updated);
+ assertEquals("newClientId", updated.getClientId());
+ assertNotNull(updated.getAccessPolicy());
+ }
+
+ @Test
+ public void delete() {
+ OIDCRPTO oidcrpTO = buildOIDCRP();
+ oidcrpTO = createClientApp(ClientAppType.OIDCRP, oidcrpTO);
+
+ clientAppService.delete(ClientAppType.OIDCRP, oidcrpTO.getKey());
+
+ try {
+ clientAppService.read(ClientAppType.OIDCRP, oidcrpTO.getKey());
+ fail("This should not happen");
+ } catch (SyncopeClientException e) {
+ assertNotNull(e);
+ }
+ }
+
+}