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:46 UTC

[syncope] 06/07: [SYNCOPE-160] RegisteredClientApp service for WA

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 abd8829d89449025cd96df2589cd61ca4e36853a
Author: Dima Ayash <di...@apache.org>
AuthorDate: Thu Apr 9 13:46:23 2020 +0200

    [SYNCOPE-160] RegisteredClientApp service for WA
---
 .../common/lib/to/RegisteredClientAppTO.java       |  71 ++++++
 .../api/service/RegisteredClientAppService.java    | 133 ++++++++++++
 .../core/logic/RegisteredClientAppLogic.java       | 238 +++++++++++++++++++++
 .../service/RegisteredClientAppServiceImpl.java    |  96 +++++++++
 .../api/data/RegisteredClientAppBinder.java        |  28 +++
 .../java/data/RegisteredClientAppBinderImpl.java   |  84 ++++++++
 .../fit/core/RegisteredClientAppITCase.java        | 114 ++++++++++
 7 files changed, 764 insertions(+)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/RegisteredClientAppTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/RegisteredClientAppTO.java
new file mode 100644
index 0000000..6b56357
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/RegisteredClientAppTO.java
@@ -0,0 +1,71 @@
+/*
+ * 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 java.io.Serializable;
+import org.apache.syncope.common.lib.policy.AccessPolicyConf;
+import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf;
+import org.apache.syncope.common.lib.policy.AuthPolicyConf;
+import org.apache.syncope.common.lib.to.client.ClientAppTO;
+
+public class RegisteredClientAppTO implements Serializable {
+
+    private static final long serialVersionUID = 6633251825655119506L;
+
+    private ClientAppTO clientAppTO;
+
+    private AccessPolicyConf accessPolicyConf;
+
+    private AuthPolicyConf authPolicyConf;
+
+    private AttrReleasePolicyConf attrReleasePolicyConf;
+
+    public ClientAppTO getClientAppTO() {
+        return clientAppTO;
+    }
+
+    public void setClientAppTO(final ClientAppTO clientAppTO) {
+        this.clientAppTO = clientAppTO;
+    }
+
+    public AccessPolicyConf getAccessPolicyConf() {
+        return accessPolicyConf;
+    }
+
+    public void setAccessPolicyConf(final AccessPolicyConf accessPolicyConf) {
+        this.accessPolicyConf = accessPolicyConf;
+    }
+
+    public AuthPolicyConf getAuthPolicyConf() {
+        return authPolicyConf;
+    }
+
+    public void setAuthPolicyConf(final AuthPolicyConf authPolicyConf) {
+        this.authPolicyConf = authPolicyConf;
+    }
+
+    public AttrReleasePolicyConf getAttrReleasePolicyConf() {
+        return attrReleasePolicyConf;
+    }
+
+    public void setAttrReleasePolicyConf(final AttrReleasePolicyConf attrReleasePolicyConf) {
+        this.attrReleasePolicyConf = attrReleasePolicyConf;
+    }
+
+}
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RegisteredClientAppService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RegisteredClientAppService.java
new file mode 100644
index 0000000..cd55423
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RegisteredClientAppService.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import java.util.List;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+
+/**
+ * REST operations for resgistered client applications.
+ */
+@Tag(name = "RegisteredClientApps")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("registeredClientApps")
+public interface RegisteredClientAppService extends JAXRSService {
+
+    /**
+     * Returns a list of all client applications to be registered.
+     *
+     * @return list of all client applications.
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    List<RegisteredClientAppTO> list();
+
+    /**
+     * Returns a client application with matching key.
+     *
+     * @param clientAppId registered client application ID to be read
+     * @return registered client application with matching id
+     */
+    @GET
+    @Path("{clientAppId}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    RegisteredClientAppTO read(@NotNull @PathParam("clientAppId") Long clientAppId);
+
+    @GET
+    @Path("{clientAppId}/{type}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    RegisteredClientAppTO read(
+            @NotNull @PathParam("clientAppId") Long clientAppId,
+            @NotNull @PathParam("type") ClientAppType type);
+
+    /**
+     * Returns a client application with matching key.
+     *
+     * @param name registered client application name to be read
+     * @return registered client application with matching name
+     */
+    @GET
+    @Path("/name/{name}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    RegisteredClientAppTO read(@NotNull @PathParam("name") String name);
+
+    @GET
+    @Path("/name/{name}/{type}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    RegisteredClientAppTO read(
+            @NotNull @PathParam("name") String name,
+            @NotNull @PathParam("type") ClientAppType type);
+
+    /**
+     * Create a new client app.
+     *
+     * @param registeredClientAppTO
+     * @return Response object featuring Location header of created registered 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
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    Response create(@NotNull RegisteredClientAppTO registeredClientAppTO);
+
+    /**
+     * Delete client app matching the given key.
+     *
+     * @param name name of registered client application to be deleted
+     * @return
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @DELETE
+    @Path("{name}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    boolean delete(@NotNull @PathParam("name") String name);
+
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/RegisteredClientAppLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/RegisteredClientAppLogic.java
new file mode 100644
index 0000000..f9b162a
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/RegisteredClientAppLogic.java
@@ -0,0 +1,238 @@
+/*
+ * 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.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.syncope.common.lib.to.AccessPolicyTO;
+import org.apache.syncope.common.lib.to.AttrReleasePolicyTO;
+import org.apache.syncope.common.lib.to.AuthPolicyTO;
+import org.apache.syncope.common.lib.to.ImplementationTO;
+import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+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.AMImplementationType;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.syncope.common.lib.types.ImplementationEngine;
+import org.apache.syncope.common.lib.types.PolicyType;
+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.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
+import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder;
+import org.apache.syncope.core.provisioning.api.data.RegisteredClientAppBinder;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+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 RegisteredClientAppLogic {
+
+    @Autowired
+    private ImplementationLogic implementationLogic;
+
+    @Autowired
+    private PolicyLogic policyLogic;
+
+    @Autowired
+    private ClientAppDataBinder clientAppDataBinder;
+
+    @Autowired
+    private RegisteredClientAppBinder binder;
+
+    @Autowired
+    private SAML2SPDAO saml2spDAO;
+
+    @Autowired
+    private OIDCRPDAO oidcrpDAO;
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public List<RegisteredClientAppTO> list() {
+        List<RegisteredClientAppTO> registeredApplications = new ArrayList<>();
+        Arrays.asList(ClientAppType.values()).forEach(type -> {
+            switch (type) {
+                case OIDCRP:
+                    registeredApplications.addAll(oidcrpDAO.findAll().stream().map(binder::getRegisteredClientAppTO).
+                            collect(Collectors.toList()));
+                    break;
+
+                case SAML2SP:
+                default:
+                    registeredApplications.addAll(saml2spDAO.findAll().stream().map(binder::getRegisteredClientAppTO).
+                            collect(Collectors.toList()));
+            }
+        });
+
+        return registeredApplications;
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public RegisteredClientAppTO read(final Long clientAppId, final ClientAppType type) {
+        switch (type) {
+            case OIDCRP:
+                OIDCRP oidcrp = oidcrpDAO.findByClientAppId(clientAppId);
+                if (oidcrp != null) {
+                    return binder.getRegisteredClientAppTO(oidcrp);
+                }
+            case SAML2SP:
+                SAML2SP saml2sp = saml2spDAO.findByClientAppId(clientAppId);
+                if (saml2sp != null) {
+                    return binder.getRegisteredClientAppTO(saml2sp);
+                }
+            default:
+                return null;
+        }
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public RegisteredClientAppTO read(final Long clientAppId) {
+        for (ClientAppType type : ClientAppType.values()) {
+            RegisteredClientAppTO registeredClientAppTO = read(clientAppId, type);
+            if (registeredClientAppTO != null) {
+                return registeredClientAppTO;
+            }
+        }
+
+        return null;
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public RegisteredClientAppTO read(final String name, final ClientAppType type) {
+        switch (type) {
+            case OIDCRP:
+                OIDCRP oidcrp = oidcrpDAO.findByName(name);
+                if (oidcrp != null) {
+                    return binder.getRegisteredClientAppTO(oidcrp);
+                }
+            case SAML2SP:
+                SAML2SP saml2sp = saml2spDAO.findByName(name);
+                if (saml2sp != null) {
+                    return binder.getRegisteredClientAppTO(saml2sp);
+                }
+            default:
+                return null;
+        }
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public RegisteredClientAppTO read(final String name) {
+        for (ClientAppType type : ClientAppType.values()) {
+            RegisteredClientAppTO registeredClientAppTO = read(name, type);
+            if (registeredClientAppTO != null) {
+                return registeredClientAppTO;
+            }
+        }
+        return null;
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional
+    public RegisteredClientAppTO create(final RegisteredClientAppTO registeredClientAppTO) {
+
+        AuthPolicyTO authPolicyTO = new AuthPolicyTO();
+        if (registeredClientAppTO.getAuthPolicyConf() != null) {
+            String policyName = registeredClientAppTO.getClientAppTO().getName() + "AuthPolicy";
+            ImplementationTO implementationTO = new ImplementationTO();
+            implementationTO.setKey(policyName);
+            implementationTO.setEngine(ImplementationEngine.JAVA);
+            implementationTO.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+            implementationTO.setBody(POJOHelper.serialize(registeredClientAppTO.getAuthPolicyConf()));
+
+            ImplementationTO conf = implementationLogic.create(implementationTO);
+
+            authPolicyTO.setConfiguration(conf.getKey());
+            authPolicyTO = policyLogic.create(PolicyType.AUTH, authPolicyTO);
+        }
+
+        AccessPolicyTO accessPolicyTO = new AccessPolicyTO();
+        if (registeredClientAppTO.getAccessPolicyConf() != null) {
+
+            String policyName = registeredClientAppTO.getClientAppTO().getName() + "AccessPolicy";
+            ImplementationTO implementationTO = new ImplementationTO();
+            implementationTO.setKey(policyName);
+            implementationTO.setEngine(ImplementationEngine.JAVA);
+            implementationTO.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+            implementationTO.setBody(POJOHelper.serialize(registeredClientAppTO.getAuthPolicyConf()));
+
+            ImplementationTO conf = implementationLogic.create(implementationTO);
+
+            accessPolicyTO.setConfiguration(conf.getKey());
+            accessPolicyTO = policyLogic.create(PolicyType.ACCESS, accessPolicyTO);
+        }
+
+        AttrReleasePolicyTO attrReleasePolicyTO = new AttrReleasePolicyTO();
+        if (registeredClientAppTO.getAttrReleasePolicyConf() != null) {
+
+            String policyName = registeredClientAppTO.getClientAppTO().getName() + "AttrReleasePolicy";
+            ImplementationTO implementationTO = new ImplementationTO();
+            implementationTO.setKey(policyName);
+            implementationTO.setEngine(ImplementationEngine.JAVA);
+            implementationTO.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+            implementationTO.setBody(POJOHelper.serialize(registeredClientAppTO.getAttrReleasePolicyConf()));
+
+            ImplementationTO conf = implementationLogic.create(implementationTO);
+
+            attrReleasePolicyTO.setConfiguration(conf.getKey());
+            attrReleasePolicyTO = policyLogic.create(PolicyType.ATTR_RELEASE, attrReleasePolicyTO);
+        }
+
+        if (registeredClientAppTO.getClientAppTO() instanceof OIDCRPTO) {
+            OIDCRPTO oidcrpto = OIDCRPTO.class.cast(registeredClientAppTO.getClientAppTO());
+            oidcrpto.setAccessPolicy(accessPolicyTO.getKey());
+            oidcrpto.setAttrReleasePolicy(attrReleasePolicyTO.getKey());
+            oidcrpto.setAuthPolicy(authPolicyTO.getKey());
+            return binder.getRegisteredClientAppTO(oidcrpDAO.save(clientAppDataBinder.create(oidcrpto)));
+
+        } else if (registeredClientAppTO.getClientAppTO() instanceof SAML2SPTO) {
+            SAML2SPTO saml2spto = SAML2SPTO.class.cast(registeredClientAppTO.getClientAppTO());
+            saml2spto.setAccessPolicy(accessPolicyTO.getKey());
+            saml2spto.setAttrReleasePolicy(attrReleasePolicyTO.getKey());
+            saml2spto.setAuthPolicy(authPolicyTO.getKey());
+            return binder.getRegisteredClientAppTO(saml2spDAO.save(clientAppDataBinder.create(saml2spto)));
+        }
+
+        return null;
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional
+    public boolean delete(final String name) {
+        ClientAppTO clientAppTO = read(name).getClientAppTO();
+        if (clientAppTO != null) {
+            if (clientAppTO instanceof OIDCRPTO) {
+                oidcrpDAO.delete(clientAppTO.getKey());
+            } else if (clientAppTO instanceof SAML2SPTO) {
+                saml2spDAO.delete(clientAppTO.getKey());
+            }
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RegisteredClientAppServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RegisteredClientAppServiceImpl.java
new file mode 100644
index 0000000..9a858fd
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RegisteredClientAppServiceImpl.java
@@ -0,0 +1,96 @@
+/*
+ * 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 org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.RegisteredClientAppService;
+import org.apache.syncope.core.logic.RegisteredClientAppLogic;
+
+@Service
+public class RegisteredClientAppServiceImpl extends AbstractServiceImpl implements RegisteredClientAppService {
+
+    @Autowired
+    private RegisteredClientAppLogic logic;
+
+    @Override
+    public List<RegisteredClientAppTO> list() {
+        return logic.list();
+    }
+
+    @Override
+    public RegisteredClientAppTO read(final Long clientAppId) {
+        RegisteredClientAppTO registeredClientAppTO = logic.read(clientAppId);
+        if (registeredClientAppTO == null) {
+            throw new NotFoundException("Client app with clientApp ID " + clientAppId + " not found");
+        }
+        return registeredClientAppTO;
+    }
+
+    @Override
+    public RegisteredClientAppTO read(final Long clientAppId, final ClientAppType type) {
+        RegisteredClientAppTO registeredClientAppTO = logic.read(clientAppId, type);
+        if (registeredClientAppTO == null) {
+            throw new NotFoundException("Client app with clientApp ID " + clientAppId
+                    + " with type " + type + " not found");
+        }
+        return registeredClientAppTO;
+    }
+
+    @Override
+    public RegisteredClientAppTO read(final String name) {
+        RegisteredClientAppTO registeredClientAppTO = logic.read(name);
+        if (registeredClientAppTO == null) {
+            throw new NotFoundException("Client app with name " + name + " not found");
+        }
+        return registeredClientAppTO;
+    }
+
+    @Override
+    public RegisteredClientAppTO read(final String name, final ClientAppType type) {
+        RegisteredClientAppTO registeredClientAppTO = logic.read(name, type);
+        if (registeredClientAppTO == null) {
+            throw new NotFoundException("Client app with name " + name + " with type " + type + " not found");
+        }
+        return registeredClientAppTO;
+    }
+
+    @Override
+    public Response create(final RegisteredClientAppTO registeredClientAppTO) {
+        RegisteredClientAppTO appTO = logic.create(registeredClientAppTO);
+        URI location = uriInfo.getAbsolutePathBuilder().path(appTO.getClientAppTO().getKey()).build();
+        return Response.created(location).
+                header(RESTHeaders.RESOURCE_KEY, appTO.getClientAppTO().getKey()).
+                build();
+    }
+
+    @Override
+    public boolean delete(final String name) {
+        return logic.delete(name);
+    }
+
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.java
new file mode 100644
index 0000000..d422534
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.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.provisioning.api.data;
+
+import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+
+public interface RegisteredClientAppBinder {
+
+    RegisteredClientAppTO getRegisteredClientAppTO(ClientApp clientApp);
+
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RegisteredClientAppBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RegisteredClientAppBinderImpl.java
new file mode 100644
index 0000000..c8aae5b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RegisteredClientAppBinderImpl.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.java.data;
+
+import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder;
+import org.apache.syncope.core.provisioning.api.data.RegisteredClientAppBinder;
+import org.apache.syncope.core.spring.ImplementationManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+public class RegisteredClientAppBinderImpl implements RegisteredClientAppBinder {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RegisteredClientAppBinder.class);
+
+    @Autowired
+    private ClientAppDataBinder clientAppDataBinder;
+
+    @Override
+    public RegisteredClientAppTO getRegisteredClientAppTO(final ClientApp clientApp) {
+        RegisteredClientAppTO registeredClientAppTO = new RegisteredClientAppTO();
+        registeredClientAppTO.setClientAppTO(clientAppDataBinder.getClientAppTO(clientApp));
+
+        try {
+            if (clientApp.getAuthPolicy() != null) {
+                registeredClientAppTO.setAuthPolicyConf(build((clientApp.getAuthPolicy()).getConfiguration()));
+            } else if (clientApp.getRealm().getAuthPolicy() != null) {
+                registeredClientAppTO.
+                        setAuthPolicyConf(build((clientApp.getRealm().getAuthPolicy()).getConfiguration()));
+            } else {
+                registeredClientAppTO.setAuthPolicyConf(null);
+            }
+
+            if (clientApp.getAccessPolicy() != null) {
+                registeredClientAppTO.setAccessPolicyConf(build((clientApp.getAccessPolicy()).getConfiguration()));
+            } else if (clientApp.getRealm().getAccessPolicy() != null) {
+                registeredClientAppTO.setAccessPolicyConf(build((clientApp.getRealm().getAccessPolicy()).
+                        getConfiguration()));
+            } else {
+                registeredClientAppTO.setAccessPolicyConf(null);
+            }
+
+            if (clientApp.getAttrReleasePolicy() != null) {
+                registeredClientAppTO.setAttrReleasePolicyConf(build((clientApp.getAttrReleasePolicy()).
+                        getConfiguration()));
+            } else if (clientApp.getRealm().getAttrReleasePolicy() != null) {
+                registeredClientAppTO.setAttrReleasePolicyConf(build((clientApp.getRealm().getAttrReleasePolicy()).
+                        getConfiguration()));
+            } else {
+                registeredClientAppTO.setAttrReleasePolicyConf(null);
+            }
+        } catch (Exception e) {
+            LOG.error("While building the configuration from an application's policy ", e);
+        }
+
+        return registeredClientAppTO;
+    }
+
+    private <T> T build(final Implementation impl) throws InstantiationException, IllegalAccessException,
+            ClassNotFoundException {
+        return ImplementationManager.build(impl);
+    }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RegisteredClientAppITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RegisteredClientAppITCase.java
new file mode 100644
index 0000000..42fe26f
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RegisteredClientAppITCase.java
@@ -0,0 +1,114 @@
+/*
+ * 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.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 java.util.List;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+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.rest.api.service.RegisteredClientAppService;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+public class RegisteredClientAppITCase extends AbstractITCase {
+
+    protected static RegisteredClientAppService registeredClientAppService;
+
+    @BeforeAll
+    public static void setup() {
+        SyncopeClient anonymous = clientFactory.create(
+                new AnonymousAuthenticationHandler(ANONYMOUS_UNAME, ANONYMOUS_KEY));
+        registeredClientAppService = anonymous.getService(RegisteredClientAppService.class);
+    }
+
+    @Test
+    public void list() {
+        createClientApp(ClientAppType.OIDCRP, buildOIDCRP());
+
+        List<RegisteredClientAppTO> list = registeredClientAppService.list();
+        assertFalse(list.isEmpty());
+    }
+
+    @Test
+    public void read() {
+        OIDCRPTO oidcrpto = createClientApp(ClientAppType.OIDCRP, buildOIDCRP());
+        RegisteredClientAppTO registeredOidcClientApp = registeredClientAppService.read(oidcrpto.getClientAppId());
+        assertNotNull(registeredOidcClientApp);
+
+        registeredOidcClientApp = registeredClientAppService.read(oidcrpto.getClientAppId(),
+                ClientAppType.OIDCRP);
+        assertNotNull(registeredOidcClientApp);
+
+        registeredOidcClientApp = registeredClientAppService.read(oidcrpto.getName());
+        assertNotNull(registeredOidcClientApp);
+
+        registeredOidcClientApp = registeredClientAppService.read(oidcrpto.getName(), ClientAppType.OIDCRP);
+        assertNotNull(registeredOidcClientApp);
+        
+        
+        SAML2SPTO samlspto = createClientApp(ClientAppType.SAML2SP, buildSAML2SP());
+        RegisteredClientAppTO registeredSamlClientApp=  registeredClientAppService.read(samlspto.getClientAppId());
+        assertNotNull(registeredSamlClientApp);
+
+        registeredSamlClientApp = registeredClientAppService.read(samlspto.getClientAppId(),
+                ClientAppType.SAML2SP);
+        assertNotNull(registeredSamlClientApp);
+
+        registeredSamlClientApp = registeredClientAppService.read(samlspto.getName());
+        assertNotNull(registeredSamlClientApp);
+
+        registeredSamlClientApp = registeredClientAppService.read(samlspto.getName(), ClientAppType.SAML2SP);
+        assertNotNull(registeredSamlClientApp);
+    }
+
+
+    @Test
+    public void delete() {
+        SAML2SPTO samlspto = createClientApp(ClientAppType.SAML2SP, buildSAML2SP());
+
+        assertTrue(registeredClientAppService.delete(samlspto.getName()));
+        try {
+            clientAppService.read(ClientAppType.SAML2SP, samlspto.getKey());
+            fail("This should not happen");
+        } catch (SyncopeClientException e) {
+            assertNotNull(e);
+        }
+    }
+
+    @Test
+    public void create() {
+        OIDCRPTO oidcrpto = buildOIDCRP();
+        RegisteredClientAppTO appTO = new RegisteredClientAppTO();
+        appTO.setClientAppTO(oidcrpto);
+
+        registeredClientAppService.create(appTO);
+        assertNotNull(registeredClientAppService.read(oidcrpto.getClientAppId()));
+    }
+
+}