This is an automated email from the ASF dual-hosted git repository.
mmoayyed pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new e2038a4 SYNCOPE-1556: Manage OIDC JWKS (#196)
e2038a4 is described below
commit e2038a4d278501b97f9fad35b5ca9ff751d8fb77
Author: Misagh Moayyed <[email protected]>
AuthorDate: Fri Jun 12 20:22:30 2020 +0430
SYNCOPE-1556: Manage OIDC JWKS (#196)
---
.../apache/syncope/common/lib/to/OIDCJWKSTO.java | 103 +++++++++++++++++++++
.../syncope/common/lib/types/AMEntitlement.java | 8 ++
.../rest/api/service/OIDCJWKSConfService.java | 54 +++++++++++
.../rest/api/service/wa/OIDCJWKSService.java | 68 ++++++++++++++
.../core/logic/GoogleMfaAuthAccountLogic.java | 4 +-
.../core/logic/GoogleMfaAuthTokenLogic.java | 4 +-
.../apache/syncope/core/logic/OIDCJWKSLogic.java | 91 ++++++++++++++++++
.../syncope/core/logic/SAML2IdPMetadataLogic.java | 6 +-
.../rest/cxf/service/OIDCJWKSConfServiceImpl.java} | 27 ++++--
.../rest/cxf/service/wa/OIDCJWKSServiceImpl.java | 53 +++++++++++
.../persistence/api/dao/auth/OIDCJWKSDAO.java} | 16 ++--
.../persistence/api/entity/auth/OIDCJWKS.java} | 13 +--
core/persistence-jpa-json/pom.xml | 5 +
core/persistence-jpa/pom.xml | 5 +
.../persistence/jpa/dao/auth/JPAOIDCJWKSDAO.java | 58 ++++++++++++
.../persistence/jpa/entity/JPAEntityFactory.java | 4 +
.../persistence/jpa/entity/auth/JPAOIDCJWKS.java} | 32 +++++--
.../core/persistence/jpa/inner/OIDCJWKSTest.java | 58 ++++++++++++
...MetadataBinder.java => OIDCJWKSDataBinder.java} | 15 ++-
...Binder.java => SAML2IdPMetadataDataBinder.java} | 2 +-
core/provisioning-java/pom.xml | 5 +
.../java/data/OIDCJWKSDataBinderImpl.java | 67 ++++++++++++++
...pl.java => SAML2IdPMetadataDataBinderImpl.java} | 4 +-
.../org/apache/syncope/fit/AbstractITCase.java | 8 ++
.../syncope/fit/core/OIDCJWKSConfITCase.java | 79 ++++++++++++++++
.../apache/syncope/fit/core/OIDCJWKSITCase.java | 54 +++++++++++
pom.xml | 17 ++++
wa/starter/pom.xml | 8 ++
.../wa/starter/config/SyncopeWAConfiguration.java | 8 ++
.../oidc/SyncopeWAOIDCJWKSGeneratorService.java | 65 +++++++++++++
30 files changed, 891 insertions(+), 50 deletions(-)
diff --git
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCJWKSTO.java
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCJWKSTO.java
new file mode 100644
index 0000000..abbcbde
--- /dev/null
+++
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/OIDCJWKSTO.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+
+public class OIDCJWKSTO implements EntityTO {
+
+ private static final long serialVersionUID = 1285073386484048953L;
+
+ private String key;
+
+ private String json;
+
+ public String getJson() {
+ return json;
+ }
+
+ public void setJson(final String json) {
+ this.json = json;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder()
+ .appendSuper(super.hashCode())
+ .append(key)
+ .append(json)
+ .toHashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (obj.getClass() != getClass()) {
+ return false;
+ }
+ OIDCJWKSTO rhs = (OIDCJWKSTO) obj;
+ return new EqualsBuilder()
+ .appendSuper(super.equals(obj))
+ .append(this.key, rhs.key)
+ .append(this.json, rhs.json)
+ .isEquals();
+ }
+
+ @Override
+ public String toString() {
+ return new ToStringBuilder(this)
+ .append("key", key)
+ .append("json", json)
+ .toString();
+ }
+
+ public static class Builder {
+
+ private final OIDCJWKSTO instance = new OIDCJWKSTO();
+
+ public OIDCJWKSTO.Builder json(final String json) {
+ instance.setJson(json);
+ return this;
+ }
+
+ public OIDCJWKSTO.Builder key(final String key) {
+ instance.setKey(key);
+ return this;
+ }
+
+ public OIDCJWKSTO build() {
+ return instance;
+ }
+ }
+}
diff --git
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
index 6577bd9..6e2ccaa 100644
---
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
+++
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMEntitlement.java
@@ -96,6 +96,14 @@ public final class AMEntitlement {
public static final String GOOGLE_MFA_COUNT_ACCOUNTS =
"GOOGLE_MFA_COUNT_ACCOUNTS";
+ public static final String OIDC_JWKS_CREATE = "OIDC_JWKS_CREATE";
+
+ public static final String OIDC_JWKS_READ = "OIDC_JWKS_READ";
+
+ public static final String OIDC_JWKS_UPDATE = "OIDC_JWKS_UPDATE";
+
+ public static final String OIDC_JWKS_DELETE = "OIDC_JWKS_DELETE";
+
private static final Set<String> VALUES;
static {
diff --git
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCJWKSConfService.java
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCJWKSConfService.java
new file mode 100644
index 0000000..fbc8de7
--- /dev/null
+++
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/OIDCJWKSConfService.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+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 org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Tag(name = "OIDC Json Web Keystore")
+@SecurityRequirements({
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer")})
+@Path("oidc/jwks")
+public interface OIDCJWKSConfService extends JAXRSService {
+
+ @ApiResponse(responseCode = "204", description = "Operation was
successful")
+ @PUT
+ @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML})
+ @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML})
+ void update(@NotNull OIDCJWKSTO jwksTO);
+
+ @DELETE
+ @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML })
+ @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML })
+ Response delete();
+}
diff --git
a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/OIDCJWKSService.java
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/OIDCJWKSService.java
new file mode 100644
index 0000000..6c28c9a
--- /dev/null
+++
b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/OIDCJWKSService.java
@@ -0,0 +1,68 @@
+/*
+ * 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.wa;
+
+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 org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Tag(name = "OIDC Json Web Keystore")
+@SecurityRequirements({
+ @SecurityRequirement(name = "BasicAuthentication"),
+ @SecurityRequirement(name = "Bearer")})
+@Path("wa/oidc/jwks")
+public interface OIDCJWKSService extends JAXRSService {
+
+ @GET
+ @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML})
+ OIDCJWKSTO get();
+
+ @ApiResponses({
+ @ApiResponse(responseCode = "201",
+ description = "JWKS successfully created", headers = {
+ @Header(name = RESTHeaders.RESOURCE_KEY, schema =
+ @Schema(type = "string"),
+ description = "UUID generated for the entity created"),
+ @Header(name = HttpHeaders.LOCATION, schema =
+ @Schema(type = "string"),
+ description = "URL of the entity created")}),
+ @ApiResponse(responseCode = "409",
+ description = "JWKS already exists")})
+ @POST
+ @Consumes({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML})
+ @Produces({MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML,
MediaType.APPLICATION_XML})
+ Response set();
+}
diff --git
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthAccountLogic.java
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthAccountLogic.java
index a534a02..1ba9274 100644
---
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthAccountLogic.java
+++
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthAccountLogic.java
@@ -29,6 +29,7 @@ import
org.apache.syncope.core.persistence.api.dao.auth.AuthProfileDAO;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.auth.AuthProfile;
import org.apache.syncope.core.provisioning.api.data.AuthProfileDataBinder;
+import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@@ -37,7 +38,6 @@ import
org.springframework.transaction.annotation.Transactional;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
-import java.util.UUID;
import java.util.stream.Collectors;
@Component
@@ -108,7 +108,7 @@ public class GoogleMfaAuthAccountLogic extends
AbstractTransactionalLogic<AuthPr
});
if (acct.getKey() == null) {
- acct.setKey(UUID.randomUUID().toString());
+ acct.setKey(SecureRandomUtils.generateRandomUUID().toString());
}
profile.setGoogleMfaAuthAccount(acct);
return authProfileDAO.save(profile).getGoogleMfaAuthAccount();
diff --git
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthTokenLogic.java
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthTokenLogic.java
index 9b43f6c..759b1d0 100644
---
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthTokenLogic.java
+++
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/GoogleMfaAuthTokenLogic.java
@@ -22,7 +22,6 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
-import java.util.UUID;
import java.util.function.Predicate;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.syncope.common.lib.to.AuthProfileTO;
@@ -34,6 +33,7 @@ import
org.apache.syncope.core.persistence.api.dao.auth.AuthProfileDAO;
import org.apache.syncope.core.persistence.api.entity.EntityFactory;
import org.apache.syncope.core.persistence.api.entity.auth.AuthProfile;
import org.apache.syncope.core.provisioning.api.data.AuthProfileDataBinder;
+import org.apache.syncope.core.spring.security.SecureRandomUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@@ -106,7 +106,7 @@ public class GoogleMfaAuthTokenLogic extends
AbstractTransactionalLogic<AuthProf
});
if (token.getKey() == null) {
- token.setKey(UUID.randomUUID().toString());
+ token.setKey(SecureRandomUtils.generateRandomUUID().toString());
}
profile.add(token);
profile = authProfileDAO.save(profile);
diff --git
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java
new file mode 100644
index 0000000..4212427
--- /dev/null
+++
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/OIDCJWKSLogic.java
@@ -0,0 +1,91 @@
+/*
+ * 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 org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.OIDCJWKSDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder;
+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;
+
+import java.lang.reflect.Method;
+
+@Component
+public class OIDCJWKSLogic extends AbstractTransactionalLogic<OIDCJWKSTO> {
+
+ @Autowired
+ private OIDCJWKSDataBinder binder;
+
+ @Autowired
+ private OIDCJWKSDAO dao;
+
+ @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_READ + "') "
+ + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+ @Transactional(readOnly = true)
+ public OIDCJWKSTO get() {
+ OIDCJWKS jwks = dao.get();
+ if (jwks != null) {
+ return binder.get(jwks);
+ }
+ throw new NotFoundException("OIDC JWKS not found");
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_CREATE + "') "
+ + "or hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+ public OIDCJWKSTO set() {
+ OIDCJWKS jwks = dao.get();
+ if (jwks == null) {
+ return binder.get(dao.save(binder.create()));
+ }
+ throw SyncopeClientException.build(ClientExceptionType.EntityExists);
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_UPDATE + "')")
+ public OIDCJWKSTO update(final OIDCJWKSTO jwksTO) {
+ OIDCJWKS jwks = dao.get();
+ if (jwks == null) {
+ throw SyncopeClientException.build(ClientExceptionType.NotFound);
+ }
+ return binder.get(dao.save(binder.update(jwks, jwksTO)));
+ }
+
+ @Override
+ protected OIDCJWKSTO resolveReference(final Method method, final Object...
args)
+ throws UnresolvedReferenceException {
+ OIDCJWKS jwks = dao.get();
+ if (jwks == null) {
+ throw new UnresolvedReferenceException();
+ }
+ return binder.get(jwks);
+ }
+
+ @PreAuthorize("hasRole('" + AMEntitlement.OIDC_JWKS_DELETE + "')")
+ public void delete() {
+ dao.delete();
+ }
+}
diff --git
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java
index 87a0461..520d0c6 100644
---
a/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java
+++
b/core/am/logic/src/main/java/org/apache/syncope/core/logic/SAML2IdPMetadataLogic.java
@@ -18,8 +18,6 @@
*/
package org.apache.syncope.core.logic;
-import static org.apache.syncope.core.logic.AbstractLogic.LOG;
-
import java.lang.reflect.Method;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.syncope.common.lib.SyncopeClientException;
@@ -30,7 +28,7 @@ import org.apache.syncope.common.lib.types.IdRepoEntitlement;
import org.apache.syncope.core.persistence.api.dao.NotFoundException;
import org.apache.syncope.core.persistence.api.dao.auth.SAML2IdPMetadataDAO;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
-import org.apache.syncope.core.provisioning.api.data.SAML2IdPMetadataBinder;
+import
org.apache.syncope.core.provisioning.api.data.SAML2IdPMetadataDataBinder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Component;
@@ -40,7 +38,7 @@ import
org.springframework.transaction.annotation.Transactional;
public class SAML2IdPMetadataLogic extends
AbstractTransactionalLogic<SAML2IdPMetadataTO> {
@Autowired
- private SAML2IdPMetadataBinder binder;
+ private SAML2IdPMetadataDataBinder binder;
@Autowired
private SAML2IdPMetadataDAO saml2IdPMetadataDAO;
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCJWKSConfServiceImpl.java
similarity index 52%
copy from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
copy to
core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCJWKSConfServiceImpl.java
index e2e190e..a6a534b 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
+++
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/OIDCJWKSConfServiceImpl.java
@@ -16,17 +16,30 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.core.provisioning.api.data;
+package org.apache.syncope.core.rest.cxf.service;
-import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.rest.api.service.OIDCJWKSConfService;
+import org.apache.syncope.core.logic.OIDCJWKSLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
-public interface SAML2IdPMetadataBinder {
+import javax.ws.rs.core.Response;
- SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
+@Service
+public class OIDCJWKSConfServiceImpl extends AbstractServiceImpl implements
OIDCJWKSConfService {
- SAML2IdPMetadata update(SAML2IdPMetadata saml2IdPMetadata,
SAML2IdPMetadataTO saml2IdPMetadataTO);
+ @Autowired
+ private OIDCJWKSLogic logic;
- SAML2IdPMetadataTO getSAML2IdPMetadataTO(SAML2IdPMetadata
saml2IdPMetadata);
+ @Override
+ public void update(final OIDCJWKSTO jwksTO) {
+ logic.update(jwksTO);
+ }
+ @Override
+ public Response delete() {
+ logic.delete();
+ return Response.noContent().build();
+ }
}
diff --git
a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/OIDCJWKSServiceImpl.java
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/OIDCJWKSServiceImpl.java
new file mode 100644
index 0000000..19df5d4
--- /dev/null
+++
b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/OIDCJWKSServiceImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.wa;
+
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.wa.OIDCJWKSService;
+import org.apache.syncope.core.logic.OIDCJWKSLogic;
+import org.apache.syncope.core.rest.cxf.service.AbstractServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.ws.rs.core.Response;
+
+import java.net.URI;
+
+@Service
+public class OIDCJWKSServiceImpl extends AbstractServiceImpl implements
OIDCJWKSService {
+ @Autowired
+ private OIDCJWKSLogic logic;
+
+ @Override
+ public OIDCJWKSTO get() {
+ return logic.get();
+ }
+
+ @Override
+ public Response set() {
+ OIDCJWKSTO jwks = logic.set();
+ URI location =
uriInfo.getAbsolutePathBuilder().path(jwks.getKey()).build();
+ return Response.created(location).
+ header(RESTHeaders.RESOURCE_KEY, jwks.getKey()).
+ entity(jwks).
+ build();
+ }
+}
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCJWKSDAO.java
similarity index 62%
copy from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
copy to
core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCJWKSDAO.java
index e2e190e..98acd8a 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
+++
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCJWKSDAO.java
@@ -16,17 +16,15 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.core.provisioning.api.data;
+package org.apache.syncope.core.persistence.api.dao.auth;
-import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.persistence.api.dao.DAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
-public interface SAML2IdPMetadataBinder {
+public interface OIDCJWKSDAO extends DAO<OIDCJWKS> {
+ OIDCJWKS get();
- SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
-
- SAML2IdPMetadata update(SAML2IdPMetadata saml2IdPMetadata,
SAML2IdPMetadataTO saml2IdPMetadataTO);
-
- SAML2IdPMetadataTO getSAML2IdPMetadataTO(SAML2IdPMetadata
saml2IdPMetadata);
+ OIDCJWKS save(OIDCJWKS jwks);
+ void delete();
}
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCJWKS.java
similarity index 62%
copy from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
copy to
core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCJWKS.java
index e2e190e..fe617a0 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
+++
b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCJWKS.java
@@ -16,17 +16,14 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.core.provisioning.api.data;
+package org.apache.syncope.core.persistence.api.entity.auth;
-import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.persistence.api.entity.Entity;
-public interface SAML2IdPMetadataBinder {
+public interface OIDCJWKS extends Entity {
- SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
+ String getJson();
- SAML2IdPMetadata update(SAML2IdPMetadata saml2IdPMetadata,
SAML2IdPMetadataTO saml2IdPMetadataTO);
-
- SAML2IdPMetadataTO getSAML2IdPMetadataTO(SAML2IdPMetadata
saml2IdPMetadata);
+ void setJson(String json);
}
diff --git a/core/persistence-jpa-json/pom.xml
b/core/persistence-jpa-json/pom.xml
index 83db64d..e71f5ae 100644
--- a/core/persistence-jpa-json/pom.xml
+++ b/core/persistence-jpa-json/pom.xml
@@ -86,6 +86,11 @@ under the License.
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/core/persistence-jpa/pom.xml b/core/persistence-jpa/pom.xml
index 0d6b810..8019673 100644
--- a/core/persistence-jpa/pom.xml
+++ b/core/persistence-jpa/pom.xml
@@ -138,6 +138,11 @@ under the License.
<artifactId>bcprov-jdk15on</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCJWKSDAO.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCJWKSDAO.java
new file mode 100644
index 0000000..19cc1e3
--- /dev/null
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCJWKSDAO.java
@@ -0,0 +1,58 @@
+/*
+ * 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.api.dao.auth.OIDCJWKSDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCJWKS;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+
+@Repository
+public class JPAOIDCJWKSDAO extends AbstractDAO<OIDCJWKS> implements
OIDCJWKSDAO {
+
+ @Transactional(readOnly = true)
+ @Override
+ public OIDCJWKS get() {
+ try {
+ TypedQuery<OIDCJWKS> query = entityManager().
+ createQuery("SELECT e FROM " +
JPAOIDCJWKS.class.getSimpleName() + " e", OIDCJWKS.class);
+ return query.getSingleResult();
+ } catch (final NoResultException e) {
+ LOG.debug(e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ public OIDCJWKS save(final OIDCJWKS jwks) {
+ return entityManager().merge(jwks);
+ }
+
+ @Override
+ public void delete() {
+ entityManager().
+ createQuery("DELETE FROM " + JPAOIDCJWKS.class.getSimpleName()).
+ executeUpdate();
+ }
+}
diff --git
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
index 131709d..6f730ef 100644
---
a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAEntityFactory.java
@@ -59,6 +59,7 @@ import
org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
import org.apache.syncope.core.persistence.api.entity.auth.AuthProfile;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
@@ -114,6 +115,7 @@ import
org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrVal
import
org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAARelationship;
import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAnyObject;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthProfile;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCJWKS;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPAOIDCRP;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SP;
import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SPKeystore;
@@ -337,6 +339,8 @@ public class JPAEntityFactory implements EntityFactory {
result = (E) new JPASAML2SPKeystore();
} else if (reference.equals(AuthProfile.class)) {
result = (E) new JPAAuthProfile();
+ } else if (reference.equals(OIDCJWKS.class)) {
+ result = (E) new JPAOIDCJWKS();
} else {
throw new IllegalArgumentException("Could not find a JPA
implementation of " + reference.getName());
}
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCJWKS.java
similarity index 50%
copy from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
copy to
core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCJWKS.java
index e2e190e..d01b038 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
+++
b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCJWKS.java
@@ -16,17 +16,35 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.syncope.core.provisioning.api.data;
+package org.apache.syncope.core.persistence.jpa.entity.auth;
-import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import
org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
-public interface SAML2IdPMetadataBinder {
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Lob;
+import javax.persistence.Table;
- SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
+@Entity
+@Table(name = JPAOIDCJWKS.TABLE)
+public class JPAOIDCJWKS extends AbstractGeneratedKeyEntity implements
OIDCJWKS {
- SAML2IdPMetadata update(SAML2IdPMetadata saml2IdPMetadata,
SAML2IdPMetadataTO saml2IdPMetadataTO);
+ public static final String TABLE = "OIDCJWKS";
- SAML2IdPMetadataTO getSAML2IdPMetadataTO(SAML2IdPMetadata
saml2IdPMetadata);
+ private static final long serialVersionUID = 47352617217394093L;
+ @Column
+ @Lob
+ private String json;
+
+ @Override
+ public String getJson() {
+ return this.json;
+ }
+
+ @Override
+ public void setJson(final String json) {
+ this.json = json;
+ }
}
diff --git
a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCJWKSTest.java
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCJWKSTest.java
new file mode 100644
index 0000000..bd23665
--- /dev/null
+++
b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCJWKSTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import org.apache.syncope.core.persistence.api.dao.auth.OIDCJWKSDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+@Transactional("Master")
+public class OIDCJWKSTest extends AbstractTest {
+
+ @Autowired
+ private OIDCJWKSDAO jwksDAO;
+
+ @Test
+ public void save() throws Exception {
+ OIDCJWKS jwks = entityFactory.newEntity(OIDCJWKS.class);
+
+ RSAKey jwk = new RSAKeyGenerator(2048)
+ .keyUse(KeyUse.SIGNATURE)
+ .keyID(UUID.randomUUID().toString())
+ .generate();
+
+ String json = new JWKSet(jwk).toString();
+ jwks.setJson(json);
+ jwks = jwksDAO.save(jwks);
+ assertNotNull(jwks);
+ assertNotNull(jwks.getKey());
+
+ }
+}
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
similarity index 66%
copy from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
copy to
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
index e2e190e..164c620 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/OIDCJWKSDataBinder.java
@@ -18,15 +18,14 @@
*/
package org.apache.syncope.core.provisioning.api.data;
-import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
-import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
-public interface SAML2IdPMetadataBinder {
+public interface OIDCJWKSDataBinder {
+
+ OIDCJWKSTO get(OIDCJWKS jwks);
- SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
-
- SAML2IdPMetadata update(SAML2IdPMetadata saml2IdPMetadata,
SAML2IdPMetadataTO saml2IdPMetadataTO);
-
- SAML2IdPMetadataTO getSAML2IdPMetadataTO(SAML2IdPMetadata
saml2IdPMetadata);
+ OIDCJWKS create();
+ OIDCJWKS update(OIDCJWKS oidcjwks, OIDCJWKSTO jwksTO);
}
diff --git
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataDataBinder.java
similarity index 96%
rename from
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
rename to
core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataDataBinder.java
index e2e190e..3cabfbb 100644
---
a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataBinder.java
+++
b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/SAML2IdPMetadataDataBinder.java
@@ -21,7 +21,7 @@ package org.apache.syncope.core.provisioning.api.data;
import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
-public interface SAML2IdPMetadataBinder {
+public interface SAML2IdPMetadataDataBinder {
SAML2IdPMetadata create(SAML2IdPMetadataTO saml2IdPMetadataTO);
diff --git a/core/provisioning-java/pom.xml b/core/provisioning-java/pom.xml
index 5a2211e..9ac7db6 100644
--- a/core/provisioning-java/pom.xml
+++ b/core/provisioning-java/pom.xml
@@ -97,6 +97,11 @@ under the License.
</dependency>
<dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>org.apache.syncope.core</groupId>
<artifactId>syncope-core-workflow-api</artifactId>
<version>${project.version}</version>
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
new file mode 100644
index 0000000..7ff3397
--- /dev/null
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCJWKSDataBinderImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCJWKS;
+import org.apache.syncope.core.provisioning.api.data.OIDCJWKSDataBinder;
+import org.apache.syncope.core.spring.security.SecureRandomUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class OIDCJWKSDataBinderImpl implements OIDCJWKSDataBinder {
+ @Autowired
+ private EntityFactory entityFactory;
+
+ @Override
+ public OIDCJWKSTO get(final OIDCJWKS jwks) {
+ return new OIDCJWKSTO.Builder().
+ json(jwks.getJson()).
+ key(jwks.getKey()).
+ build();
+ }
+
+ @Override
+ public OIDCJWKS create() {
+ try {
+ OIDCJWKS jwks = entityFactory.newEntity(OIDCJWKS.class);
+ RSAKey jwk = new RSAKeyGenerator(2048)
+ .keyUse(KeyUse.SIGNATURE)
+ .keyID(SecureRandomUtils.generateRandomUUID().toString())
+ .generate();
+ jwks.setJson(new JWKSet(jwk).toString());
+ return jwks;
+ } catch (final Exception e) {
+ throw new RuntimeException("Unable to create OIDC JWKS", e);
+ }
+ }
+
+ @Override
+ public OIDCJWKS update(final OIDCJWKS oidcjwks, final OIDCJWKSTO jwksTO) {
+ oidcjwks.setJson(jwksTO.getJson());
+ return oidcjwks;
+ }
+}
diff --git
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataBinderImpl.java
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataDataBinderImpl.java
similarity index 97%
rename from
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataBinderImpl.java
rename to
core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataDataBinderImpl.java
index 1f4d6fa..2d17168 100644
---
a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataBinderImpl.java
+++
b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPMetadataDataBinderImpl.java
@@ -23,10 +23,10 @@ import
org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.apache.syncope.common.lib.to.SAML2IdPMetadataTO;
import org.apache.syncope.core.persistence.api.entity.auth.SAML2IdPMetadata;
-import org.apache.syncope.core.provisioning.api.data.SAML2IdPMetadataBinder;
+import
org.apache.syncope.core.provisioning.api.data.SAML2IdPMetadataDataBinder;
@Component
-public class SAML2IdPMetadataBinderImpl implements SAML2IdPMetadataBinder {
+public class SAML2IdPMetadataDataBinderImpl implements
SAML2IdPMetadataDataBinder {
@Autowired
private EntityFactory entityFactory;
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
index 3f0764d..cf081ff 100644
---
a/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/AbstractITCase.java
@@ -113,9 +113,11 @@ import
org.apache.syncope.common.rest.api.service.ConnectorService;
import org.apache.syncope.common.rest.api.service.DynRealmService;
import org.apache.syncope.common.rest.api.service.LoggerService;
import org.apache.syncope.common.rest.api.service.NotificationService;
+import org.apache.syncope.common.rest.api.service.OIDCJWKSConfService;
import org.apache.syncope.common.rest.api.service.SAML2SPKeystoreConfService;
import
org.apache.syncope.common.rest.api.service.wa.GoogleMfaAuthAccountService;
import org.apache.syncope.common.rest.api.service.wa.GoogleMfaAuthTokenService;
+import org.apache.syncope.common.rest.api.service.wa.OIDCJWKSService;
import org.apache.syncope.common.rest.api.service.wa.SAML2SPKeystoreService;
import org.apache.syncope.common.rest.api.service.SAML2SPMetadataConfService;
import org.apache.syncope.common.rest.api.service.wa.SAML2SPMetadataService;
@@ -343,6 +345,10 @@ public abstract class AbstractITCase {
protected static AuthProfileService authProfileService;
+ protected static OIDCJWKSService oidcJwksService;
+
+ protected static OIDCJWKSConfService oidcJwksConfService;
+
@BeforeAll
public static void securitySetup() {
try (InputStream propStream =
Encryptor.class.getResourceAsStream("/security.properties")) {
@@ -423,6 +429,8 @@ public abstract class AbstractITCase {
googleMfaAuthTokenService =
adminClient.getService(GoogleMfaAuthTokenService.class);
googleMfaAuthAccountService =
adminClient.getService(GoogleMfaAuthAccountService.class);
authProfileService = adminClient.getService(AuthProfileService.class);
+ oidcJwksService = adminClient.getService(OIDCJWKSService.class);
+ oidcJwksConfService =
adminClient.getService(OIDCJWKSConfService.class);
}
@Autowired
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSConfITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSConfITCase.java
new file mode 100644
index 0000000..8c1a70d
--- /dev/null
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSConfITCase.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.KeyUse;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+import org.springframework.http.HttpStatus;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OIDCJWKSConfITCase extends AbstractITCase {
+
+ private static OIDCJWKSTO getCurrentJwksTO() {
+ try {
+ return oidcJwksService.get();
+ } catch (final SyncopeClientException e) {
+ if (e.getType() == ClientExceptionType.NotFound) {
+ Response response = oidcJwksService.set();
+ assertEquals(HttpStatus.CREATED.value(), response.getStatus());
+ return response.readEntity(new GenericType<OIDCJWKSTO>() {
+ });
+ }
+ }
+ throw new RuntimeException("Unable to locate current OIDC JWKS");
+ }
+
+ @Test
+ public void verifyJwks() throws Exception {
+ oidcJwksConfService.delete();
+
+ RSAKey jwk = new RSAKeyGenerator(2048)
+ .keyUse(KeyUse.SIGNATURE)
+ .keyID(UUID.randomUUID().toString())
+ .generate();
+ String json = new JWKSet(jwk).toString();
+
+ assertDoesNotThrow(new Executable() {
+ @Override
+ public void execute() {
+ OIDCJWKSTO currentTO = getCurrentJwksTO();
+ currentTO.setJson(json);
+ oidcJwksConfService.update(currentTO);
+ }
+ });
+ OIDCJWKSTO currentTO = getCurrentJwksTO();
+ assertEquals(json, currentTO.getJson());
+ }
+
+}
diff --git
a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSITCase.java
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSITCase.java
new file mode 100644
index 0000000..931213f
--- /dev/null
+++
b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/OIDCJWKSITCase.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.core;
+
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+import org.springframework.http.HttpStatus;
+
+import javax.ws.rs.core.Response;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class OIDCJWKSITCase extends AbstractITCase {
+
+ @Test
+ public void verifyJwks() {
+ try {
+ oidcJwksConfService.delete();
+
+ oidcJwksService.get();
+ fail("Should not locate an OIDC JWKS");
+ } catch (final SyncopeClientException e) {
+ assertEquals(ClientExceptionType.NotFound, e.getType());
+ }
+ Response response = oidcJwksService.set();
+ assertEquals(HttpStatus.CREATED.value(), response.getStatus());
+ try {
+ oidcJwksService.set();
+ fail("Should not recreate an OIDC JWKS");
+ } catch (final SyncopeClientException e) {
+ assertEquals(ClientExceptionType.EntityExists, e.getType());
+ }
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index 0e7d626..ae58642 100644
--- a/pom.xml
+++ b/pom.xml
@@ -452,6 +452,8 @@ under the License.
<h2.version>1.4.200</h2.version>
+ <nimbus.jose.version>8.19</nimbus.jose.version>
+
<junit.version>5.6.2</junit.version>
<mockito.version>3.3.0</mockito.version>
@@ -1607,6 +1609,16 @@ under the License.
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
+ <artifactId>cas-server-support-oidc-core-api</artifactId>
+ <version>${cas.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apereo.cas</groupId>
+ <artifactId>cas-server-support-oidc-core</artifactId>
+ <version>${cas.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-oauth-services</artifactId>
<version>${cas.version}</version>
</dependency>
@@ -1993,6 +2005,11 @@ under the License.
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.nimbusds</groupId>
+ <artifactId>nimbus-jose-jwt</artifactId>
+ <version>${nimbus.jose.version}</version>
+ </dependency>
<!-- TEST -->
<dependency>
diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml
index 16e7126..419dfd3 100644
--- a/wa/starter/pom.xml
+++ b/wa/starter/pom.xml
@@ -180,6 +180,14 @@ under the License.
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
+ <artifactId>cas-server-support-oidc-core-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apereo.cas</groupId>
+ <artifactId>cas-server-support-oidc-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-oauth-services</artifactId>
</dependency>
<dependency>
diff --git
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
index be14196..0cce830 100644
---
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
+++
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/config/SyncopeWAConfiguration.java
@@ -20,6 +20,7 @@ package org.apache.syncope.wa.starter.config;
import org.apereo.cas.audit.AuditTrailExecutionPlanConfigurer;
import org.apereo.cas.configuration.CasConfigurationProperties;
+import org.apereo.cas.oidc.jwks.OidcJsonWebKeystoreGeneratorService;
import
org.apereo.cas.otp.repository.credentials.OneTimeTokenCredentialRepository;
import org.apereo.cas.otp.repository.token.OneTimeTokenRepository;
import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer;
@@ -48,6 +49,7 @@ import org.apache.syncope.wa.starter.mapping.AuthMapper;
import org.apache.syncope.wa.starter.mapping.ClientAppMapFor;
import org.apache.syncope.wa.starter.mapping.ClientAppMapper;
import org.apache.syncope.wa.starter.mapping.RegisteredServiceMapper;
+import org.apache.syncope.wa.starter.oidc.SyncopeWAOIDCJWKSGeneratorService;
import org.apache.syncope.wa.starter.pac4j.saml.SyncopeWASAML2ClientCustomizer;
import
org.apache.syncope.wa.starter.saml.idp.metadata.RestfulSamlIdPMetadataGenerator;
import
org.apache.syncope.wa.starter.saml.idp.metadata.RestfulSamlIdPMetadataLocator;
@@ -189,6 +191,12 @@ public class SyncopeWAConfiguration {
}
@Bean
+ @Autowired
+ public OidcJsonWebKeystoreGeneratorService
oidcJsonWebKeystoreGeneratorService(final WARestClient restClient) {
+ return new SyncopeWAOIDCJWKSGeneratorService(restClient);
+ }
+
+ @Bean
public KeymasterStart keymasterStart() {
return new KeymasterStart(NetworkService.Type.WA);
}
diff --git
a/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/SyncopeWAOIDCJWKSGeneratorService.java
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/SyncopeWAOIDCJWKSGeneratorService.java
new file mode 100644
index 0000000..346f504
--- /dev/null
+++
b/wa/starter/src/main/java/org/apache/syncope/wa/starter/oidc/SyncopeWAOIDCJWKSGeneratorService.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.syncope.wa.starter.oidc;
+
+import org.apereo.cas.oidc.jwks.OidcJsonWebKeystoreGeneratorService;
+
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.OIDCJWKSTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.rest.api.service.wa.OIDCJWKSService;
+import org.apache.syncope.wa.bootstrap.WARestClient;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.Resource;
+
+import javax.ws.rs.core.GenericType;
+import javax.ws.rs.core.Response;
+
+import java.nio.charset.StandardCharsets;
+
+public class SyncopeWAOIDCJWKSGeneratorService implements
OidcJsonWebKeystoreGeneratorService {
+ private final WARestClient waRestClient;
+
+ public SyncopeWAOIDCJWKSGeneratorService(final WARestClient restClient) {
+ this.waRestClient = restClient;
+ }
+
+ @Override
+ public Resource generate() {
+ if (!WARestClient.isReady()) {
+ throw new RuntimeException("Syncope core is not yet ready");
+ }
+
+ OIDCJWKSService service = waRestClient.getSyncopeClient().
+ getService(OIDCJWKSService.class);
+ try {
+ Response response = service.set();
+ OIDCJWKSTO jwksTO = response.readEntity(new
GenericType<OIDCJWKSTO>() {
+ });
+ return new
ByteArrayResource(jwksTO.getJson().getBytes(StandardCharsets.UTF_8), "OIDC
JWKS");
+ } catch (final SyncopeClientException e) {
+ if (e.getType() == ClientExceptionType.EntityExists) {
+ OIDCJWKSTO jwksTO = service.get();
+ return new
ByteArrayResource(jwksTO.getJson().getBytes(StandardCharsets.UTF_8), "OIDC
JWKS");
+ }
+ }
+ throw new RuntimeException("Unable to determine OIDC JWKS resource");
+ }
+}