You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by mm...@apache.org on 2020/07/14 09:05:45 UTC

[syncope] branch master updated: SYNCOPE-1577: Support CAS protocol for client applications (#203)

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 4774ee8  SYNCOPE-1577: Support CAS protocol for client applications (#203)
4774ee8 is described below

commit 4774ee8c4b9ec876d802b4481fb294d60dd23b48
Author: Misagh Moayyed <mm...@gmail.com>
AuthorDate: Tue Jul 14 13:35:37 2020 +0430

    SYNCOPE-1577: Support CAS protocol for client applications (#203)
---
 .../syncope/common/lib/to/client/CASSPTO.java      | 75 +++++++++++++++++
 .../syncope/common/lib/types/ClientAppType.java    |  1 +
 .../apache/syncope/core/logic/ClientAppLogic.java  | 45 ++++++++--
 .../core/persistence/api/dao/auth/CASSPDAO.java    | 24 +++++-
 .../core/persistence/api/entity/auth/CASSP.java    |  9 +-
 .../core/persistence/jpa/dao/auth/JPACASSPDAO.java | 95 ++++++++++++++++++++++
 .../persistence/jpa/entity/JPAEntityFactory.java   |  4 +
 .../core/persistence/jpa/entity/auth/JPACASSP.java | 28 ++++++-
 .../jpa/entity/auth/JPAClientAppUtils.java         |  4 +-
 .../jpa/entity/auth/JPAClientAppUtilsFactory.java  |  6 ++
 .../core/persistence/jpa/inner/CASSPTest.java      | 75 +++++++++++++++++
 .../java/data/ClientAppDataBinderImpl.java         | 88 +++++++++++++++++++-
 .../apache/syncope/fit/core/ClientAppITCase.java   | 61 ++++++++++++++
 .../syncope/wa/starter/mapping/CASSPTOMapper.java  | 57 +++++++++++++
 14 files changed, 548 insertions(+), 24 deletions(-)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/CASSPTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/CASSPTO.java
new file mode 100644
index 0000000..67c0ec4
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/CASSPTO.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to.client;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+
+@Schema(allOf = {ClientAppTO.class})
+public class CASSPTO extends ClientAppTO {
+
+    private static final long serialVersionUID = -5370888503924521351L;
+
+    private String serviceId;
+
+    public String getServiceId() {
+        return serviceId;
+    }
+
+    public void setServiceId(final String serviceId) {
+        this.serviceId = serviceId;
+    }
+
+    @JacksonXmlProperty(localName = "_class", isAttribute = true)
+    @JsonProperty("_class")
+    @Schema(name = "_class", required = true, example = "org.apache.syncope.common.lib.to.client.CASSPTO")
+    @Override
+    public String getDiscriminator() {
+        return getClass().getName();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (obj == this) {
+            return true;
+        }
+        if (obj.getClass() != getClass()) {
+            return false;
+        }
+        CASSPTO rhs = (CASSPTO) obj;
+        return new EqualsBuilder()
+            .appendSuper(super.equals(obj))
+            .append(this.serviceId, rhs.serviceId)
+            .isEquals();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder()
+            .appendSuper(super.hashCode())
+            .append(this.serviceId)
+            .toHashCode();
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
index 3bf730e..b8cd021 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
@@ -20,6 +20,7 @@ package org.apache.syncope.common.lib.types;
 
 public enum ClientAppType {
     SAML2SP,
+    CASSP,
     OIDCRP;
 
 }
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java
index 0c9b0cb..ec2261b 100644
--- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java
@@ -39,8 +39,10 @@ import org.apache.syncope.common.lib.types.AMEntitlement;
 import org.apache.syncope.common.lib.types.ClientAppType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.CASSPDAO;
 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.CASSP;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtils;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtilsFactory;
@@ -74,6 +76,9 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
     @Autowired
     private OIDCRPDAO oidcrpDAO;
 
+    @Autowired
+    private CASSPDAO casspDAO;
+
     @Resource(name = "anonymousUser")
     private String anonymousUser;
 
@@ -88,7 +93,9 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
             case OIDCRP:
                 stream = oidcrpDAO.findAll().stream().map(binder::getClientAppTO);
                 break;
-
+            case CASSP:
+                stream = casspDAO.findAll().stream().map(binder::getClientAppTO);
+                break;
             case SAML2SP:
             default:
                 stream = saml2spDAO.findAll().stream().map(binder::getClientAppTO);
@@ -97,7 +104,7 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
         return stream.collect(Collectors.toList());
     }
 
-    private void checkType(final ClientAppType type, final ClientAppUtils clientAppUtils) {
+    private static void checkType(final ClientAppType type, final ClientAppUtils clientAppUtils) {
         if (clientAppUtils.getType() != type) {
             SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidRequest);
             sce.getElements().add("Found " + type + ", expected " + clientAppUtils.getType());
@@ -118,7 +125,15 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
                 checkType(type, clientAppUtilsFactory.getInstance(oidcrp));
 
                 return binder.getClientAppTO(oidcrp);
+            case CASSP:
+                CASSP cassp = casspDAO.find(key);
+                if (cassp == null) {
+                    throw new NotFoundException("Client app " + key + " not found");
+                }
 
+                checkType(type, clientAppUtilsFactory.getInstance(cassp));
+
+                return binder.getClientAppTO(cassp);
             case SAML2SP:
             default:
                 SAML2SP saml2sp = saml2spDAO.find(key);
@@ -139,7 +154,8 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
         switch (type) {
             case OIDCRP:
                 return binder.getClientAppTO(oidcrpDAO.save(binder.create(clientAppTO)));
-
+            case CASSP:
+                return binder.getClientAppTO(casspDAO.save(binder.create(clientAppTO)));
             case SAML2SP:
             default:
                 return binder.getClientAppTO(saml2spDAO.save(binder.create(clientAppTO)));
@@ -159,7 +175,14 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
                 binder.update(oidcrp, clientAppTO);
                 oidcrpDAO.save(oidcrp);
                 break;
-
+            case CASSP:
+                CASSP cassp = casspDAO.find(clientAppTO.getKey());
+                if (cassp == null) {
+                    throw new NotFoundException("Client app " + clientAppTO.getKey() + " not found");
+                }
+                binder.update(cassp, clientAppTO);
+                casspDAO.save(cassp);
+                break;
             case SAML2SP:
             default:
                 SAML2SP saml2sp = saml2spDAO.find(clientAppTO.getKey());
@@ -181,7 +204,13 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
                 }
                 oidcrpDAO.delete(oidcrp);
                 break;
-
+            case CASSP:
+                CASSP cassp = casspDAO.find(key);
+                if (cassp == null) {
+                    throw new NotFoundException("Client app " + key + " not found");
+                }
+                casspDAO.delete(cassp);
+                break;
             case SAML2SP:
             default:
                 SAML2SP saml2sp = saml2spDAO.find(key);
@@ -216,9 +245,9 @@ public class ClientAppLogic extends AbstractTransactionalLogic<ClientAppTO> {
                 }
 
                 return binder.getClientAppTO(clientApp);
-            } catch (Throwable ignore) {
-                LOG.debug("Unresolved reference", ignore);
-                throw new UnresolvedReferenceException(ignore);
+            } catch (Throwable ex) {
+                LOG.debug("Unresolved reference", ex);
+                throw new UnresolvedReferenceException(ex);
             }
         }
 
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/CASSPDAO.java
similarity index 62%
copy from common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/CASSPDAO.java
index 3bf730e..5c179bd 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/CASSPDAO.java
@@ -16,10 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.core.persistence.api.dao.auth;
 
-public enum ClientAppType {
-    SAML2SP,
-    OIDCRP;
+import org.apache.syncope.core.persistence.api.dao.DAO;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
 
+import java.util.List;
+
+public interface CASSPDAO extends DAO<CASSP> {
+
+    CASSP find(String key);
+
+    CASSP findByClientAppId(Long clientAppId);
+
+    CASSP findByName(String name);
+
+    List<CASSP> findAll();
+
+    CASSP save(CASSP clientApp);
+
+    void delete(String key);
+
+    void delete(CASSP clientApp);
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/CASSP.java
similarity index 82%
copy from common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/CASSP.java
index 3bf730e..88cf6a5 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/CASSP.java
@@ -16,10 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.core.persistence.api.entity.auth;
 
-public enum ClientAppType {
-    SAML2SP,
-    OIDCRP;
+public interface CASSP extends ClientApp {
 
+    void setServiceId(String serviceId);
+
+    String getServiceId();
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPACASSPDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPACASSPDAO.java
new file mode 100644
index 0000000..c59a459
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPACASSPDAO.java
@@ -0,0 +1,95 @@
+/*
+ * 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.CASSPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPACASSP;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+
+import java.util.List;
+
+@Repository
+public class JPACASSPDAO extends AbstractDAO<CASSP> implements CASSPDAO {
+
+    @Override
+    public CASSP find(final String key) {
+        return entityManager().find(JPACASSP.class, key);
+    }
+
+    private CASSP find(final String column, final Object value) {
+        TypedQuery<CASSP> query = entityManager().createQuery(
+                "SELECT e FROM " + JPACASSP.class.getSimpleName() + " e WHERE e." + column + "=:value",
+            CASSP.class);
+        query.setParameter("value", value);
+
+        CASSP result = null;
+        try {
+            result = query.getSingleResult();
+        } catch (final NoResultException e) {
+            LOG.debug("No OIDCRP found with " + column + " {}", value, e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public CASSP findByClientAppId(final Long clientAppId) {
+        return find("clientAppId", clientAppId);
+    }
+
+    @Override
+    public CASSP findByName(final String name) {
+        return find("name", name);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<CASSP> findAll() {
+        TypedQuery<CASSP> query = entityManager().createQuery(
+                "SELECT e FROM " + JPACASSP.class.getSimpleName() + " e", CASSP.class);
+
+        return query.getResultList();
+    }
+
+    @Override
+    public CASSP save(final CASSP clientApp) {
+        return entityManager().merge(clientApp);
+    }
+
+    @Override
+    public void delete(final String key) {
+        CASSP rpTO = find(key);
+        if (rpTO == null) {
+            return;
+        }
+
+        delete(rpTO);
+    }
+
+    @Override
+    public void delete(final CASSP clientApp) {
+        entityManager().remove(clientApp);
+    }
+}
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 5db4207..5ec9130 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
@@ -58,6 +58,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.CASSP;
 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;
@@ -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.JPACASSP;
 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;
@@ -329,6 +331,8 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPAAttrReleasePolicy();
         } else if (reference.equals(OIDCRP.class)) {
             result = (E) new JPAOIDCRP();
+        } else if (reference.equals(CASSP.class)) {
+            result = (E) new JPACASSP();
         } else if (reference.equals(SAML2SP.class)) {
             result = (E) new JPASAML2SP();
         } else if (reference.equals(SAML2IdPMetadata.class)) {
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPACASSP.java
similarity index 53%
copy from common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
copy to core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPACASSP.java
index 3bf730e..763930d 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPACASSP.java
@@ -16,10 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.core.persistence.jpa.entity.auth;
 
-public enum ClientAppType {
-    SAML2SP,
-    OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
 
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Table;
+
+@Entity
+@Table(name = JPACASSP.TABLE)
+public class JPACASSP extends AbstractClientApp implements CASSP {
+
+    public static final String TABLE = "CASSP";
+
+    private static final long serialVersionUID = 6422422526695279794L;
+
+    @Column(unique = true, nullable = false)
+    private String serviceId;
+
+    public String getServiceId() {
+        return serviceId;
+    }
+
+    public void setServiceId(final String serviceId) {
+        this.serviceId = serviceId;
+    }
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java
index ced3c6a..3cb32b3 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.core.persistence.jpa.entity.auth;
 
 import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtils;
 import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
@@ -42,7 +43,8 @@ public class JPAClientAppUtils implements ClientAppUtils {
         switch (type) {
             case OIDCRP:
                 return OIDCRP.class;
-
+            case CASSP:
+                return CASSP.class;
             case SAML2SP:
             default:
                 return SAML2SP.class;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java
index 610cc63..512f999 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java
@@ -18,10 +18,12 @@
  */
 package org.apache.syncope.core.persistence.jpa.entity.auth;
 
+import org.apache.syncope.common.lib.to.client.CASSPTO;
 import org.apache.syncope.common.lib.to.client.ClientAppTO;
 import org.apache.syncope.common.lib.to.client.OIDCRPTO;
 import org.apache.syncope.common.lib.to.client.SAML2SPTO;
 import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtils;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientAppUtilsFactory;
@@ -42,6 +44,8 @@ public class JPAClientAppUtilsFactory implements ClientAppUtilsFactory {
         ClientAppType type;
         if (clientApp instanceof SAML2SP) {
             type = ClientAppType.SAML2SP;
+        } else if (clientApp instanceof CASSP) {
+            type = ClientAppType.CASSP;
         } else if (clientApp instanceof OIDCRP) {
             type = ClientAppType.OIDCRP;
         } else {
@@ -56,6 +60,8 @@ public class JPAClientAppUtilsFactory implements ClientAppUtilsFactory {
         ClientAppType type;
         if (clientAppClass == SAML2SPTO.class) {
             type = ClientAppType.SAML2SP;
+        } else if (clientAppClass == CASSPTO.class) {
+            type = ClientAppType.CASSP;
         } else if (clientAppClass == OIDCRPTO.class) {
             type = ClientAppType.OIDCRP;
         } else {
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/CASSPTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/CASSPTest.java
new file mode 100644
index 0000000..45aa0f6
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/CASSPTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.apache.syncope.core.persistence.api.dao.auth.CASSPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+@Transactional("Master")
+public class CASSPTest extends AbstractClientAppTest {
+
+    @Autowired
+    private CASSPDAO casspDAO;
+
+    @Test
+    public void find() {
+        int beforeCount = casspDAO.findAll().size();
+
+        CASSP rp = entityFactory.newEntity(CASSP.class);
+        rp.setName("CAS");
+        rp.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+        rp.setDescription("This is a sample CAS RP");
+        rp.setServiceId("https://syncope.apache.org/.*");
+
+        AccessPolicy accessPolicy = buildAndSaveAccessPolicy();
+        rp.setAccessPolicy(accessPolicy);
+
+        AuthPolicy authPolicy = buildAndSaveAuthPolicy();
+        rp.setAuthPolicy(authPolicy);
+
+        casspDAO.save(rp);
+
+        assertNotNull(rp);
+        assertNotNull(rp.getKey());
+
+        int afterCount = casspDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+
+
+        rp = casspDAO.findByName("CAS");
+        assertNotNull(rp);
+        
+        rp = casspDAO.findByClientAppId(rp.getClientAppId());
+        assertNotNull(rp);
+
+        casspDAO.delete(rp);
+        assertNull(casspDAO.findByName("CAS"));
+    }
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
index 67db455..cc811fa 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
@@ -19,13 +19,16 @@
 package org.apache.syncope.core.provisioning.java.data;
 
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.client.CASSPTO;
 import org.apache.syncope.common.lib.to.client.ClientAppTO;
 import org.apache.syncope.common.lib.to.client.OIDCRPTO;
 import org.apache.syncope.common.lib.to.client.SAML2SPTO;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.apache.syncope.core.persistence.api.entity.auth.CASSP;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+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.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
@@ -34,7 +37,6 @@ import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
-import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
 
 @Component
 public class ClientAppDataBinderImpl implements ClientAppDataBinder {
@@ -52,6 +54,8 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
             return (T) doCreate((SAML2SPTO) clientAppTO);
         } else if (clientAppTO instanceof OIDCRPTO) {
             return (T) doCreate((OIDCRPTO) clientAppTO);
+        } else if (clientAppTO instanceof CASSPTO) {
+            return (T) doCreate((CASSPTO) clientAppTO);
         } else {
             throw new IllegalArgumentException("Unsupported client app: " + clientAppTO.getClass().getName());
         }
@@ -63,6 +67,8 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
             doUpdate((SAML2SP) clientApp, (SAML2SPTO) clientAppTO);
         } else if (clientAppTO instanceof OIDCRPTO) {
             doUpdate((OIDCRP) clientApp, (OIDCRPTO) clientAppTO);
+        } else if (clientAppTO instanceof CASSPTO) {
+            doUpdate((CASSP) clientApp, (CASSPTO) clientAppTO);
         } else {
             throw new IllegalArgumentException("Unsupported client app: " + clientAppTO.getClass().getName());
         }
@@ -75,6 +81,8 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
             return (T) getClientAppTO((SAML2SP) clientApp);
         } else if (clientApp instanceof OIDCRP) {
             return (T) getClientAppTO((OIDCRP) clientApp);
+        } else if (clientApp instanceof CASSP) {
+            return (T) getClientAppTO((CASSP) clientApp);
         } else {
             throw new IllegalArgumentException("Unsupported client app: " + clientApp.getClass().getName());
         }
@@ -86,6 +94,12 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
         return saml2sp;
     }
 
+    private CASSP doCreate(final CASSPTO clientAppTO) {
+        CASSP saml2sp = entityFactory.newEntity(CASSP.class);
+        update(saml2sp, clientAppTO);
+        return saml2sp;
+    }
+
     private void doUpdate(final SAML2SP clientApp, final SAML2SPTO clientAppTO) {
         clientApp.setDescription(clientAppTO.getDescription());
         clientApp.setName(clientAppTO.getName());
@@ -147,7 +161,7 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
         }
     }
 
-    private SAML2SPTO getClientAppTO(final SAML2SP clientApp) {
+    private static SAML2SPTO getClientAppTO(final SAML2SP clientApp) {
         SAML2SPTO clientAppTO = new SAML2SPTO();
 
         clientAppTO.setName(clientApp.getName());
@@ -240,7 +254,7 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
         }
     }
 
-    private OIDCRPTO getClientAppTO(final OIDCRP clientApp) {
+    private static OIDCRPTO getClientAppTO(final OIDCRP clientApp) {
         OIDCRPTO clientAppTO = new OIDCRPTO();
 
         clientAppTO.setName(clientApp.getName());
@@ -265,4 +279,72 @@ public class ClientAppDataBinderImpl implements ClientAppDataBinder {
 
         return clientAppTO;
     }
+
+    private void doUpdate(final CASSP clientApp, final CASSPTO clientAppTO) {
+        clientApp.setName(clientAppTO.getName());
+        clientApp.setClientAppId(clientAppTO.getClientAppId());
+        clientApp.setDescription(clientAppTO.getDescription());
+        clientApp.setServiceId(clientAppTO.getServiceId());
+
+        if (clientAppTO.getAuthPolicy() == null) {
+            clientApp.setAuthPolicy(null);
+        } else {
+            Policy policy = policyDAO.find(clientAppTO.getAuthPolicy());
+            if (policy instanceof AuthPolicy) {
+                clientApp.setAuthPolicy((AuthPolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
+                    + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+
+        if (clientAppTO.getAccessPolicy() == null) {
+            clientApp.setAccessPolicy(null);
+        } else {
+            Policy policy = policyDAO.find(clientAppTO.getAccessPolicy());
+            if (policy instanceof AccessPolicy) {
+                clientApp.setAccessPolicy((AccessPolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
+                    + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+
+        if (clientAppTO.getAttrReleasePolicy() == null) {
+            clientApp.setAttrReleasePolicy(null);
+        } else {
+            Policy policy = policyDAO.find(clientAppTO.getAttrReleasePolicy());
+            if (policy instanceof AttrReleasePolicy) {
+                clientApp.setAttrReleasePolicy((AttrReleasePolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
+                    + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+    }
+
+    private static CASSPTO getClientAppTO(final CASSP clientApp) {
+        CASSPTO clientAppTO = new CASSPTO();
+
+        clientAppTO.setName(clientApp.getName());
+        clientAppTO.setKey(clientApp.getKey());
+        clientAppTO.setDescription(clientApp.getDescription());
+        clientAppTO.setClientAppId(clientApp.getClientAppId());
+        clientAppTO.setServiceId(clientApp.getServiceId());
+
+        clientAppTO.setAuthPolicy(clientApp.getAuthPolicy() == null
+            ? null : clientApp.getAuthPolicy().getKey());
+        clientAppTO.setAccessPolicy(clientApp.getAccessPolicy() == null
+            ? null : clientApp.getAccessPolicy().getKey());
+        clientAppTO.setAttrReleasePolicy(clientApp.getAttrReleasePolicy() == null
+            ? null : clientApp.getAttrReleasePolicy().getKey());
+
+        return clientAppTO;
+    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
index 10a0194..ba6cf59 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
@@ -27,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.fail;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.policy.AccessPolicyTO;
+import org.apache.syncope.common.lib.policy.AuthPolicyTO;
+import org.apache.syncope.common.lib.to.client.CASSPTO;
 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;
@@ -34,6 +36,8 @@ import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.fit.AbstractITCase;
 import org.junit.jupiter.api.Test;
 
+import java.util.UUID;
+
 public class ClientAppITCase extends AbstractITCase {
 
     @Test
@@ -100,6 +104,11 @@ public class ClientAppITCase extends AbstractITCase {
     }
 
     @Test
+    public void createCASSP() {
+        createClientApp(ClientAppType.CASSP, buildCASSP());
+    }
+
+    @Test
     public void readOIDCRP() {
         OIDCRPTO oidcrpTO = buildOIDCRP();
         oidcrpTO = createClientApp(ClientAppType.OIDCRP, oidcrpTO);
@@ -116,6 +125,17 @@ public class ClientAppITCase extends AbstractITCase {
     }
 
     @Test
+    public void readCASSP() {
+        CASSPTO casspTO = buildCASSP();
+        casspTO = createClientApp(ClientAppType.CASSP, casspTO);
+        CASSPTO found = clientAppService.read(ClientAppType.CASSP, casspTO.getKey());
+        assertNotNull(found);
+        assertNotNull(found.getServiceId());
+        assertNotNull(found.getAccessPolicy());
+        assertNotNull(found.getAuthPolicy());
+    }
+
+    @Test
     public void updateOIDCRP() {
         OIDCRPTO oidcrpTO = buildOIDCRP();
         oidcrpTO = createClientApp(ClientAppType.OIDCRP, oidcrpTO);
@@ -151,4 +171,45 @@ public class ClientAppITCase extends AbstractITCase {
             assertNotNull(e);
         }
     }
+
+    @Test
+    public void deleteCASSP() {
+        CASSPTO casspTO = buildCASSP();
+        casspTO = createClientApp(ClientAppType.CASSP, casspTO);
+
+        clientAppService.delete(ClientAppType.CASSP, casspTO.getKey());
+
+        try {
+            clientAppService.read(ClientAppType.CASSP, casspTO.getKey());
+            fail("This should not happen");
+        } catch (SyncopeClientException e) {
+            assertNotNull(e);
+        }
+    }
+
+    private CASSPTO buildCASSP() {
+        AuthPolicyTO authPolicyTO = new AuthPolicyTO();
+        authPolicyTO.setKey("AuthPolicyTest_" + getUUIDString());
+        authPolicyTO.setDescription("Authentication Policy");
+        authPolicyTO = createPolicy(PolicyType.AUTH, authPolicyTO);
+        assertNotNull(authPolicyTO);
+
+        AccessPolicyTO accessPolicyTO = new AccessPolicyTO();
+        accessPolicyTO.setKey("AccessPolicyTest_" + getUUIDString());
+        accessPolicyTO.setDescription("Access policy");
+        accessPolicyTO = createPolicy(PolicyType.ACCESS, accessPolicyTO);
+        assertNotNull(accessPolicyTO);
+
+        CASSPTO casspTO = new CASSPTO();
+        casspTO.setName("ExampleRP_" + getUUIDString());
+        casspTO.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+        casspTO.setDescription("Example OIDC RP application");
+        casspTO.setServiceId("https://cassp.example.org/" + UUID.randomUUID().getMostSignificantBits());
+
+        casspTO.setAuthPolicy(authPolicyTO.getKey());
+        casspTO.setAccessPolicy(accessPolicyTO.getKey());
+        return casspTO;
+    }
+
+
 }
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
new file mode 100644
index 0000000..d5b870d
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/mapping/CASSPTOMapper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.mapping;
+
+import org.apereo.cas.services.RegexRegisteredService;
+import org.apereo.cas.services.RegisteredService;
+import org.apereo.cas.services.RegisteredServiceAccessStrategy;
+import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
+import org.apereo.cas.services.RegisteredServiceAuthenticationPolicy;
+
+import org.apache.syncope.common.lib.to.client.CASSPTO;
+import org.apache.syncope.common.lib.to.client.ClientAppTO;
+import org.springframework.stereotype.Component;
+
+@ClientAppMapFor(clientAppClass = CASSPTO.class)
+@Component
+public class CASSPTOMapper implements ClientAppMapper {
+
+    @Override
+    public RegisteredService build(
+        final ClientAppTO clientAppTO,
+        final RegisteredServiceAuthenticationPolicy authPolicy,
+        final RegisteredServiceAccessStrategy accessStrategy,
+        final RegisteredServiceAttributeReleasePolicy attributeReleasePolicy) {
+
+        CASSPTO rp = CASSPTO.class.cast(clientAppTO);
+
+        RegexRegisteredService service = new RegexRegisteredService();
+
+        service.setServiceId(rp.getServiceId());
+        service.setId(rp.getClientAppId());
+        service.setName(rp.getName());
+        service.setDescription(rp.getDescription());
+        service.setAccessStrategy(accessStrategy);
+        service.setAuthenticationPolicy(authPolicy);
+        service.setAttributeReleasePolicy(attributeReleasePolicy);
+
+        return service;
+    }
+
+}