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/10 08:28:02 UTC

[syncope] branch master updated (d50672f -> f7e75bb)

This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git.


    from d50672f  [SYNCOPE-1497] Waiting for Wicket 9.0.0-M6, simplifying workaround for WICKET-6766
     new d42c2cf  [SYNCOPE-160] Preliminary settings
     new 426b2f2  [SYNCOPE-160] AuthModule service
     new 90a3d32  [SYNCOPE-1545] ClientApp service
     new 9a4a178  [SYNCOPE-1545] allow wa to lazy-load application context by scheduling a quartz job to retry/refresh the context after startup
     new 485ddef  [SYNCOPE-160] Access, Auth and AttrRelease policies
     new 95630c4  [SYNCOPE-160] RegisteredClientApp service for WA
     new 2a1d9e8  [SYNCOPE-160] WA service refactoring
     new f7e75bb  [SYNCOPE-160] Adding missing support for AuthModule items

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../commons/IdMImplementationInfoProvider.java     |   8 +-
 .../resources/ItemTransformersTogglePanel.java     |   4 +-
 .../commons/IdRepoImplementationInfoProvider.java  |   4 +
 .../init/ClassPathScanImplementationLookup.java    |   2 +-
 .../common/lib/auth/AbstractAuthModuleConf.java    |  48 ++
 .../syncope/common/lib/auth/AuthModuleConf.java}   |  30 +-
 .../common/lib/auth/GoogleMfaAuthModuleConf.java   |  99 ++++
 .../common/lib/auth/JDBCAuthModuleConf.java        |  99 ++++
 .../common/lib/auth/JaasAuthModuleConf.java        |  88 +++
 .../common/lib/auth/LDAPAuthModuleConf.java        | 126 +++++
 .../common/lib/auth/OIDCAuthModuleConf.java        | 182 +++++++
 .../common/lib/auth/RadiusAuthModuleConf.java      | 172 ++++++
 .../common/lib/auth/SAML2IdPAuthModuleConf.java    | 430 +++++++++++++++
 .../common/lib/auth/StaticAuthModuleConf.java      |  47 ++
 .../common/lib/auth/SyncopeAuthModuleConf.java}    |  45 +-
 .../syncope/common/lib/auth/U2FAuthModuleConf.java |  69 +++
 .../syncope/common/lib/auth/package-info.java}     |  31 +-
 .../lib/policy/AbstractAccessPolicyConf.java       |  87 +++
 .../lib/policy/AbstractAttrReleasePolicyConf.java} |  42 +-
 .../common/lib/policy/AbstractAuthPolicyConf.java  |  60 +++
 .../policy/AbstractAuthPolicyCriteriaConf.java}    |  53 +-
 .../common/lib/policy/AccessPolicyConf.java}       |  34 +-
 .../lib/policy/AllowedAttrReleasePolicyConf.java   |  48 ++
 .../common/lib/policy/AttrReleasePolicyConf.java}  |  28 +-
 .../syncope/common/lib/policy/AuthPolicyConf.java} |  32 +-
 .../common/lib/policy/AuthPolicyCriteriaConf.java} |  37 +-
 .../lib/policy/DefaultAccessPolicyConf.java}       |  27 +-
 .../common/lib/policy/DefaultAuthPolicyConf.java   |  43 ++
 .../lib/policy/DefaultAuthPolicyCriteriaConf.java} |  36 +-
 .../syncope/common/lib/policy/package-info.java}   |  31 +-
 .../syncope/common/lib/to/AccessPolicyTO.java      |  53 ++
 .../syncope/common/lib/to/AttrReleasePolicyTO.java |  52 ++
 .../apache/syncope/common/lib/to/AuthModuleTO.java | 132 +++++
 .../apache/syncope/common/lib/to/AuthPolicyTO.java |  53 ++
 .../syncope/common/lib/to/client/ClientAppTO.java  | 154 ++++++
 .../syncope/common/lib/to/client/OIDCRPTO.java     | 168 ++++++
 .../syncope/common/lib/to/client/SAML2SPTO.java    | 225 ++++++++
 .../common/lib/to/client/package-info.java}        |  31 +-
 .../syncope/common/lib/to/package-info.java}       |  31 +-
 .../syncope/common/lib/types/AMEntitlement.java    |  20 +
 .../common/lib/types/AMImplementationType.java     |  44 ++
 .../syncope/common/lib/types/ClientAppType.java}   |  21 +-
 .../syncope/common/lib/types/OIDCSubjectType.java} |  21 +-
 .../syncope/common/lib/types/SAML2SPNameId.java}   |  33 +-
 .../apache/syncope/common/lib/wa/WAClientApp.java  |  71 +++
 .../syncope/common/lib/wa/package-info.java}       |  31 +-
 .../common/rest/api/service/AuthModuleService.java | 122 +++++
 .../common/rest/api/service/ClientAppService.java  | 134 +++++
 .../rest/api/service/wa/WAClientAppService.java    |  78 +++
 .../apache/syncope/common/lib/to/ResourceTO.java   |  26 +
 .../common/lib/types/IdMImplementationType.java    |  13 +-
 .../org/apache/syncope/common/lib/to/RealmTO.java  |  36 ++
 .../common/lib/types/IdRepoImplementationType.java |   5 +-
 .../syncope/common/lib/types/PolicyType.java       |  12 +
 core/am/logic/pom.xml                              |   6 +
 .../apache/syncope/core/logic/AuthModuleLogic.java | 131 +++++
 .../apache/syncope/core/logic/ClientAppLogic.java  | 204 +++++++
 .../logic/init/AMImplementationTypeLoader.java     |  36 +-
 .../init/ClassPathScanImplementationLookup.java    | 127 +++++
 .../syncope/core/logic/wa/WAClientAppLogic.java    | 160 ++++++
 .../rest/cxf/service/AuthModuleServiceImpl.java    |  65 +++
 .../rest/cxf/service/ClientAppServiceImpl.java     |  66 +++
 .../cxf/service/wa/WAClientAppServiceImpl.java     |  50 ++
 .../syncope/core/logic/ImplementationLogic.java    |   2 +-
 .../org/apache/syncope/core/logic/PolicyLogic.java |   3 +
 .../syncope/core/logic/cocoon/FopSerializer.java   |  26 +-
 .../syncope/core/logic/cocoon/TextSerializer.java  |   2 +-
 .../syncope/core/logic/cocoon/XSLTTransformer.java |  26 +-
 .../init/ClassPathScanImplementationLookup.java    |   2 +-
 .../core/rest/cxf/service/AbstractServiceImpl.java |   2 +-
 .../core/persistence/api/dao/PolicyDAO.java        |   9 +
 .../persistence/api/dao/auth/AuthModuleDAO.java    |  40 +-
 .../core/persistence/api/dao/auth/OIDCRPDAO.java   |  48 +-
 .../core/persistence/api/dao/auth/SAML2SPDAO.java  |  48 +-
 .../syncope/core/persistence/api/entity/Realm.java |  27 +-
 .../persistence/api/entity/auth/AuthModule.java    |  45 +-
 .../api/entity/auth/AuthModuleItem.java            |  25 +-
 .../persistence/api/entity/auth/ClientApp.java     |  56 ++
 .../api/entity/auth/ClientAppUtils.java            |  25 +-
 .../api/entity/auth/ClientAppUtilsFactory.java     |  30 +-
 .../core/persistence/api/entity/auth/OIDCRP.java   |  55 +-
 .../core/persistence/api/entity/auth/SAML2SP.java  |  76 +++
 .../api/entity/policy/AccessPolicy.java            |  30 +-
 .../api/entity/policy/AttrReleasePolicy.java       |  29 +-
 .../persistence/api/entity/policy/AuthPolicy.java  |  37 +-
 .../api/entity/resource/ExternalResource.java      |   2 +-
 .../src/test/resources/domains/MasterContent.xml   |  44 +-
 .../core/persistence/jpa/dao/AbstractDAO.java      |   4 +-
 .../core/persistence/jpa/dao/JPAPolicyDAO.java     |  75 ++-
 .../core/persistence/jpa/dao/JPARealmDAO.java      |  17 +-
 .../persistence/jpa/dao/auth/JPAAuthModuleDAO.java |  66 +++
 .../persistence/jpa/dao/auth/JPAOIDCRPDAO.java     | 107 ++++
 .../persistence/jpa/dao/auth/JPASAML2SPDAO.java    | 107 ++++
 .../persistence/jpa/entity/JPAEntityFactory.java   | 110 ++--
 .../core/persistence/jpa/entity/JPARealm.java      |  50 +-
 .../jpa/entity/auth/AbstractClientApp.java         | 131 +++++
 .../persistence/jpa/entity/auth/JPAAuthModule.java | 102 ++++
 .../jpa/entity/auth/JPAAuthModuleItem.java         |  55 +-
 .../jpa/entity/auth/JPAClientAppUtils.java         |  51 ++
 .../jpa/entity/auth/JPAClientAppUtilsFactory.java  |  72 +++
 .../persistence/jpa/entity/auth/JPAOIDCRP.java     | 143 +++++
 .../persistence/jpa/entity/auth/JPASAML2SP.java    | 204 +++++++
 .../jpa/entity/policy/JPAAccessPolicy.java         |  68 +++
 .../jpa/entity/policy/JPAAttrReleasePolicy.java    |  68 +++
 .../jpa/entity/policy/JPAAuthPolicy.java           |  68 +++
 .../jpa/entity/policy/JPAPolicyUtils.java          |  13 +-
 .../jpa/entity/policy/JPAPolicyUtilsFactory.java   |  18 +
 .../jpa/entity/resource/JPAMappingItem.java        |   4 +-
 .../jpa/entity/resource/JPAOrgUnitItem.java        |   4 +-
 .../jpa/inner/AbstractClientAppTest.java           | 109 ++++
 .../core/persistence/jpa/inner/AuthModuleTest.java | 498 +++++++++++++++++
 .../persistence/jpa/inner/ImplementationTest.java  |   9 +-
 .../core/persistence/jpa/inner/OIDCRPTest.java     |  82 +++
 .../core/persistence/jpa/inner/PolicyTest.java     | 186 ++++++-
 .../core/persistence/jpa/inner/SAML2SPTest.java    |  80 +++
 .../core/persistence/jpa/outer/PolicyTest.java     | 122 +++++
 .../src/test/resources/domains/MasterContent.xml   |  48 +-
 .../api/data/AuthModuleDataBinder.java             |  29 +-
 .../provisioning/api/data/ClientAppDataBinder.java |  28 +-
 .../api/data/wa/WAClientAppBinder.java             |  26 +-
 .../java/data/AuthModuleDataBinderImpl.java        | 138 +++++
 .../java/data/ClientAppDataBinderImpl.java         | 268 ++++++++++
 .../java/data/ConnInstanceDataBinderImpl.java      |   4 -
 .../java/data/ImplementationDataBinderImpl.java    |  44 +-
 .../java/data/PolicyDataBinderImpl.java            |  90 +++-
 .../java/data/RealmDataBinderImpl.java             |  86 ++-
 .../java/data/ResourceDataBinderImpl.java          |   4 -
 .../java/data/wa/WAClientAppBinderImpl.java        |  74 +++
 docker/wa/src/main/resources/wa.properties         |  27 +
 .../jpa/entity/JPAOIDCProviderItem.java            |   4 +-
 .../java/data/OIDCProviderDataBinderImpl.java      | 105 ++--
 .../core/persistence/jpa/dao/JPASAML2IdPDAO.java   |   1 -
 .../persistence/jpa/entity/JPASAML2IdPItem.java    |   4 +-
 .../java/data/SAML2IdPDataBinderImpl.java          | 104 ++--
 .../fit/core/reference/ITImplementationLookup.java |   2 +-
 .../org/apache/syncope/fit/AbstractITCase.java     | 104 +++-
 .../apache/syncope/fit/core/AuthModuleITCase.java  | 592 +++++++++++++++++++++
 .../apache/syncope/fit/core/ClientAppITCase.java   | 155 ++++++
 .../org/apache/syncope/fit/core/PolicyITCase.java  | 285 +++++++++-
 .../syncope/fit/core/PropagationTaskITCase.java    |  10 +-
 .../apache/syncope/fit/core/PullTaskITCase.java    |   5 +-
 .../org/apache/syncope/fit/core/RealmITCase.java   | 166 +++++-
 .../apache/syncope/fit/core/WAClientAppITCase.java |  83 +++
 fit/wa-reference/pom.xml                           |  24 +-
 fit/wa-reference/src/main/resources/wa.properties  |  27 +
 .../view/ResourceExplorerTopComponent.java         |   2 +-
 pom.xml                                            |  52 +-
 wa/bootstrap/pom.xml                               |  10 +-
 .../java/org/apache/syncope/wa/WARestClient.java   | 101 ++++
 .../bootstrap/SyncopeWABootstrapConfiguration.java |  52 ++
 ...on.java => SyncopeWAPropertySourceLocator.java} |  44 +-
 .../src/main/resources/META-INF/spring.factories   |   2 +-
 wa/starter/pom.xml                                 |  49 +-
 .../wa/starter/RegisteredServiceMapper.java        | 205 +++++++
 .../syncope/wa/starter/SyncopeServiceRegistry.java | 128 +++++
 .../syncope/wa/starter/SyncopeWAApplication.java   |  73 ++-
 .../syncope/wa/starter/SyncopeWAConfiguration.java |  29 +-
 .../src/main/resources/application.properties      |   6 +-
 wa/starter/src/main/resources/wa.properties        |  19 +
 159 files changed, 9989 insertions(+), 1085 deletions(-)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java} (62%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java} (52%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java} (62%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAccessPolicyConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAttrReleasePolicyConf.java} (54%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyCriteriaConf.java} (51%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyConf.java} (60%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AllowedAttrReleasePolicyConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyConf.java} (66%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyConf.java} (58%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyCriteriaConf.java} (50%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAccessPolicyConf.java} (65%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyConf.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyCriteriaConf.java} (61%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/policy/package-info.java} (62%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AccessPolicyTO.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AttrReleasePolicyTO.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthPolicyTO.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/ClientAppTO.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/OIDCRPTO.java
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/SAML2SPTO.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/to/client/package-info.java} (62%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java} (62%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/types/ClientAppType.java} (73%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCSubjectType.java} (73%)
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/types/SAML2SPNameId.java} (59%)
 create mode 100644 common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/WAClientApp.java
 copy common/{idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => am/lib/src/main/java/org/apache/syncope/common/lib/wa/package-info.java} (62%)
 create mode 100644 common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
 create mode 100644 common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/ClientAppService.java
 create mode 100644 common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAClientAppService.java
 create mode 100644 core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java
 create mode 100644 core/am/logic/src/main/java/org/apache/syncope/core/logic/ClientAppLogic.java
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMImplementationTypeLoader.java (55%)
 create mode 100644 core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
 create mode 100644 core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
 create mode 100644 core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.java
 create mode 100644 core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/ClientAppServiceImpl.java
 create mode 100644 core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAClientAppServiceImpl.java
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java (64%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/OIDCRPDAO.java (58%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/SAML2SPDAO.java (57%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java (59%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java (65%)
 create mode 100644 core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientApp.java
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtils.java (65%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/ClientAppUtilsFactory.java (63%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/OIDCRP.java (53%)
 create mode 100644 core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/SAML2SP.java
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AccessPolicy.java (65%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AttrReleasePolicy.java (65%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AuthPolicy.java (65%)
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAOIDCRPDAO.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPASAML2SPDAO.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/AbstractClientApp.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
 copy ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProviderItem.java => core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java (62%)
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtils.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAClientAppUtilsFactory.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAOIDCRP.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPASAML2SP.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java
 create mode 100644 core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java
 create mode 100644 core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java
 create mode 100644 core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
 create mode 100644 core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/OIDCRPTest.java
 create mode 100644 core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/SAML2SPTest.java
 create mode 100644 core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PolicyTest.java
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java (65%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/ClientAppDataBinder.java (65%)
 copy common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java => core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/wa/WAClientAppBinder.java (65%)
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ClientAppDataBinderImpl.java
 create mode 100644 core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/wa/WAClientAppBinderImpl.java
 create mode 100644 fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
 create mode 100644 fit/core-reference/src/test/java/org/apache/syncope/fit/core/ClientAppITCase.java
 create mode 100644 fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAClientAppITCase.java
 create mode 100644 wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java
 create mode 100644 wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java
 rename wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/{RestfulCloudConfigBootstrapConfiguration.java => SyncopeWAPropertySourceLocator.java} (55%)
 create mode 100644 wa/starter/src/main/java/org/apache/syncope/wa/starter/RegisteredServiceMapper.java
 create mode 100644 wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeServiceRegistry.java


[syncope] 03/08: [SYNCOPE-1545] ClientApp service

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 90a3d3290e3bf641a89a66d74883f3cb9d72342c
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu Apr 9 13:35:40 2020 +0200

    [SYNCOPE-1545] ClientApp service
---
 .../syncope/common/lib/to/client/ClientAppTO.java  | 154 ++++++++++++
 .../syncope/common/lib/to/client/OIDCRPTO.java     | 168 +++++++++++++
 .../syncope/common/lib/to/client/SAML2SPTO.java    | 225 +++++++++++++++++
 .../syncope/common/lib/types/ClientAppType.java    |  28 +++
 .../syncope/common/lib/types/SAML2SPNameId.java    |  42 ++++
 .../common/rest/api/service/ClientAppService.java  | 134 +++++++++++
 .../apache/syncope/core/logic/ClientAppLogic.java  | 204 ++++++++++++++++
 .../rest/cxf/service/ClientAppServiceImpl.java     |  66 +++++
 .../core/persistence/api/dao/auth/OIDCRPDAO.java   |  45 ++++
 .../core/persistence/api/dao/auth/SAML2SPDAO.java  |  45 ++++
 .../persistence/api/entity/auth/ClientApp.java     |  56 +++++
 .../api/entity/auth/ClientAppUtils.java            |  28 +++
 .../api/entity/auth/ClientAppUtilsFactory.java     |  33 +++
 .../core/persistence/api/entity/auth/OIDCRP.java   |  52 ++++
 .../core/persistence/api/entity/auth/SAML2SP.java  |  76 ++++++
 .../persistence/jpa/dao/auth/JPAOIDCRPDAO.java     | 107 ++++++++
 .../persistence/jpa/dao/auth/JPASAML2SPDAO.java    | 107 ++++++++
 .../jpa/entity/auth/AbstractClientApp.java         | 131 ++++++++++
 .../jpa/entity/auth/JPAClientAppUtils.java         |  51 ++++
 .../jpa/entity/auth/JPAClientAppUtilsFactory.java  |  72 ++++++
 .../persistence/jpa/entity/auth/JPAOIDCRP.java     | 143 +++++++++++
 .../persistence/jpa/entity/auth/JPASAML2SP.java    | 204 ++++++++++++++++
 .../jpa/inner/AbstractClientAppTest.java           | 109 +++++++++
 .../core/persistence/jpa/inner/OIDCRPTest.java     |  82 +++++++
 .../core/persistence/jpa/inner/SAML2SPTest.java    |  80 ++++++
 .../provisioning/api/data/ClientAppDataBinder.java |  31 +++
 .../java/data/ClientAppDataBinderImpl.java         | 268 +++++++++++++++++++++
 .../apache/syncope/fit/core/ClientAppITCase.java   | 155 ++++++++++++
 28 files changed, 2896 insertions(+)

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


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

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 95630c4ad32aecb81e976a91e1a34f07a7312c19
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()));
+    }
+
+}


[syncope] 01/08: [SYNCOPE-160] Preliminary settings

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit d42c2cf923d22b1cb865ef263d55db945589d5d9
Author: Marco Di Sabatino <md...@apache.org>
AuthorDate: Thu Apr 9 13:45:46 2020 +0200

    [SYNCOPE-160] Preliminary settings
---
 .../init/ClassPathScanImplementationLookup.java    |   2 +-
 .../syncope/common/lib/to/client/package-info.java |  30 ++++
 .../apache/syncope/common/lib/to/package-info.java |  30 ++++
 .../syncope/common/lib/types/AMEntitlement.java    |  20 +++
 .../common/lib/types/AMImplementationType.java     |  47 ++++++
 .../syncope/common/lib/types/OIDCSubjectType.java  |  28 ++++
 .../apache/syncope/common/lib/to/ResourceTO.java   |  26 ++++
 .../org/apache/syncope/common/lib/to/RealmTO.java  |  36 +++++
 core/am/logic/pom.xml                              |   6 +
 .../logic/init/AMImplementationTypeLoader.java     |  39 +++++
 .../init/ClassPathScanImplementationLookup.java    | 127 ++++++++++++++++
 .../syncope/core/logic/cocoon/FopSerializer.java   |  26 ++--
 .../syncope/core/logic/cocoon/TextSerializer.java  |   2 +-
 .../syncope/core/logic/cocoon/XSLTTransformer.java |  26 ++--
 .../syncope/core/persistence/api/entity/Realm.java |  27 +++-
 .../api/entity/resource/ExternalResource.java      |   2 +-
 .../src/test/resources/domains/MasterContent.xml   |  44 +++++-
 .../core/persistence/jpa/dao/AbstractDAO.java      |   4 +-
 .../core/persistence/jpa/dao/JPARealmDAO.java      |  17 ++-
 .../persistence/jpa/entity/JPAEntityFactory.java   | 110 +++++++++-----
 .../core/persistence/jpa/entity/JPARealm.java      |  50 ++++++-
 .../persistence/jpa/inner/ImplementationTest.java  |   9 +-
 .../src/test/resources/domains/MasterContent.xml   |  48 +++++-
 .../java/data/ConnInstanceDataBinderImpl.java      |   4 -
 .../java/data/ImplementationDataBinderImpl.java    |  42 ++++++
 .../java/data/RealmDataBinderImpl.java             |  86 ++++++++++-
 .../java/data/ResourceDataBinderImpl.java          |   4 -
 .../org/apache/syncope/fit/AbstractITCase.java     | 105 ++++++++++++-
 .../org/apache/syncope/fit/core/RealmITCase.java   | 166 ++++++++++++++++++++-
 pom.xml                                            |  52 ++++++-
 30 files changed, 1111 insertions(+), 104 deletions(-)

diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
index 3064747..f1733f5 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
@@ -241,7 +241,7 @@ public class ClassPathScanImplementationLookup {
         idmPages.sort(Comparator.comparing(o -> o.getAnnotation(IdMPage.class).priority()));
         idmPages = Collections.unmodifiableList(idmPages);
 
-        amPages.sort(Comparator.comparing(o -> o.getAnnotation(IdMPage.class).priority()));
+        amPages.sort(Comparator.comparing(o -> o.getAnnotation(AMPage.class).priority()));
         amPages = Collections.unmodifiableList(amPages);
 
         extPages.sort(Comparator.comparing(o -> o.getAnnotation(ExtPage.class).priority()));
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/package-info.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/package-info.java
new file mode 100644
index 0000000..503eff0
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/client/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NS)
+@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(type = Date.class, value = DateAdapter.class), })
+package org.apache.syncope.common.lib.to.client;
+
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.jaxb.DateAdapter;
+
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
+
+import java.util.Date;
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java
new file mode 100644
index 0000000..54e71ba
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NS)
+@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(type = Date.class, value = DateAdapter.class), })
+package org.apache.syncope.common.lib.to;
+
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.jaxb.DateAdapter;
+
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
+
+import java.util.Date;
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 06d511c..5938dd9 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
@@ -34,6 +34,26 @@ public final class AMEntitlement {
 
     public static final String GATEWAY_ROUTE_PUSH = "GATEWAY_ROUTE_PUSH";
 
+    public static final String CLIENTAPP_READ = "CLIENTAPP_READ";
+
+    public static final String CLIENTAPP_LIST = "CLIENTAPP_LIST";
+
+    public static final String CLIENTAPP_CREATE = "CLIENTAPP_CREATE";
+
+    public static final String CLIENTAPP_UPDATE = "CLIENTAPP_CREATE";
+
+    public static final String CLIENTAPP_DELETE = "CLIENTAPP_DELETE";
+
+    public static final String AUTH_MODULE_LIST = "AUTH_MODULE_LIST";
+
+    public static final String AUTH_MODULE_CREATE = "AUTH_MODULE_CREATE";
+
+    public static final String AUTH_MODULE_READ = "AUTH_MODULE_READ";
+
+    public static final String AUTH_MODULE_UPDATE = "AUTH_MODULE_UPDATE";
+
+    public static final String AUTH_MODULE_DELETE = "AUTH_MODULE_DELETE";
+
     private static final Set<String> VALUES;
 
     static {
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java
new file mode 100644
index 0000000..1c05727
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.types;
+
+import org.apache.commons.lang3.tuple.Pair;
+import java.util.Map;
+
+public final class AMImplementationType {
+
+    public static final String AUTH_POLICY_CONFIGURATIONS = "AUTH_POLICY_CONFIGURATIONS";
+
+    public static final String ACCESS_POLICY_CONFIGURATIONS = "ACCESS_POLICY_CONFIGURATIONS";
+
+    public static final String ATTR_RELEASE_POLICY_CONFIGURATIONS = "ATTR_RELEASE_CONFIGURATIONS";
+
+    private AMImplementationType() {
+        // private constructor for static utility class
+    }
+
+    private static final Map<String, String> VALUES = Map.ofEntries(
+            Pair.of(AUTH_POLICY_CONFIGURATIONS,
+                    "org.apache.syncope.common.lib.policy.AuthPolicyConf"),
+            Pair.of(ATTR_RELEASE_POLICY_CONFIGURATIONS,
+                    "org.apache.syncope.common.lib.policy.AttrReleasePolicyConf"),
+            Pair.of(ACCESS_POLICY_CONFIGURATIONS,
+                    "org.apache.syncope.common.lib.policy.AccessPolicyConf"));
+
+    public static Map<String, String> values() {
+        return VALUES;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCSubjectType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCSubjectType.java
new file mode 100644
index 0000000..93966a5
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/OIDCSubjectType.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.types;
+
+import javax.xml.bind.annotation.XmlEnum;
+
+@XmlEnum
+public enum OIDCSubjectType {
+    PAIRWISE,
+    PUBLIC
+
+}
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
index 20a8628..945ab80 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/to/ResourceTO.java
@@ -82,6 +82,10 @@ public class ResourceTO implements EntityTO {
 
     private String provisionSorter;
 
+    private String authPolicy;
+
+    private String accessPolicy;
+
     private final List<ConnConfProperty> confOverride = new ArrayList<>();
 
     private boolean overrideCapabilities = false;
@@ -205,6 +209,22 @@ public class ResourceTO implements EntityTO {
         this.provisionSorter = provisionSorter;
     }
 
+    public String getAuthPolicy() {
+        return authPolicy;
+    }
+
+    public void setAuthPolicy(final String authPolicy) {
+        this.authPolicy = authPolicy;
+    }
+
+    public String getAccessPolicy() {
+        return accessPolicy;
+    }
+
+    public void setAccessPolicy(final String accessPolicy) {
+        this.accessPolicy = accessPolicy;
+    }
+
     @JsonIgnore
     public Optional<ProvisionTO> getProvision(final String anyType) {
         return provisions.stream().filter(
@@ -294,9 +314,12 @@ public class ResourceTO implements EntityTO {
                 append(accountPolicy, other.accountPolicy).
                 append(pullPolicy, other.pullPolicy).
                 append(pushPolicy, other.pushPolicy).
+                append(authPolicy, other.authPolicy).
+                append(accessPolicy, other.accessPolicy).
                 append(confOverride, other.confOverride).
                 append(capabilitiesOverride, other.capabilitiesOverride).
                 append(propagationActions, other.propagationActions).
+                append(provisionSorter, other.provisionSorter).
                 build();
     }
 
@@ -319,10 +342,13 @@ public class ResourceTO implements EntityTO {
                 append(accountPolicy).
                 append(pullPolicy).
                 append(pushPolicy).
+                append(authPolicy).
+                append(accessPolicy).
                 append(confOverride).
                 append(overrideCapabilities).
                 append(capabilitiesOverride).
                 append(propagationActions).
+                append(provisionSorter).
                 build();
     }
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
index 910043f..037f782 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/to/RealmTO.java
@@ -54,6 +54,12 @@ public class RealmTO extends BaseBean implements NamedEntityTO, TemplatableTO {
 
     private String passwordPolicy;
 
+    private String authPolicy;
+
+    private String accessPolicy;
+
+    private String attrReleasePolicy;
+
     private final List<String> actions = new ArrayList<>();
 
     @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
@@ -114,6 +120,30 @@ public class RealmTO extends BaseBean implements NamedEntityTO, TemplatableTO {
         this.passwordPolicy = passwordPolicy;
     }
 
+    public String getAuthPolicy() {
+        return authPolicy;
+    }
+
+    public void setAuthPolicy(final String authPolicy) {
+        this.authPolicy = authPolicy;
+    }
+
+    public String getAccessPolicy() {
+        return accessPolicy;
+    }
+
+    public void setAccessPolicy(final String accessPolicy) {
+        this.accessPolicy = accessPolicy;
+    }
+
+    public String getAttrReleasePolicy() {
+        return attrReleasePolicy;
+    }
+
+    public void setAttrReleasePolicy(final String attrReleasePolicy) {
+        this.attrReleasePolicy = attrReleasePolicy;
+    }
+
     @XmlElementWrapper(name = "actions")
     @XmlElement(name = "action")
     @JsonProperty("actions")
@@ -153,6 +183,9 @@ public class RealmTO extends BaseBean implements NamedEntityTO, TemplatableTO {
                 append(fullPath, other.fullPath).
                 append(accountPolicy, other.accountPolicy).
                 append(passwordPolicy, other.passwordPolicy).
+                append(authPolicy, other.authPolicy).
+                append(accessPolicy, other.accessPolicy).
+                append(attrReleasePolicy, other.attrReleasePolicy).
                 append(actions, other.actions).
                 append(templates, other.templates).
                 append(resources, other.resources).
@@ -168,6 +201,9 @@ public class RealmTO extends BaseBean implements NamedEntityTO, TemplatableTO {
                 append(fullPath).
                 append(accountPolicy).
                 append(passwordPolicy).
+                append(authPolicy).
+                append(accessPolicy).
+                append(attrReleasePolicy).
                 append(actions).
                 append(templates).
                 append(resources).
diff --git a/core/am/logic/pom.xml b/core/am/logic/pom.xml
index 82e0d6d..e4ad801 100644
--- a/core/am/logic/pom.xml
+++ b/core/am/logic/pom.xml
@@ -43,6 +43,12 @@ under the License.
       <artifactId>syncope-core-idrepo-logic</artifactId>
       <version>${project.version}</version>
     </dependency>
+    
+    <dependency>
+      <groupId>org.apache.syncope.common.am</groupId>
+      <artifactId>syncope-common-am-lib</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMImplementationTypeLoader.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMImplementationTypeLoader.java
new file mode 100644
index 0000000..da5d393
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/AMImplementationTypeLoader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.init;
+
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
+import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AMImplementationTypeLoader implements SyncopeCoreLoader {
+
+    @Override
+    public int getOrder() {
+        return Ordered.HIGHEST_PRECEDENCE;
+    }
+
+    @Override
+    public void load() {
+        ImplementationTypesHolder.getInstance().putAll(AMImplementationType.values());
+    }
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
new file mode 100644
index 0000000..e887aef
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -0,0 +1,127 @@
+/*
+ * 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.init;
+
+import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.syncope.common.lib.policy.AccessPolicyConf;
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.common.lib.types.ImplementationTypesHolder;
+import org.apache.syncope.core.persistence.api.ImplementationLookup;
+import org.apache.syncope.core.persistence.api.SyncopeCoreLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
+import org.springframework.core.Ordered;
+import org.springframework.core.type.filter.AssignableTypeFilter;
+import org.springframework.util.ClassUtils;
+import org.apache.syncope.common.lib.policy.AuthPolicyConf;
+
+/**
+ * Cache class names for all implementations of Syncope interfaces found in classpath, for later usage.
+ */
+public class ClassPathScanImplementationLookup implements SyncopeCoreLoader {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ImplementationLookup.class);
+
+    private static final String DEFAULT_BASE_PACKAGE = "org.apache.syncope.core";
+
+    private Map<String, Set<String>> classNames;
+
+    private Map<Class<? extends AuthPolicyConf>, Class<? extends AuthPolicyConf>> authPolicyClasses;
+
+    private Map<Class<? extends AccessPolicyConf>, Class<? extends AccessPolicyConf>> accessPolicyClasses;
+
+    @Override
+    public int getOrder() {
+        return Ordered.HIGHEST_PRECEDENCE;
+    }
+
+    /**
+     * This method can be overridden by subclasses to customize classpath scan.
+     *
+     * @return basePackage for classpath scanning
+     */
+    protected static String getBasePackage() {
+        return DEFAULT_BASE_PACKAGE;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public void load() {
+        classNames = new HashMap<>();
+        ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
+        ImplementationTypesHolder.getInstance().getValues().forEach((typeName, typeInterface) -> {
+            classNames.put(typeName, new HashSet<>());
+            try {
+                scanner.addIncludeFilter(new AssignableTypeFilter(
+                        ClassUtils.resolveClassName(typeInterface, ClassUtils.getDefaultClassLoader())));
+            } catch (IllegalArgumentException e) {
+                LOG.error("Could not find class, ignoring...", e);
+            }
+        });
+
+        authPolicyClasses = new HashMap<>();
+        accessPolicyClasses = new HashMap<>();
+
+        scanner.findCandidateComponents(getBasePackage()).forEach(bd -> {
+            try {
+                Class<?> clazz = ClassUtils.resolveClassName(
+                        Objects.requireNonNull(bd.getBeanClassName()), ClassUtils.getDefaultClassLoader());
+                boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
+
+                if (AuthPolicyConf.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    classNames.get(AMImplementationType.AUTH_POLICY_CONFIGURATIONS).add(bd.getBeanClassName());
+                }
+                if (AccessPolicyConf.class.isAssignableFrom(clazz) && !isAbstractClazz) {
+                    classNames.get(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS).add(bd.getBeanClassName());
+                }
+            } catch (Throwable t) {
+                LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
+            }
+        });
+
+        classNames = Collections.unmodifiableMap(classNames);
+        LOG.debug("Implementation classes found: {}", classNames);
+
+        authPolicyClasses = Collections.unmodifiableMap(authPolicyClasses);
+        accessPolicyClasses = Collections.unmodifiableMap(accessPolicyClasses);
+    }
+
+    public Set<String> getClassNames(final String type) {
+        return classNames.get(type);
+    }
+
+    public Class<? extends AuthPolicyConf> getAuthPolicyConfClass(
+            final Class<? extends AuthPolicyConf> authPolicyConfClass) {
+
+        return authPolicyClasses.get(authPolicyConfClass);
+    }
+
+    public Class<? extends AccessPolicyConf> getAccessPolicyConfClass(
+            final Class<? extends AccessPolicyConf> accessPolicyConfClass) {
+
+        return accessPolicyClasses.get(accessPolicyConfClass);
+    }
+}
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/FopSerializer.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/FopSerializer.java
index bacec1f..5af1669 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/FopSerializer.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/FopSerializer.java
@@ -1,18 +1,20 @@
 /*
- * 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
+ * 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
+ *   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.
+ * 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.cocoon;
 
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/TextSerializer.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/TextSerializer.java
index ce4ffb4..8d60855 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/TextSerializer.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/TextSerializer.java
@@ -7,7 +7,7 @@
  * "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
+ *   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
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/XSLTTransformer.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/XSLTTransformer.java
index 271f84a..eb5dab6 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/XSLTTransformer.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/cocoon/XSLTTransformer.java
@@ -1,18 +1,20 @@
 /*
- * 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
+ * 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
+ *   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.
+ * 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.cocoon;
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Realm.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Realm.java
index c4ef281..3e2b23b 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Realm.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/Realm.java
@@ -18,31 +18,46 @@
  */
 package org.apache.syncope.core.persistence.api.entity;
 
-import java.util.List;
-import java.util.Optional;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import java.util.List;
+import java.util.Optional;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 public interface Realm extends Entity {
 
     String getName();
 
+    void setName(String name);
+
     Realm getParent();
 
+    void setParent(Realm parent);
+
     String getFullPath();
 
     AccountPolicy getAccountPolicy();
 
+    void setAccountPolicy(AccountPolicy accountPolicy);
+
     PasswordPolicy getPasswordPolicy();
 
-    void setName(String name);
+    void setPasswordPolicy(PasswordPolicy passwordPolicy);
 
-    void setParent(Realm parent);
+    void setAuthPolicy(AuthPolicy authPolicy);
 
-    void setAccountPolicy(AccountPolicy accountPolicy);
+    AuthPolicy getAuthPolicy();
 
-    void setPasswordPolicy(PasswordPolicy passwordPolicy);
+    void setAccessPolicy(AccessPolicy accessPolicy);
+
+    AccessPolicy getAccessPolicy();
+
+    void setAttrReleasePolicy(AttrReleasePolicy policy);
+
+    AttrReleasePolicy getAttrReleasePolicy();
 
     boolean add(Implementation action);
 
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
index 1bb44d9..8ad9c40 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/resource/ExternalResource.java
@@ -63,7 +63,7 @@ public interface ExternalResource extends ProvidedKeyEntity {
     void setPullPolicy(PullPolicy pullPolicy);
 
     PushPolicy getPushPolicy();
-    
+
     Implementation getProvisionSorter();
 
     void setProvisionSorter(Implementation provisionSorter);
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 7669c7b..8e82492 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -43,6 +43,48 @@ under the License.
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
+  
+  <!-- authentication policies -->
+  <Implementation id="MyDefaultAuthPolicyConf" type="AUTH_POLICY_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
+  <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf" description="an authentication policy"/>
+  <AuthPolicy id="b912a0d4-a890-416f-9ab8-84ab077eb028" name="DefaultAuthPolicy" description="Default authentication policy"/>
+
+  <!-- access policies -->
+  <Implementation id="MyDefaultAccessPolicyConf" type="ACCESS_POLICY_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf","name":"MyDefaultAccessPolicyConf","enabled":true,"ssoEnabled":true}'/>
+  <AccessPolicy id="419935c7-deb3-40b3-8a9a-683037e523a2" name="MyDefaultAccessPolicyConf" description="an access policy"/>
+
+  <!-- Attr Release Policies -->
+  <Implementation id="AllowedAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"AllowedAttrReleasePolicy","allowedAttributes":["cn","givenName","uid"]}'/>
+  <AttrReleasePolicy id="319935c7-deb3-40b3-8a9a-683037e523a2" name="AllowedAttrReleasePolicy" description="allowed attribute release policy policy"/>
+
+  <Implementation id="DenyAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"DenyAttrReleasePolicyConf"}'/>
+  <AttrReleasePolicy id="219935c7-deb3-40b3-8a9a-683037e523a2" name="DenyAttrReleasePolicy" description="deny attribute release policy policy"/>
+  
+  <!-- Authentication modules -->
+  <AuthModule id="be456831-593d-4003-b273-4c3fb61700df" name="DefaultLDAPAuthModule" 
+              description="LDAP auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.LDAPAuthModuleConf","name":"MyLDAPAuthModuleConf","userIdAttribute":"uid","bindCredential":"Password","ldapUrl":"ldap://localhost:1389","searchFilter":"cn={user}","baseDn":"dc=example,dc=org","subtreeSearch":true}'/>
+  <AuthModule id="4c3ed7e8-7008-11ea-bc55-0242ac130003" name="DefaultJDBCAuthModule"
+              description="JDBC auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.JDBCAuthModuleConf","name":"MyJDBCAuthModuleConf", "sql":"SELECT * FROM table WHERE name=?"}'/>
+  <AuthModule id="4c3ed4e6-7008-11ea-bc55-0242ac130003" name="DefaultGoogleMfaAuthModule"
+              description="Google Mfa auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf","name":"MyGoogleMfaAuthModuleConf","codeDigits":6,"issuer":"SyncopeTest"}'/>
+  <AuthModule id="4c3ed8f6-7008-11ea-bc55-0242ac130003" name="DefaultOIDCAuthModule"
+              description="OIDC auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.OIDCAuthModuleConf","name":"MyOIDCAuthModuleConf", "discoveryUri":"www.testurl.com"}'/>
+  <AuthModule id="4c3ed9d2-7008-11ea-bc55-0242ac130003" name="DefaultSAML2IdPAuthModule"
+              description="SAML2 IdP auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.SAML2IdPAuthModuleConf","name":"MySAML2IdPAuthModuleConf", "providerName":"testProviderName","serviceProviderMetadataPath":"file:/etc/metadata"}'/>
+  <AuthModule id="4c3edbbc-7008-11ea-bc55-0242ac130003" name="DefaultJaasAuthModule"
+              description="Jaas auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.JaasAuthModuleConf","name":"MyJaasAuthModuleConf","realm":"SYNCOPE","kerberosRealmSystemProperty":"sample-value"}'/>
+  <AuthModule id="4c3edc98-7008-11ea-bc55-0242ac130003" name="DefaultStaticAuthModule"
+              description="Static auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.StaticAuthModuleConf","name":"MyStaticAuthModuleConf","users":{"user1": "testUserPassword123"}}'/>
+  <AuthModule id="4c3edd60-7008-11ea-bc55-0242ac130003" name="DefaultSyncopeAuthModule"
+              description="Syncope auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf","name":"MySyncopeAuthModuleConf","domain":"Master","url":"http://mydomain.com/syncope/rest"}'/>
+  <AuthModule id="07c528f3-63b4-4dc1-a4da-87f35b8bdec8" name="DefaultRadiusAuthModule"
+              description="Radius auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.RadiusAuthModuleConf","name":"MyRadiusAuthModuleConf","protocol":"MSCHAPv2","inetAddress":"1.2.3.4", "sharedSecret":"thesecret"}'/>
+  <AuthModule id="f6e1288d-50d9-45fe-82ee-597c42242205" name="DefaultU2FAuthModule"
+              description="U2F auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.U2FAuthModuleConf","name":"MyU2FAuthModuleConf","expireDevices":40}'/>
 
   <RelationshipType id="inclusion" description="Models the act that an object is included in another"/>
   <RelationshipType id="neighborhood" description="Models the act that an object is near another"/>
@@ -2383,4 +2425,4 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
-</dataset>
\ No newline at end of file
+</dataset>
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
index fd87b30..899c3a5 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/AbstractDAO.java
@@ -19,10 +19,10 @@
 package org.apache.syncope.core.persistence.jpa.dao;
 
 import javax.persistence.EntityManager;
-import org.apache.syncope.core.spring.security.AuthContextUtils;
-import org.apache.syncope.core.spring.ApplicationContextProvider;
 import org.apache.syncope.core.persistence.api.dao.DAO;
 import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.spring.ApplicationContextProvider;
+import org.apache.syncope.core.spring.security.AuthContextUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Configurable;
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
index 1773a9b..b0abd08 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPARealmDAO.java
@@ -32,6 +32,8 @@ import org.apache.syncope.core.persistence.api.dao.RoleDAO;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.ProvisioningPolicy;
@@ -41,6 +43,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Repository;
 import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 @Repository
 public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
@@ -147,10 +150,22 @@ public class JPARealmDAO extends AbstractDAO<Realm> implements RealmDAO {
         if (ProvisioningPolicy.class.isAssignableFrom(policy.getClass())) {
             return Collections.<Realm>emptyList();
         }
+        String policyColumn = null;
+        if (policy instanceof AccountPolicy) {
+            policyColumn = "accountPolicy";
+        } else if (policy instanceof PasswordPolicy) {
+            policyColumn = "passwordPolicy";
+        } else if (policy instanceof AuthPolicy) {
+            policyColumn = "authPolicy";
+        } else if (policy instanceof AccessPolicy) {
+            policyColumn = "accessPolicy";
+        } else if (policy instanceof AttrReleasePolicy) {
+            policyColumn = "attrReleasePolicy";
+        }
 
         TypedQuery<Realm> query = entityManager().createQuery(
                 "SELECT e FROM " + JPARealm.class.getSimpleName() + " e WHERE e."
-                + (policy instanceof AccountPolicy ? "accountPolicy" : "passwordPolicy") + "=:policy", Realm.class);
+                + policyColumn + "=:policy", Realm.class);
         query.setParameter("policy", policy);
 
         List<Realm> result = new ArrayList<>();
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 b2a6786..a034449 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
@@ -19,15 +19,7 @@
 package org.apache.syncope.core.persistence.jpa.entity;
 
 import org.apache.syncope.core.persistence.api.dao.AnySearchDAO;
-import org.apache.syncope.core.persistence.jpa.entity.user.JPASecurityQuestion;
 import org.apache.syncope.core.persistence.api.entity.AccessToken;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
-import org.apache.syncope.core.persistence.jpa.entity.user.JPADynRoleMembership;
-import org.apache.syncope.core.persistence.jpa.entity.resource.JPAExternalResource;
-import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.AnyAbout;
 import org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
@@ -37,22 +29,25 @@ import org.apache.syncope.core.persistence.api.entity.Batch;
 import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 import org.apache.syncope.core.persistence.api.entity.ConnPoolConf;
 import org.apache.syncope.core.persistence.api.entity.DerSchema;
-import org.apache.syncope.core.persistence.api.entity.user.DynRoleMembership;
+import org.apache.syncope.core.persistence.api.entity.DynRealm;
+import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
 import org.apache.syncope.core.persistence.api.entity.Entity;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
-import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.Logger;
 import org.apache.syncope.core.persistence.api.entity.MailTemplate;
 import org.apache.syncope.core.persistence.api.entity.Notification;
-import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.PlainSchema;
-import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.entity.Privilege;
 import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.RelationshipType;
+import org.apache.syncope.core.persistence.api.entity.Remediation;
 import org.apache.syncope.core.persistence.api.entity.Report;
 import org.apache.syncope.core.persistence.api.entity.ReportExec;
 import org.apache.syncope.core.persistence.api.entity.ReportTemplate;
 import org.apache.syncope.core.persistence.api.entity.Role;
+import org.apache.syncope.core.persistence.api.entity.SchemaLabel;
 import org.apache.syncope.core.persistence.api.entity.VirSchema;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ADynGroupMembership;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AMembership;
@@ -61,20 +56,43 @@ import org.apache.syncope.core.persistence.api.entity.anyobject.APlainAttrUnique
 import org.apache.syncope.core.persistence.api.entity.anyobject.APlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.anyobject.ARelationship;
 import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+import org.apache.syncope.core.persistence.api.entity.auth.SAML2SP;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttr;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.api.entity.group.GPlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.group.Group;
 import org.apache.syncope.core.persistence.api.entity.group.TypeExtension;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
+import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
+import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
 import org.apache.syncope.core.persistence.api.entity.resource.Provision;
+import org.apache.syncope.core.persistence.api.entity.task.AnyTemplatePullTask;
 import org.apache.syncope.core.persistence.api.entity.task.NotificationTask;
 import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
+import org.apache.syncope.core.persistence.api.entity.task.PullTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTask;
 import org.apache.syncope.core.persistence.api.entity.task.PushTaskAnyFilter;
 import org.apache.syncope.core.persistence.api.entity.task.SchedTask;
 import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
+import org.apache.syncope.core.persistence.api.entity.user.DynRoleMembership;
+import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttr;
+import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrUniqueValue;
+import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
 import org.apache.syncope.core.persistence.api.entity.user.SecurityQuestion;
 import org.apache.syncope.core.persistence.api.entity.user.UDynGroupMembership;
 import org.apache.syncope.core.persistence.api.entity.user.UMembership;
@@ -83,6 +101,7 @@ import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrUniqueValue
 import org.apache.syncope.core.persistence.api.entity.user.UPlainAttrValue;
 import org.apache.syncope.core.persistence.api.entity.user.URelationship;
 import org.apache.syncope.core.persistence.api.entity.user.User;
+import org.apache.syncope.core.persistence.jpa.dao.JPAAnySearchDAO;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAADynGroupMembership;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAMembership;
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttr;
@@ -90,22 +109,42 @@ import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrUni
 import org.apache.syncope.core.persistence.jpa.entity.anyobject.JPAAPlainAttrValue;
 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.JPAOIDCRP;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPASAML2SP;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGPlainAttr;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGPlainAttrUniqueValue;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGPlainAttrValue;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPAGroup;
 import org.apache.syncope.core.persistence.jpa.entity.group.JPATypeExtension;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccessPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.resource.JPAExternalResource;
 import org.apache.syncope.core.persistence.jpa.entity.resource.JPAMapping;
 import org.apache.syncope.core.persistence.jpa.entity.resource.JPAMappingItem;
+import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnit;
+import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnitItem;
 import org.apache.syncope.core.persistence.jpa.entity.resource.JPAProvision;
-import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTaskAnyFilter;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPAAnyTemplatePullTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPANotificationTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPropagationTask;
+import org.apache.syncope.core.persistence.jpa.entity.task.JPAPullTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTask;
+import org.apache.syncope.core.persistence.jpa.entity.task.JPAPushTaskAnyFilter;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPASchedTask;
-import org.apache.syncope.core.persistence.jpa.entity.task.JPAPullTask;
 import org.apache.syncope.core.persistence.jpa.entity.task.JPATaskExec;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPADynRoleMembership;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttr;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttrUniqueValue;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttrValue;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
+import org.apache.syncope.core.persistence.jpa.entity.user.JPASecurityQuestion;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUDynGroupMembership;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUMembership;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttr;
@@ -113,34 +152,9 @@ import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrUniqueVa
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUPlainAttrValue;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAURelationship;
 import org.apache.syncope.core.persistence.jpa.entity.user.JPAUser;
-import org.apache.syncope.core.persistence.api.entity.task.PullTask;
-import org.apache.syncope.core.persistence.api.entity.task.AnyTemplatePullTask;
-import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
-import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
-import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnit;
-import org.apache.syncope.core.persistence.api.entity.DynRealm;
-import org.apache.syncope.core.persistence.api.entity.DynRealmMembership;
-import org.apache.syncope.core.persistence.api.entity.GatewayRoute;
-import org.apache.syncope.core.persistence.api.entity.Implementation;
-import org.apache.syncope.core.persistence.api.entity.Privilege;
-import org.apache.syncope.core.persistence.api.entity.Remediation;
-import org.apache.syncope.core.persistence.api.entity.SchemaLabel;
-import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
-import org.apache.syncope.core.persistence.jpa.entity.resource.JPAOrgUnitItem;
-import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
-import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
-import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttr;
-import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrUniqueValue;
-import org.apache.syncope.core.persistence.api.entity.user.LAPlainAttrValue;
-import org.apache.syncope.core.persistence.api.entity.user.LinkedAccount;
-import org.apache.syncope.core.persistence.jpa.dao.JPAAnySearchDAO;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
-import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttr;
-import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttrUniqueValue;
-import org.apache.syncope.core.persistence.jpa.entity.user.JPALAPlainAttrValue;
-import org.apache.syncope.core.persistence.jpa.entity.user.JPALinkedAccount;
 import org.apache.syncope.core.spring.security.SecureRandomUtils;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModuleItem;
 
 public class JPAEntityFactory implements EntityFactory {
 
@@ -293,6 +307,20 @@ public class JPAEntityFactory implements EntityFactory {
             result = (E) new JPABatch();
         } else if (reference.equals(GatewayRoute.class)) {
             result = (E) new JPAGatewayRoute();
+        } else if (reference.equals(AuthModule.class)) {
+            result = (E) new JPAAuthModule();
+        } else if (reference.equals(AuthModuleItem.class)) {
+            result = (E) new JPAAuthModuleItem();
+        } else if (reference.equals(AuthPolicy.class)) {
+            result = (E) new JPAAuthPolicy();
+        } else if (reference.equals(AccessPolicy.class)) {
+            result = (E) new JPAAccessPolicy();
+        } else if (reference.equals(AttrReleasePolicy.class)) {
+            result = (E) new JPAAttrReleasePolicy();
+        } else if (reference.equals(OIDCRP.class)) {
+            result = (E) new JPAOIDCRP();
+        } else if (reference.equals(SAML2SP.class)) {
+            result = (E) new JPASAML2SP();
         } else {
             throw new IllegalArgumentException("Could not find a JPA implementation of " + reference.getName());
         }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
index a0ad395..abe881c 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPARealm.java
@@ -40,11 +40,17 @@ import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.core.persistence.api.entity.AnyTemplateRealm;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
-import org.apache.syncope.core.persistence.api.entity.Realm;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccessPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.resource.JPAExternalResource;
 import org.apache.syncope.core.persistence.jpa.validation.entity.RealmCheck;
@@ -72,6 +78,15 @@ public class JPARealm extends AbstractGeneratedKeyEntity implements Realm {
     @ManyToOne(fetch = FetchType.EAGER)
     private JPAAccountPolicy accountPolicy;
 
+    @ManyToOne(fetch = FetchType.EAGER)
+    private JPAAuthPolicy authPolicy;
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    private JPAAccessPolicy accessPolicy;
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    private JPAAttrReleasePolicy attrReleasePolicy;
+
     @ManyToMany(fetch = FetchType.EAGER)
     @JoinTable(name = TABLE + "Action",
             joinColumns =
@@ -145,6 +160,28 @@ public class JPARealm extends AbstractGeneratedKeyEntity implements Realm {
     }
 
     @Override
+    public AuthPolicy getAuthPolicy() {
+        return authPolicy;
+    }
+
+    @Override
+    public void setAuthPolicy(final AuthPolicy authPolicy) {
+        checkType(authPolicy, JPAAuthPolicy.class);
+        this.authPolicy = (JPAAuthPolicy) authPolicy;
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy() {
+        return accessPolicy;
+    }
+
+    @Override
+    public void setAccessPolicy(final AccessPolicy accessPolicy) {
+        checkType(accessPolicy, JPAAccessPolicy.class);
+        this.accessPolicy = (JPAAccessPolicy) accessPolicy;
+    }
+
+    @Override
     public boolean add(final Implementation action) {
         checkType(action, JPAImplementation.class);
         checkImplementationType(action, IdRepoImplementationType.LOGIC_ACTIONS);
@@ -175,6 +212,17 @@ public class JPARealm extends AbstractGeneratedKeyEntity implements Realm {
     }
 
     @Override
+    public void setAttrReleasePolicy(final AttrReleasePolicy policy) {
+        checkType(policy, JPAAttrReleasePolicy.class);
+        this.attrReleasePolicy = (JPAAttrReleasePolicy) policy;
+    }
+
+    @Override
+    public AttrReleasePolicy getAttrReleasePolicy() {
+        return this.attrReleasePolicy;
+    }
+
+    @Override
     public boolean add(final ExternalResource resource) {
         checkType(resource, JPAExternalResource.class);
         return resources.contains((JPAExternalResource) resource) || resources.add((JPAExternalResource) resource);
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
index 21297d0..f34a7e7 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import java.util.List;
+import org.apache.syncope.common.lib.types.AMImplementationType;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
@@ -44,7 +45,7 @@ public class ImplementationTest extends AbstractTest {
         List<Implementation> implementations = implementationDAO.findAll();
         assertFalse(implementations.isEmpty());
 
-        assertEquals(18, implementations.size());
+        assertEquals(22, implementations.size());
 
         implementations = implementationDAO.findByType(IdMImplementationType.PULL_ACTIONS);
         assertEquals(1, implementations.size());
@@ -72,6 +73,12 @@ public class ImplementationTest extends AbstractTest {
 
         implementations = implementationDAO.findByType(IdMImplementationType.PUSH_CORRELATION_RULE);
         assertEquals(1, implementations.size());
+
+        implementations = implementationDAO.findByType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        assertEquals(1, implementations.size());
+
+        implementations = implementationDAO.findByType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        assertEquals(1, implementations.size());
     }
 
     @Test
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index f338a2b..b484956 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -43,6 +43,48 @@ under the License.
   <Implementation id="DefaultPasswordRuleConf3" type="PASSWORD_RULE" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf","maxLength":0,"minLength":10,"nonAlphanumericRequired":true,"alphanumericRequired":false,"digitRequired":true,"lowercaseRequired":true,"uppercaseRequired":true,"mustStartWithDigit":true,"mustntStartWithDigit":false,"mustEndWithDigit":true,"mustntEndWithDigit":false,"mustStartWithNonAlpha":false,"mustStartWithAlpha":false,"mustntStartWithNonAlpha":false,"mustntStartWithAlpha":false,"mustEndWit [...]
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
+  
+  <!-- Authentication policies -->
+  <Implementation id="MyDefaultAuthPolicyConf" type="AUTH_POLICY_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
+  <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf" description="an authentication policy"/>
+  <AuthPolicy id="b912a0d4-a890-416f-9ab8-84ab077eb028" name="DefaultAuthPolicy" description="Default authentication policy"/>
+
+  <!-- Access policies -->
+  <Implementation id="MyDefaultAccessPolicyConf" type="ACCESS_POLICY_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf","name":"MyDefaultAccessPolicyConf","enabled":true,"ssoEnabled":true}'/>
+  <AccessPolicy id="419935c7-deb3-40b3-8a9a-683037e523a2" name="MyDefaultAccessPolicyConf" description="an access policy"/>
+
+  <!-- Attr Release Policies -->
+  <Implementation id="AllowedAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"AllowedAttrReleasePolicy","allowedAttributes":["cn","givenName","uid"]}'/>
+  <AttrReleasePolicy id="319935c7-deb3-40b3-8a9a-683037e523a2" name="AllowedAttrReleasePolicy" description="allowed attribute release policy policy"/>
+
+  <Implementation id="DenyAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+                  body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"DenyAttrReleasePolicyConf"}'/>
+  <AttrReleasePolicy id="219935c7-deb3-40b3-8a9a-683037e523a2" name="DenyAttrReleasePolicy" description="deny attribute release policy policy"/>
+  
+  <!-- Authentication modules -->
+  <AuthModule id="be456831-593d-4003-b273-4c3fb61700df" name="DefaultLDAPAuthModule" 
+              description="LDAP auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.LDAPAuthModuleConf","name":"MyLDAPAuthModuleConf","userIdAttribute":"uid","bindCredential":"Password","ldapUrl":"ldap://localhost:1389","searchFilter":"cn={user}","baseDn":"dc=example,dc=org","subtreeSearch":true}'/>
+  <AuthModule id="4c3ed7e8-7008-11ea-bc55-0242ac130003" name="DefaultJDBCAuthModule"
+              description="JDBC auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.JDBCAuthModuleConf","name":"MyJDBCAuthModuleConf", "sql":"SELECT * FROM table WHERE name=?"}'/>
+  <AuthModule id="4c3ed4e6-7008-11ea-bc55-0242ac130003" name="DefaultGoogleMfaAuthModule"
+              description="Google Mfa auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf","name":"MyGoogleMfaAuthModuleConf","codeDigits":6,"issuer":"SyncopeTest"}'/>
+  <AuthModule id="4c3ed8f6-7008-11ea-bc55-0242ac130003" name="DefaultOIDCAuthModule"
+              description="OIDC auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.OIDCAuthModuleConf","name":"MyOIDCAuthModuleConf", "discoveryUri":"www.testurl.com"}'/>
+  <AuthModule id="4c3ed9d2-7008-11ea-bc55-0242ac130003" name="DefaultSAML2IdPAuthModule"
+              description="SAML2 IdP auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.SAML2IdPAuthModuleConf","name":"MySAML2IdPAuthModuleConf", "providerName":"testProviderName","serviceProviderMetadataPath":"file:/etc/metadata"}'/>
+  <AuthModule id="4c3edbbc-7008-11ea-bc55-0242ac130003" name="DefaultJaasAuthModule"
+              description="Jaas auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.JaasAuthModuleConf","name":"MyJaasAuthModuleConf","realm":"SYNCOPE","kerberosRealmSystemProperty":"sample-value"}'/>
+  <AuthModule id="4c3edc98-7008-11ea-bc55-0242ac130003" name="DefaultStaticAuthModule"
+              description="Static auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.StaticAuthModuleConf","name":"MyStaticAuthModuleConf","users":{"user1": "testUserPassword123"}}'/>
+  <AuthModule id="4c3edd60-7008-11ea-bc55-0242ac130003" name="DefaultSyncopeAuthModule"
+              description="Syncope auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf","name":"MySyncopeAuthModuleConf","domain":"Master","url":"http://mydomain.com/syncope/rest"}'/>
+  <AuthModule id="07c528f3-63b4-4dc1-a4da-87f35b8bdec8" name="DefaultRadiusAuthModule"
+              description="Radius auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.RadiusAuthModuleConf","name":"MyRadiusAuthModuleConf","protocol":"MSCHAPv2","inetAddress":"1.2.3.4", "sharedSecret":"thesecret"}'/>
+  <AuthModule id="f6e1288d-50d9-45fe-82ee-597c42242205" name="DefaultU2FAuthModule"
+              description="U2F auth module" jsonConf='{"@class":"org.apache.syncope.common.lib.auth.U2FAuthModuleConf","name":"MyU2FAuthModuleConf","expireDevices":40}'/>
 
   <RelationshipType id="inclusion" description="Models the act that an object is included in another"/>
   <RelationshipType id="neighborhood" description="Models the act that an object is near another"/>
@@ -500,7 +542,7 @@ under the License.
   
   <GPlainAttr id="22690472-ed3f-4972-8979-4c9251fab044" owner_id="ba9ed509-b1f5-48ab-a334-c8530a6422dc" schema_id="title"/>
   <GPlainAttrValue attribute_id="22690472-ed3f-4972-8979-4c9251fab044" id="e16765e6-f806-469e-ae34-1ddf56f2102a" stringValue="r13"/>
-
+  
   <!-- pull policies -->
   <PullPolicy id="66691e96-285f-4464-bc19-e68384ea4c85" description="a pull policy" conflictResolutionAction="IGNORE"/>
   <PullPolicy id="880f8553-069b-4aed-9930-2cd53873f544" description="another pull policy" conflictResolutionAction="ALL"/>
@@ -2451,7 +2493,6 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[UserLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
-
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[assign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[create]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[GroupLogic]:[]:[deprovision]:[SUCCESS]" logLevel="DEBUG"/>
@@ -2469,4 +2510,5 @@ $$ }&#10;
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unassign]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[unlink]:[SUCCESS]" logLevel="DEBUG"/>
   <SyncopeLogger logType="AUDIT" logName="syncope.audit.[LOGIC]:[AnyObjectLogic]:[]:[update]:[SUCCESS]" logLevel="DEBUG"/>
-</dataset>
\ No newline at end of file
+
+</dataset>
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
index 812b1bf..9502a5d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ConnInstanceDataBinderImpl.java
@@ -24,7 +24,6 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
 import org.apache.commons.lang3.tuple.Pair;
-import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ConnPoolConfTO;
@@ -60,9 +59,6 @@ public class ConnInstanceDataBinderImpl implements ConnInstanceDataBinder {
     private RealmDAO realmDAO;
 
     @Autowired
-    private ConfParamOps confParamOps;
-
-    @Autowired
     private EntityFactory entityFactory;
 
     @Override
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
index 0cfe2ff..9df9f8c 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
@@ -21,9 +21,12 @@ package org.apache.syncope.core.provisioning.java.data;
 import java.lang.reflect.Modifier;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.policy.AccessPolicyConf;
+import org.apache.syncope.common.lib.policy.AttrReleasePolicyConf;
 import org.apache.syncope.common.lib.policy.RuleConf;
 import org.apache.syncope.common.lib.report.ReportletConf;
 import org.apache.syncope.common.lib.to.ImplementationTO;
+import org.apache.syncope.common.lib.types.AMImplementationType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
@@ -34,6 +37,8 @@ import org.apache.syncope.core.persistence.api.dao.PasswordRule;
 import org.apache.syncope.core.persistence.api.dao.Reportlet;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.provisioning.api.LogicActions;
 import org.apache.syncope.core.provisioning.api.data.ImplementationDataBinder;
 import org.apache.syncope.core.provisioning.api.data.ItemTransformer;
@@ -51,6 +56,8 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
+import org.apache.syncope.common.lib.policy.AuthPolicyConf;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 @Component
 public class ImplementationDataBinderImpl implements ImplementationDataBinder {
@@ -149,6 +156,17 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                     base = ProvisionSorter.class;
                     break;
 
+                case AMImplementationType.ACCESS_POLICY_CONFIGURATIONS:
+                    base = AccessPolicy.class;
+                    break;
+
+                case AMImplementationType.AUTH_POLICY_CONFIGURATIONS:
+                    base = AuthPolicy.class;
+                    break;
+
+                case AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS:
+                    base = AttrReleasePolicy.class;
+                    break;
                 default:
             }
 
@@ -158,6 +176,30 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
             }
 
             switch (implementation.getType()) {
+                case AMImplementationType.ACCESS_POLICY_CONFIGURATIONS:
+                    AccessPolicyConf accessPolicyConf =
+                            POJOHelper.deserialize(implementation.getBody(), AccessPolicyConf.class);
+                    if (accessPolicyConf == null) {
+                        sce.getElements().add("Could not deserialize as AccessPolicy");
+                        throw sce;
+                    }
+                    break;
+                case AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS:
+                    AttrReleasePolicyConf policyConf =
+                            POJOHelper.deserialize(implementation.getBody(), AttrReleasePolicyConf.class);
+                    if (policyConf == null) {
+                        sce.getElements().add("Could not deserialize as AttrReleasePolicy");
+                        throw sce;
+                    }
+                    break;
+                case AMImplementationType.AUTH_POLICY_CONFIGURATIONS:
+                    AuthPolicyConf authPolicyConf =
+                            POJOHelper.deserialize(implementation.getBody(), AuthPolicyConf.class);
+                    if (authPolicyConf == null) {
+                        sce.getElements().add("Could not deserialize as AuthPolicy");
+                        throw sce;
+                    }
+                    break;
                 case IdRepoImplementationType.REPORTLET:
                     ReportletConf reportlet = POJOHelper.deserialize(implementation.getBody(), ReportletConf.class);
                     if (reportlet == null) {
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
index 236027c..00599ff 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RealmDataBinderImpl.java
@@ -36,6 +36,8 @@ import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.provisioning.api.data.RealmDataBinder;
@@ -43,6 +45,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 @Component
 public class RealmDataBinderImpl implements RealmDataBinder {
@@ -67,9 +70,6 @@ public class RealmDataBinderImpl implements RealmDataBinder {
     @Autowired
     private EntityFactory entityFactory;
 
-    @Autowired
-    private TemplateUtils templateUtils;
-
     private void setTemplates(final RealmTO realmTO, final Realm realm) {
         // validate JEXL expressions from templates and proceed if fine
         TemplateUtils.check(realmTO.getTemplates(), ClientExceptionType.InvalidRealm);
@@ -123,6 +123,39 @@ public class RealmDataBinderImpl implements RealmDataBinder {
                 throw sce;
             }
         }
+        if (realmTO.getAuthPolicy() != null) {
+            Policy policy = policyDAO.find(realmTO.getAuthPolicy());
+            if (policy instanceof AuthPolicy) {
+                realm.setAuthPolicy((AuthPolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
+                        + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+        if (realmTO.getAccessPolicy() != null) {
+            Policy policy = policyDAO.find(realmTO.getAccessPolicy());
+            if (policy instanceof AccessPolicy) {
+                realm.setAccessPolicy((AccessPolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
+                        + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+        if (realmTO.getAttrReleasePolicy() != null) {
+            Policy policy = policyDAO.find(realmTO.getAttrReleasePolicy());
+            if (policy instanceof AttrReleasePolicy) {
+                realm.setAttrReleasePolicy((AttrReleasePolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
+                        + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
 
         realmTO.getActions().forEach(logicActionsKey -> {
             Implementation logicAction = implementationDAO.find(logicActionsKey);
@@ -180,6 +213,48 @@ public class RealmDataBinderImpl implements RealmDataBinder {
             }
         }
 
+        if (realmTO.getAuthPolicy() == null) {
+            realm.setAuthPolicy(null);
+        } else {
+            Policy policy = policyDAO.find(realmTO.getAuthPolicy());
+            if (policy instanceof AuthPolicy) {
+                realm.setAuthPolicy((AuthPolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AuthPolicy.class.getSimpleName()
+                        + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+
+        if (realmTO.getAccessPolicy() == null) {
+            realm.setAccessPolicy(null);
+        } else {
+            Policy policy = policyDAO.find(realmTO.getAccessPolicy());
+            if (policy instanceof AccessPolicy) {
+                realm.setAccessPolicy((AccessPolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AccessPolicy.class.getSimpleName()
+                        + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+
+        if (realmTO.getAttrReleasePolicy() == null) {
+            realm.setAttrReleasePolicy(null);
+        } else {
+            Policy policy = policyDAO.find(realmTO.getAttrReleasePolicy());
+            if (policy instanceof AttrReleasePolicy) {
+                realm.setAttrReleasePolicy((AttrReleasePolicy) policy);
+            } else {
+                SyncopeClientException sce = SyncopeClientException.build(ClientExceptionType.InvalidPolicy);
+                sce.getElements().add("Expected " + AttrReleasePolicy.class.getSimpleName()
+                        + ", found " + policy.getClass().getSimpleName());
+                throw sce;
+            }
+        }
+
         realmTO.getActions().forEach(logicActionsKey -> {
             Implementation logicActions = implementationDAO.find(logicActionsKey);
             if (logicActions == null) {
@@ -228,6 +303,11 @@ public class RealmDataBinderImpl implements RealmDataBinder {
         if (admin) {
             realmTO.setAccountPolicy(realm.getAccountPolicy() == null ? null : realm.getAccountPolicy().getKey());
             realmTO.setPasswordPolicy(realm.getPasswordPolicy() == null ? null : realm.getPasswordPolicy().getKey());
+            realmTO.setAuthPolicy(
+                    realm.getAuthPolicy() == null ? null : realm.getAuthPolicy().getKey());
+            realmTO.setAccessPolicy(realm.getAccessPolicy() == null ? null : realm.getAccessPolicy().getKey());
+            realmTO.setAttrReleasePolicy(
+                    realm.getAttrReleasePolicy() == null ? null : realm.getAttrReleasePolicy().getKey());
 
             realm.getActions().forEach(action -> realmTO.getActions().add(action.getKey()));
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
index 1df550e..34fd19b 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ResourceDataBinderImpl.java
@@ -27,7 +27,6 @@ import java.util.Optional;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.syncope.common.keymaster.client.api.ConfParamOps;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
@@ -97,9 +96,6 @@ public class ResourceDataBinderImpl implements ResourceDataBinder {
     private AnyTypeClassDAO anyTypeClassDAO;
 
     @Autowired
-    private ConfParamOps confParamOps;
-
-    @Autowired
     private ImplementationDAO implementationDAO;
 
     @Autowired
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 38ca003..c8f8129 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
@@ -18,7 +18,6 @@
  */
 package org.apache.syncope.fit;
 
-import static de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesome5IconType.java;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.fail;
@@ -66,6 +65,7 @@ import org.apache.syncope.common.lib.request.UserCR;
 import org.apache.syncope.common.lib.to.SchemaTO;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.Attr;
+import org.apache.syncope.common.lib.to.AccessPolicyTO;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.to.GroupTO;
@@ -75,9 +75,17 @@ import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.ReportTO;
 import org.apache.syncope.common.lib.to.RoleTO;
 import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.lib.to.AuthPolicyTO;
+import org.apache.syncope.common.lib.to.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.AnyTypeKind;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.types.OIDCSubjectType;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
 import org.apache.syncope.common.lib.types.SchemaType;
 import org.apache.syncope.common.lib.types.TraceLevel;
 import org.apache.syncope.common.rest.api.RESTHeaders;
@@ -87,11 +95,14 @@ import org.apache.syncope.common.rest.api.service.AnyObjectService;
 import org.apache.syncope.common.rest.api.service.AnyTypeClassService;
 import org.apache.syncope.common.rest.api.service.AnyTypeService;
 import org.apache.syncope.common.rest.api.service.ApplicationService;
+import org.apache.syncope.common.rest.api.service.AuthModuleService;
 import org.apache.syncope.common.rest.api.service.CamelRouteService;
+import org.apache.syncope.common.rest.api.service.ClientAppService;
 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.SAML2SPService;
 import org.apache.syncope.common.rest.api.service.PolicyService;
 import org.apache.syncope.common.rest.api.service.ReportService;
 import org.apache.syncope.common.rest.api.service.ResourceService;
@@ -107,7 +118,6 @@ import org.apache.syncope.common.rest.api.service.RemediationService;
 import org.apache.syncope.common.rest.api.service.ReportTemplateService;
 import org.apache.syncope.common.rest.api.service.RoleService;
 import org.apache.syncope.common.rest.api.service.SAML2IdPService;
-import org.apache.syncope.common.rest.api.service.SAML2SPService;
 import org.apache.syncope.common.rest.api.service.SCIMConfService;
 import org.apache.syncope.common.rest.api.service.SchemaService;
 import org.apache.syncope.common.rest.api.service.SecurityQuestionService;
@@ -268,6 +278,8 @@ public abstract class AbstractITCase {
 
     protected static PolicyService policyService;
 
+    protected static AuthModuleService authModuleService;
+
     protected static SecurityQuestionService securityQuestionService;
 
     protected static ImplementationService implementationService;
@@ -288,6 +300,8 @@ public abstract class AbstractITCase {
 
     protected static SCIMConfService scimConfService;
 
+    protected static ClientAppService clientAppService;
+
     @BeforeAll
     public static void securitySetup() {
         try (InputStream propStream = Encryptor.class.getResourceAsStream("/security.properties")) {
@@ -352,11 +366,13 @@ public abstract class AbstractITCase {
         remediationService = adminClient.getService(RemediationService.class);
         gatewayRouteService = adminClient.getService(GatewayRouteService.class);
         camelRouteService = adminClient.getService(CamelRouteService.class);
-        saml2SpService = adminClient.getService(SAML2SPService.class);
+        saml2SpService = adminClient.getService(org.apache.syncope.common.rest.api.service.SAML2SPService.class);
         saml2IdPService = adminClient.getService(SAML2IdPService.class);
         oidcClientService = adminClient.getService(OIDCClientService.class);
         oidcProviderService = adminClient.getService(OIDCProviderService.class);
         scimConfService = adminClient.getService(SCIMConfService.class);
+        clientAppService = adminClient.getService(ClientAppService.class);
+        authModuleService = adminClient.getService(AuthModuleService.class);
     }
 
     @Autowired
@@ -559,6 +575,18 @@ public abstract class AbstractITCase {
         return (T) getObject(response.getLocation(), PolicyService.class, policy.getClass());
     }
 
+    @SuppressWarnings("unchecked")
+    protected AuthModuleTO createAuthModule(final AuthModuleTO authModule) {
+        Response response = authModuleService.create(authModule);
+        if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+            Exception ex = clientFactory.getExceptionMapper().fromResponse(response);
+            if (ex != null) {
+                throw (RuntimeException) ex;
+            }
+        }
+        return getObject(response.getLocation(), AuthModuleService.class, authModule.getClass());
+    }
+
     protected ResourceTO createResource(final ResourceTO resourceTO) {
         Response response = resourceService.create(resourceTO);
         if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
@@ -695,4 +723,75 @@ public abstract class AbstractITCase {
 
         return object;
     }
+
+    protected OIDCRPTO buildOIDCRP() {
+        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);
+
+        OIDCRPTO oidcrpTO = new OIDCRPTO();
+        oidcrpTO.setName("ExampleRP_" + getUUIDString());
+        oidcrpTO.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+        oidcrpTO.setDescription("Example OIDC RP application");
+        oidcrpTO.setClientId("clientId_" + getUUIDString());
+        oidcrpTO.setClientSecret("secret");
+        oidcrpTO.setSubjectType(OIDCSubjectType.PUBLIC);
+        oidcrpTO.getSupportedGrantTypes().add("something");
+        oidcrpTO.getSupportedResponseTypes().add("something");
+
+        oidcrpTO.setAuthPolicy(authPolicyTO.getKey());
+        oidcrpTO.setAccessPolicy(accessPolicyTO.getKey());
+
+        return oidcrpTO;
+    }
+
+    protected SAML2SPTO buildSAML2SP() {
+        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);
+
+        SAML2SPTO saml2spto = new SAML2SPTO();
+        saml2spto.setName("ExampleSAML2SP_" + getUUIDString());
+        saml2spto.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+        saml2spto.setDescription("Example SAML 2.0 service provider");
+        saml2spto.setEntityId("SAML2SPEntityId_" + getUUIDString());
+        saml2spto.setMetadataLocation("file:./test.xml");
+        saml2spto.setRequiredNameIdFormat(SAML2SPNameId.EMAIL_ADDRESS);
+        saml2spto.setEncryptionOptional(true);
+        saml2spto.setEncryptAssertions(true);
+
+        saml2spto.setAuthPolicy(authPolicyTO.getKey());
+        saml2spto.setAccessPolicy(accessPolicyTO.getKey());
+
+        return saml2spto;
+    }
+
+    @SuppressWarnings("unchecked")
+    protected <T extends ClientAppTO> T createClientApp(final ClientAppType type, final T clientAppTO) {
+        Response response = clientAppService.create(type, clientAppTO);
+        if (response.getStatusInfo().getStatusCode() != Response.Status.CREATED.getStatusCode()) {
+            Exception ex = clientFactory.getExceptionMapper().fromResponse(response);
+            if (ex != null) {
+                throw (RuntimeException) ex;
+            }
+        }
+        return (T) getObject(response.getLocation(), ClientAppService.class, clientAppTO.getClass());
+    }
+
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
index 1d8d5bc..8a0d9b2 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
@@ -27,17 +27,25 @@ import static org.junit.jupiter.api.Assertions.fail;
 
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 import java.util.UUID;
 import javax.ws.rs.core.GenericType;
 import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
+import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
 import org.apache.syncope.common.lib.to.RealmTO;
 import org.apache.syncope.common.lib.policy.DefaultAccountRuleConf;
+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.PagedResult;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
+import org.apache.syncope.common.lib.types.AMImplementationType;
 import org.apache.syncope.common.lib.types.ClientExceptionType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.PolicyType;
@@ -99,11 +107,17 @@ public class RealmITCase extends AbstractITCase {
         // 2. update setting policies
         actual.setAccountPolicy("06e2ed52-6966-44aa-a177-a0ca7434201f");
         actual.setPasswordPolicy("986d1236-3ac5-4a19-810c-5ab21d79cba1");
+        actual.setAuthPolicy("b912a0d4-a890-416f-9ab8-84ab077eb028");
+        actual.setAccessPolicy("419935c7-deb3-40b3-8a9a-683037e523a2");
+        actual.setAttrReleasePolicy("319935c7-deb3-40b3-8a9a-683037e523a2");
         realmService.update(actual);
 
         actual = getRealm(actual.getFullPath()).get();
         assertNotNull(actual.getAccountPolicy());
         assertNotNull(actual.getPasswordPolicy());
+        assertNotNull(actual.getAuthPolicy());
+        assertNotNull(actual.getAccessPolicy());
+        assertNotNull(actual.getAttrReleasePolicy());
 
         // 3. update changing parent
         actual.setParent(getRealm("/odd").get().getKey());
@@ -170,7 +184,7 @@ public class RealmITCase extends AbstractITCase {
 
         // 2. create realm with policy assigned
         RealmTO realm = new RealmTO();
-        realm.setName("withppolicy");
+        realm.setName("withPolicy");
 
         response = realmService.create(SyncopeConstants.ROOT_REALM, realm);
         RealmTO[] actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
@@ -198,6 +212,156 @@ public class RealmITCase extends AbstractITCase {
     }
 
     @Test
+    public void deletingAuthPolicy() {
+        // 1. create authentication policy
+        DefaultAuthPolicyConf ruleConf = new DefaultAuthPolicyConf();
+        ruleConf.getAuthModules().addAll(List.of("LdapAuthentication1"));
+
+        ImplementationTO rule = new ImplementationTO();
+        rule.setKey("TestAuthPolicy" + getUUIDString());
+        rule.setEngine(ImplementationEngine.JAVA);
+        rule.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        rule.setBody(POJOHelper.serialize(ruleConf));
+        Response response = implementationService.create(rule);
+        rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+
+        AuthPolicyTO policy = new AuthPolicyTO();
+        policy.setDescription("Test Authentication policy");
+        policy.setKey(rule.getKey());
+        policy = createPolicy(PolicyType.AUTH, policy);
+        assertNotNull(policy);
+
+        // 2. create realm with policy assigned
+        RealmTO realm = new RealmTO();
+        realm.setName("withAuthPolicy");
+
+        response = realmService.create(SyncopeConstants.ROOT_REALM, realm);
+        RealmTO[] actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
+        assertNotNull(actuals);
+        assertTrue(actuals.length > 0);
+        realm = actuals[0];
+
+        String existingAuthPolicy = realm.getAuthPolicy();
+
+        realm.setAuthPolicy(policy.getKey());
+        realmService.update(realm);
+
+        actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
+        assertNotNull(actuals);
+        assertTrue(actuals.length > 0);
+        RealmTO actual = actuals[0];
+        assertEquals(policy.getKey(), actual.getAuthPolicy());
+
+        // 3. remove policy
+        policyService.delete(PolicyType.AUTH, policy.getKey());
+
+        // 4. verify
+        actual = getRealm(actual.getFullPath()).get();
+        assertEquals(existingAuthPolicy, actual.getAuthPolicy());
+    }
+
+    @Test
+    public void deletingAccessPolicy() {
+        // 1. create access policy
+        DefaultAccessPolicyConf ruleConf = new DefaultAccessPolicyConf();
+        ruleConf.setEnabled(true);
+        ruleConf.setName("TestAccessPolicyConf" + getUUIDString());
+        ruleConf.getRequiredAttributes().put("cn", Set.of("admin", "Admin", "TheAdmin"));
+
+        ImplementationTO rule = new ImplementationTO();
+        rule.setKey("TestAccessPolicy" + getUUIDString());
+        rule.setEngine(ImplementationEngine.JAVA);
+        rule.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        rule.setBody(POJOHelper.serialize(ruleConf));
+        Response response = implementationService.create(rule);
+        rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+
+        AccessPolicyTO policy = new AccessPolicyTO();
+        policy.setDescription("Test Access policy");
+        policy.setKey(rule.getKey());
+        policy = createPolicy(PolicyType.ACCESS, policy);
+        assertNotNull(policy);
+
+        // 2. create realm with policy assigned
+        RealmTO realm = new RealmTO();
+        realm.setName("withAccessPolicy");
+
+        response = realmService.create(SyncopeConstants.ROOT_REALM, realm);
+        RealmTO[] actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
+        assertNotNull(actuals);
+        assertTrue(actuals.length > 0);
+        realm = actuals[0];
+
+        String existingAccessPolicy = realm.getAccessPolicy();
+
+        realm.setAccessPolicy(policy.getKey());
+        realmService.update(realm);
+
+        actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
+        assertNotNull(actuals);
+        assertTrue(actuals.length > 0);
+        RealmTO actual = actuals[0];
+        assertEquals(policy.getKey(), actual.getAccessPolicy());
+
+        // 3. remove policy
+        policyService.delete(PolicyType.ACCESS, policy.getKey());
+
+        // 4. verify
+        actual = getRealm(actual.getFullPath()).get();
+        assertEquals(existingAccessPolicy, actual.getAccessPolicy());
+    }
+
+    @Test
+    public void deletingAttributeReleasePolicy() {
+        // 1. create attribute release policy
+        AllowedAttrReleasePolicyConf ruleConf = new AllowedAttrReleasePolicyConf();
+        ruleConf.setName("MyDefaultAttrReleasePolicyConf" + getUUIDString());
+        ruleConf.getAllowedAttributes().addAll(List.of("cn", "givenName"));
+
+        ImplementationTO rule = new ImplementationTO();
+        rule.setKey("TestAttrReleasePolicy" + getUUIDString());
+        rule.setEngine(ImplementationEngine.JAVA);
+        rule.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        rule.setBody(POJOHelper.serialize(ruleConf));
+        Response response = implementationService.create(rule);
+        rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+
+        AttrReleasePolicyTO policy = new AttrReleasePolicyTO();
+        policy.setDescription("Test Attribute Release policy");
+        policy.setKey(rule.getKey());
+        policy = createPolicy(PolicyType.ATTR_RELEASE, policy);
+        assertNotNull(policy);
+
+        // 2. create realm with policy assigned
+        RealmTO realm = new RealmTO();
+        realm.setName("withAttrReleasePolicy");
+
+        response = realmService.create(SyncopeConstants.ROOT_REALM, realm);
+        RealmTO[] actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
+        assertNotNull(actuals);
+        assertTrue(actuals.length > 0);
+        realm = actuals[0];
+
+        String existingAttrReleasePolicy = realm.getAttrReleasePolicy();
+
+        realm.setAttrReleasePolicy(policy.getKey());
+        realmService.update(realm);
+
+        actuals = getObject(response.getLocation(), RealmService.class, RealmTO[].class);
+        assertNotNull(actuals);
+        assertTrue(actuals.length > 0);
+        RealmTO actual = actuals[0];
+        assertEquals(policy.getKey(), actual.getAttrReleasePolicy());
+
+        // 3. remove policy
+        policyService.delete(PolicyType.ATTR_RELEASE, policy.getKey());
+
+        // 4. verify
+        actual = getRealm(actual.getFullPath()).get();
+        assertEquals(existingAttrReleasePolicy, actual.getAttrReleasePolicy());
+    }
+
+    @Test
     public void delete() {
         RealmTO realm = new RealmTO();
         realm.setName("deletable3");
diff --git a/pom.xml b/pom.xml
index 03565b3..d66f64c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1465,6 +1465,11 @@ under the License.
       </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-core-util-api</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-core-configuration</artifactId>
         <version>${cas.version}</version>
       </dependency>
@@ -1490,6 +1495,16 @@ under the License.
       </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-core-services-registry</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-core-services-api</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-core-tickets</artifactId>
         <version>${cas.version}</version>
       </dependency>
@@ -1560,11 +1575,36 @@ under the License.
       </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-support-radius</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-support-radius-mfa</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-support-saml-idp</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-support-saml-idp-core</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-support-oidc</artifactId>
         <version>${cas.version}</version>
       </dependency>
       <dependency>
         <groupId>org.apereo.cas</groupId>
+        <artifactId>cas-server-support-oauth-services</artifactId>
+        <version>${cas.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apereo.cas</groupId>
         <artifactId>cas-server-support-validation</artifactId>
         <version>${cas.version}</version>
       </dependency>
@@ -1921,6 +1961,11 @@ under the License.
           </exclusion>
         </exclusions>
       </dependency>
+      <dependency>
+        <groupId>org.bouncycastle</groupId>
+        <artifactId>bcpkix-jdk15on</artifactId>
+        <version>1.65</version>
+      </dependency>
 
       <!-- TEST -->
       <dependency>
@@ -1942,17 +1987,12 @@ under the License.
         <scope>test</scope>
       </dependency>
       <dependency>
-        <groupId>org.bouncycastle</groupId>
-        <artifactId>bcpkix-jdk15on</artifactId>
-        <version>1.65</version>
-        <scope>test</scope>
-      </dependency>
-      <dependency>
         <groupId>org.eclipse.jetty</groupId>
         <artifactId>jetty-client</artifactId>
         <version>9.4.20.v20190813</version>
         <scope>test</scope>
       </dependency>
+
       <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-test</artifactId>


[syncope] 05/08: [SYNCOPE-160] Access, Auth and AttrRelease policies

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 485ddef93383d473d2c189b3a5987bceeb00815c
Author: Matteo Alessandroni <sk...@apache.org>
AuthorDate: Thu Apr 9 13:43:15 2020 +0200

    [SYNCOPE-160] Access, Auth and AttrRelease policies
---
 .../lib/policy/AbstractAccessPolicyConf.java       |  87 ++++++
 .../lib/policy/AbstractAttrReleasePolicyConf.java} |  42 +--
 .../common/lib/policy/AbstractAuthPolicyConf.java  |  60 +++++
 .../policy/AbstractAuthPolicyCriteriaConf.java}    |  53 ++--
 .../common/lib/policy/AccessPolicyConf.java}       |  34 ++-
 .../lib/policy/AllowedAttrReleasePolicyConf.java   |  48 ++++
 .../common/lib/policy/AttrReleasePolicyConf.java}  |  28 +-
 .../syncope/common/lib/policy/AuthPolicyConf.java} |  32 ++-
 .../common/lib/policy/AuthPolicyCriteriaConf.java} |  37 ++-
 .../lib/policy/DefaultAccessPolicyConf.java}       |  27 +-
 .../common/lib/policy/DefaultAuthPolicyConf.java   |  43 +++
 .../lib/policy/DefaultAuthPolicyCriteriaConf.java} |  36 ++-
 .../syncope/common/lib/policy/package-info.java}   |  31 +--
 .../syncope/common/lib/to/AccessPolicyTO.java      |  53 ++++
 .../syncope/common/lib/to/AttrReleasePolicyTO.java |  52 ++++
 .../apache/syncope/common/lib/to/AuthPolicyTO.java |  53 ++++
 .../syncope/common/lib/types/PolicyType.java       |  12 +
 .../org/apache/syncope/core/logic/PolicyLogic.java |   3 +
 .../core/persistence/api/dao/PolicyDAO.java        |   9 +
 .../api/entity/policy/AccessPolicy.java            |  30 +--
 .../api/entity/policy/AttrReleasePolicy.java       |  29 +-
 .../persistence/api/entity/policy/AuthPolicy.java  |  37 +--
 .../core/persistence/jpa/dao/JPAPolicyDAO.java     |  75 +++++-
 .../jpa/entity/policy/JPAAccessPolicy.java         |  68 +++++
 .../jpa/entity/policy/JPAAttrReleasePolicy.java    |  68 +++++
 .../jpa/entity/policy/JPAAuthPolicy.java           |  68 +++++
 .../jpa/entity/policy/JPAPolicyUtils.java          |  13 +-
 .../jpa/entity/policy/JPAPolicyUtilsFactory.java   |  18 ++
 .../core/persistence/jpa/inner/PolicyTest.java     | 186 +++++++++++--
 .../core/persistence/jpa/outer/PolicyTest.java     | 122 +++++++++
 .../java/data/PolicyDataBinderImpl.java            |  90 ++++++-
 .../org/apache/syncope/fit/core/PolicyITCase.java  | 291 ++++++++++++++++++++-
 32 files changed, 1544 insertions(+), 291 deletions(-)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAccessPolicyConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAccessPolicyConf.java
new file mode 100644
index 0000000..653fe11
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAccessPolicyConf.java
@@ -0,0 +1,87 @@
+/*
+ * 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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.syncope.common.lib.jaxb.XmlGenericMapAdapter;
+
+@XmlType
+@XmlSeeAlso({ DefaultAccessPolicyConf.class })
+public abstract class AbstractAccessPolicyConf implements Serializable, AccessPolicyConf {
+
+    private static final long serialVersionUID = 1153200197344709778L;
+
+    private String name;
+
+    private boolean enabled = true;
+
+    private boolean ssoEnabled = true;
+
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, Set<String>> requiredAttributes = new LinkedHashMap<>();
+
+    public AbstractAccessPolicyConf() {
+        setName(getClass().getName());
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(final boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    @Override
+    public boolean isSsoEnabled() {
+        return this.ssoEnabled;
+    }
+
+    public void setSsoEnabled(final boolean ssoEnabled) {
+        this.ssoEnabled = ssoEnabled;
+    }
+
+    @XmlElementWrapper(name = "requiredAttributes")
+    @XmlElement(name = "requiredAttribute")
+    @JsonProperty("requiredAttributes")
+    @Override
+    public Map<String, Set<String>> getRequiredAttributes() {
+        return requiredAttributes;
+    }
+
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAttrReleasePolicyConf.java
similarity index 54%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAttrReleasePolicyConf.java
index ea429c2..e3a8206 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAttrReleasePolicyConf.java
@@ -16,28 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import java.io.Serializable;
 
-@XmlEnum
-public enum PolicyType {
+@XmlType
+@XmlSeeAlso({ AllowedAttrReleasePolicyConf.class })
+public abstract class AbstractAttrReleasePolicyConf implements Serializable, AttrReleasePolicyConf {
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    private static final long serialVersionUID = 1153200197344709778L;
 
+    private String name;
+
+    public AbstractAttrReleasePolicyConf() {
+        setName(getClass().getName());
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final void setName(final String name) {
+        this.name = name;
+    }
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyConf.java
new file mode 100644
index 0000000..ac19f37
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyConf.java
@@ -0,0 +1,60 @@
+/*
+ * 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.policy;
+
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import java.io.Serializable;
+
+@XmlType
+@XmlSeeAlso({ DefaultAuthPolicyConf.class })
+public abstract class AbstractAuthPolicyConf implements Serializable, AuthPolicyConf {
+
+    private static final long serialVersionUID = 9185127128182430142L;
+
+    private String name;
+
+    private AuthPolicyCriteriaConf criteria;
+
+    public AbstractAuthPolicyConf() {
+        setName(getClass().getName());
+    }
+
+    public AbstractAuthPolicyConf(final String name) {
+        setName(name);
+    }
+
+    @Override
+    public AuthPolicyCriteriaConf getCriteria() {
+        return criteria;
+    }
+
+    public void setCriteria(final AuthPolicyCriteriaConf criteria) {
+        this.criteria = criteria;
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final void setName(final String name) {
+        this.name = name;
+    }
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyCriteriaConf.java
similarity index 51%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyCriteriaConf.java
index ea429c2..1f497de 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AbstractAuthPolicyCriteriaConf.java
@@ -16,28 +16,35 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
-
-import javax.xml.bind.annotation.XmlEnum;
-
-@XmlEnum
-public enum PolicyType {
-
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+package org.apache.syncope.common.lib.policy;
+
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+import java.io.Serializable;
+
+@XmlType
+@XmlSeeAlso({ DefaultAuthPolicyCriteriaConf.class })
+public abstract class AbstractAuthPolicyCriteriaConf implements AuthPolicyCriteriaConf, Serializable {
+
+    private static final long serialVersionUID = -6882164291962510245L;
+
+    private String name;
+
+    public AbstractAuthPolicyCriteriaConf() {
+        setName(getClass().getName());
+    }
+
+    public AbstractAuthPolicyCriteriaConf(final String name) {
+        setName(name);
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final void setName(final String name) {
+        this.name = name;
+    }
 
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyConf.java
similarity index 60%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyConf.java
index ea429c2..3cb0505 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AccessPolicyConf.java
@@ -16,28 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
 
-@XmlEnum
-public enum PolicyType {
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public interface AccessPolicyConf extends Serializable {
 
     /**
-     * How username values should look like.
+     * Give name of related access policy.
+     *
+     * @return name of this access policy
      */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    String getName();
+
+    boolean isEnabled();
+
+    boolean isSsoEnabled();
 
+    Map<String, Set<String>> getRequiredAttributes();
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AllowedAttrReleasePolicyConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AllowedAttrReleasePolicyConf.java
new file mode 100644
index 0000000..3bcccd0
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AllowedAttrReleasePolicyConf.java
@@ -0,0 +1,48 @@
+/*
+ * 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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+@XmlRootElement(name = "AllowedAttrReleasePolicyConf")
+@XmlType
+public class AllowedAttrReleasePolicyConf extends AbstractAttrReleasePolicyConf implements AttrReleasePolicyConf {
+
+    private static final long serialVersionUID = -1969836661359025380L;
+
+    /**
+     * Specify the list of allowed attribute to release.
+     * Use the special {@code *} to release everything.
+     */
+    private final List<String> allowedAttributes = new ArrayList<>();
+
+    @XmlElementWrapper(name = "allowedAttributes")
+    @XmlElement(name = "allowedAttribute")
+    @JsonProperty("allowedAttributes")
+    public List<String> getAllowedAttributes() {
+        return allowedAttributes;
+    }
+
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyConf.java
similarity index 66%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyConf.java
index ea429c2..0a3e1d1 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AttrReleasePolicyConf.java
@@ -16,28 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.io.Serializable;
 
-@XmlEnum
-public enum PolicyType {
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public interface AttrReleasePolicyConf extends Serializable {
 
     /**
-     * How username values should look like.
+     * Give name of related attr release policy.
+     *
+     * @return name of this attr release policy
      */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
-
+    String getName();
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyConf.java
similarity index 58%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyConf.java
index ea429c2..7740d3c 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyConf.java
@@ -16,28 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.io.Serializable;
 
-@XmlEnum
-public enum PolicyType {
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public interface AuthPolicyConf extends Serializable {
 
     /**
-     * How username values should look like.
+     * Give name of related authentication policy instance.
+     *
+     * @return name of this authentication policy instance
      */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
+    String getName();
+
     /**
-     * For handling conflicts resolution during push.
+     * Gets the policy criteria linked to this policy
+     * to establish criteria for success.
+     *
+     * @return the criteria
      */
-    PUSH;
-
+    AuthPolicyCriteriaConf getCriteria();
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyCriteriaConf.java
similarity index 50%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyCriteriaConf.java
index ea429c2..1847fc4 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/AuthPolicyCriteriaConf.java
@@ -16,28 +16,27 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.io.Serializable;
 
-@XmlEnum
-public enum PolicyType {
+/**
+ * Determine the criteria by which the authentication policy
+ * should conduct itself. Typically, this translates to how
+ * the policy should execute the modules to establish a success
+ * criteria. Examples would include "all modules should validate the user"
+ * or "any module can validate the user" before the policy can consider it
+ * a success.
+ */
+@FunctionalInterface
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public interface AuthPolicyCriteriaConf extends Serializable {
 
     /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
+     * Describe the name of the authentication policy criteria.
+     *
+     * @return name of this authentication policy instance
      */
-    PUSH;
-
+    String getName();
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAccessPolicyConf.java
similarity index 65%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAccessPolicyConf.java
index ea429c2..d12c98b 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAccessPolicyConf.java
@@ -16,28 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
 
-@XmlEnum
-public enum PolicyType {
+@XmlRootElement(name = "DefaultAccessPolicyConf")
+@XmlType
+public class DefaultAccessPolicyConf extends AbstractAccessPolicyConf implements AccessPolicyConf {
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    private static final long serialVersionUID = -1969836600059025380L;
 
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyConf.java
new file mode 100644
index 0000000..3076d39
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyConf.java
@@ -0,0 +1,43 @@
+/*
+ * 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.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+@XmlRootElement(name = "DefaultAuthPolicyConf")
+@XmlType
+public class DefaultAuthPolicyConf extends AbstractAuthPolicyConf implements AuthPolicyConf {
+
+    private static final long serialVersionUID = -2969836600059025380L;
+
+    private final List<String> authModules = new ArrayList<>();
+
+    @XmlElementWrapper(name = "authModules")
+    @XmlElement(name = "authModule")
+    @JsonProperty("authModules")
+    public List<String> getAuthModules() {
+        return authModules;
+    }
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyCriteriaConf.java
similarity index 61%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyCriteriaConf.java
index ea429c2..8bcb84b 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/DefaultAuthPolicyCriteriaConf.java
@@ -16,28 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
 
-@XmlEnum
-public enum PolicyType {
+@XmlRootElement(name = "AnyAuthPolicyCriteriaConf")
+@XmlType
+public class DefaultAuthPolicyCriteriaConf extends AbstractAuthPolicyCriteriaConf {
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    private static final long serialVersionUID = 3928807032588105869L;
 
+    private boolean all;
+
+    public boolean isAll() {
+        return all;
+    }
+
+    public void setAll(final boolean all) {
+        this.all = all;
+    }
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/package-info.java
similarity index 62%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/package-info.java
index ea429c2..d3c7ee7 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/policy/package-info.java
@@ -16,28 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+@XmlSchema(namespace = SyncopeConstants.NS)
+@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(type = Date.class, value = DateAdapter.class), })
+package org.apache.syncope.common.lib.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.jaxb.DateAdapter;
 
-@XmlEnum
-public enum PolicyType {
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
-
-}
+import java.util.Date;
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AccessPolicyTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AccessPolicyTO.java
new file mode 100644
index 0000000..f134fb6
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AccessPolicyTO.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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.syncope.common.lib.policy.PolicyTO;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "accessPolicy")
+@XmlType
+public class AccessPolicyTO extends PolicyTO {
+
+    private static final long serialVersionUID = -6711411162433533300L;
+
+    private String configuration;
+
+    @XmlTransient
+    @JsonProperty("@class")
+    @Schema(name = "@class", required = true, example = "org.apache.syncope.common.lib.to.AccessPolicyTO")
+    @Override
+    public String getDiscriminator() {
+        return getClass().getName();
+    }
+
+    public String getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(final String configuration) {
+        this.configuration = configuration;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AttrReleasePolicyTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AttrReleasePolicyTO.java
new file mode 100644
index 0000000..a5ca384
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AttrReleasePolicyTO.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.syncope.common.lib.policy.PolicyTO;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "attrReleasePolicy")
+@XmlType
+public class AttrReleasePolicyTO extends PolicyTO {
+
+    private static final long serialVersionUID = -1432411162433533300L;
+
+    private String configuration;
+
+    @XmlTransient
+    @JsonProperty("@class")
+    @Schema(name = "@class", required = true, example = "org.apache.syncope.common.lib.to.AttrReleasePolicyTO")
+    @Override
+    public String getDiscriminator() {
+        return getClass().getName();
+    }
+
+    public String getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(final String configuration) {
+        this.configuration = configuration;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthPolicyTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthPolicyTO.java
new file mode 100644
index 0000000..e18cd93
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthPolicyTO.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.common.lib.to;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.syncope.common.lib.policy.PolicyTO;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "authPolicy")
+@XmlType
+public class AuthPolicyTO extends PolicyTO {
+
+    private static final long serialVersionUID = -6711411162433533300L;
+
+    private String configuration;
+
+    @XmlTransient
+    @JsonProperty("@class")
+    @Schema(name = "@class", required = true,
+            example = "org.apache.syncope.common.lib.to.AuthPolicyTO")
+    @Override
+    public String getDiscriminator() {
+        return getClass().getName();
+    }
+
+    public String getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(final String configuration) {
+        this.configuration = configuration;
+    }
+
+}
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
index ea429c2..44b2667 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
@@ -32,6 +32,18 @@ public enum PolicyType {
      */
     PASSWORD,
     /**
+     * How authentication policies should look like.
+     */
+    AUTH,
+    /**
+     * How attribute release policies should look like.
+     */
+    ATTR_RELEASE,
+    /**
+     * How access policies should be defined.
+     */
+    ACCESS,
+    /**
      * For handling conflicts resolution during pull.
      */
     PULL,
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
index b643057..d391d71 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/PolicyLogic.java
@@ -65,6 +65,9 @@ public class PolicyLogic extends AbstractTransactionalLogic<PolicyTO> {
     @PreAuthorize("hasRole('" + IdRepoEntitlement.POLICY_UPDATE + "')")
     public PolicyTO update(final PolicyType type, final PolicyTO policyTO) {
         Policy policy = policyDAO.find(policyTO.getKey());
+        if (policy == null) {
+            throw new NotFoundException("Policy " + policyTO.getKey() + " not found");
+        }
 
         PolicyUtils policyUtils = policyUtilsFactory.getInstance(policy);
         if (policyUtils.getType() != type) {
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PolicyDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PolicyDAO.java
index 6765030..6fb6f24 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PolicyDAO.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/PolicyDAO.java
@@ -20,12 +20,15 @@ package org.apache.syncope.core.persistence.api.dao;
 
 import java.util.List;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 public interface PolicyDAO extends DAO<Policy> {
 
@@ -39,6 +42,12 @@ public interface PolicyDAO extends DAO<Policy> {
 
     List<PullPolicy> findByPullCorrelationRule(Implementation correlationRule);
 
+    List<AuthPolicy> findByAuthPolicy(Implementation policy);
+
+    List<AccessPolicy> findByAccessPolicy(Implementation policy);
+
+    List<AttrReleasePolicy> findByAttrReleasePolicy(Implementation policy);
+
     List<PushPolicy> findByPushCorrelationRule(Implementation correlationRule);
 
     List<AccountPolicy> findByResource(ExternalResource resource);
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AccessPolicy.java
similarity index 65%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AccessPolicy.java
index ea429c2..0294443 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AccessPolicy.java
@@ -16,28 +16,18 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.core.persistence.api.entity.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 
-@XmlEnum
-public enum PolicyType {
+public interface AccessPolicy extends Policy {
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    String getName();
+
+    void setName(String name);
+
+    Implementation getConfiguration();
+
+    void setConfiguration(Implementation configuration);
 
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AttrReleasePolicy.java
similarity index 65%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AttrReleasePolicy.java
index ea429c2..3b3b5db 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AttrReleasePolicy.java
@@ -16,28 +16,17 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
+package org.apache.syncope.core.persistence.api.entity.policy;
 
-import javax.xml.bind.annotation.XmlEnum;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
 
-@XmlEnum
-public enum PolicyType {
+public interface AttrReleasePolicy extends Policy {
 
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+    String getName();
 
+    void setName(String name);
+
+    Implementation getConfiguration();
+
+    void setConfiguration(Implementation configuration);
 }
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AuthPolicy.java
similarity index 65%
copy from common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
copy to core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AuthPolicy.java
index ea429c2..d7c4a9a 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/PolicyType.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/policy/AuthPolicy.java
@@ -16,28 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.types;
-
-import javax.xml.bind.annotation.XmlEnum;
-
-@XmlEnum
-public enum PolicyType {
-
-    /**
-     * How username values should look like.
-     */
-    ACCOUNT,
-    /**
-     * How password values should look like.
-     */
-    PASSWORD,
-    /**
-     * For handling conflicts resolution during pull.
-     */
-    PULL,
-    /**
-     * For handling conflicts resolution during push.
-     */
-    PUSH;
+package org.apache.syncope.core.persistence.api.entity.policy;
+
+
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+
+public interface AuthPolicy extends Policy {
+
+    String getName();
+
+    void setName(String name);
+
+    Implementation getConfiguration();
+
+    void setConfiguration(Implementation configuration);
 
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
index 57f60b3..1e8b9bd 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPAPolicyDAO.java
@@ -24,16 +24,22 @@ import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
-import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 import org.apache.syncope.core.persistence.jpa.entity.policy.AbstractPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccessPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAccountPolicy;
-import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAttrReleasePolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAAuthPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPasswordPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPullPolicy;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.jpa.entity.policy.JPAPushPolicy;
@@ -61,6 +67,12 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
                 ? JPAPullPolicy.class
                 : PushPolicy.class.isAssignableFrom(reference)
                 ? JPAPushPolicy.class
+                : AuthPolicy.class.isAssignableFrom(reference)
+                ? JPAAuthPolicy.class
+                : AccessPolicy.class.isAssignableFrom(reference)
+                ? JPAAccessPolicy.class
+                : AttrReleasePolicy.class.isAssignableFrom(reference)
+                ? JPAAttrReleasePolicy.class
                 : null;
     }
 
@@ -109,6 +121,33 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
     }
 
     @Override
+    public List<AuthPolicy> findByAuthPolicy(final Implementation configuration) {
+        TypedQuery<AuthPolicy> query = entityManager().createQuery("SELECT e FROM " + JPAAuthPolicy.class.
+                getSimpleName() + " e "
+                + "WHERE e.configuration=:configuration", AuthPolicy.class);
+        query.setParameter("configuration", configuration);
+        return query.getResultList();
+    }
+
+    @Override
+    public List<AccessPolicy> findByAccessPolicy(final Implementation configuration) {
+        TypedQuery<AccessPolicy> query = entityManager().createQuery("SELECT e FROM " + JPAAccessPolicy.class.
+                getSimpleName() + " e "
+                + "WHERE  e.configuration=:configuration", AccessPolicy.class);
+        query.setParameter("configuration", configuration);
+        return query.getResultList();
+    }
+
+    @Override
+    public List<AttrReleasePolicy> findByAttrReleasePolicy(final Implementation configuration) {
+        TypedQuery<AttrReleasePolicy> query = entityManager().createQuery(
+                "SELECT e FROM " + JPAAttrReleasePolicy.class.getSimpleName() + " e "
+                + "WHERE e.configuration=:configuration", AttrReleasePolicy.class);
+        query.setParameter("configuration", configuration);
+        return query.getResultList();
+    }
+
+    @Override
     public List<PushPolicy> findByPushCorrelationRule(final Implementation correlationRule) {
         TypedQuery<PushPolicy> query = entityManager().createQuery(
                 "SELECT DISTINCT e.pushPolicy FROM " + JPAPushCorrelationRuleEntity.class.getSimpleName() + " e "
@@ -147,20 +186,30 @@ public class JPAPolicyDAO extends AbstractDAO<Policy> implements PolicyDAO {
                 realm.setAccountPolicy(null);
             } else if (policy instanceof PasswordPolicy) {
                 realm.setPasswordPolicy(null);
+            } else if (policy instanceof AuthPolicy) {
+                realm.setAuthPolicy(null);
+            } else if (policy instanceof AccessPolicy) {
+                realm.setAccessPolicy(null);
+            } else if (policy instanceof AttrReleasePolicy) {
+                realm.setAttrReleasePolicy(null);
             }
         });
 
-        resourceDAO.findByPolicy(policy).forEach(resource -> {
-            if (policy instanceof AccountPolicy) {
-                resource.setAccountPolicy(null);
-            } else if (policy instanceof PasswordPolicy) {
-                resource.setPasswordPolicy(null);
-            } else if (policy instanceof PullPolicy) {
-                resource.setPullPolicy(null);
-            } else if (policy instanceof PushPolicy) {
-                resource.setPushPolicy(null);
-            }
-        });
+        if (!(policy instanceof AuthPolicy)
+                && !(policy instanceof AttrReleasePolicy)
+                && !(policy instanceof AccessPolicy)) {
+            resourceDAO.findByPolicy(policy).forEach(resource -> {
+                if (policy instanceof AccountPolicy) {
+                    resource.setAccountPolicy(null);
+                } else if (policy instanceof PasswordPolicy) {
+                    resource.setPasswordPolicy(null);
+                } else if (policy instanceof PullPolicy) {
+                    resource.setPullPolicy(null);
+                } else if (policy instanceof PushPolicy) {
+                    resource.setPushPolicy(null);
+                }
+            });
+        }
 
         entityManager().remove(policy);
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java
new file mode 100644
index 0000000..261c176
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.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.core.persistence.jpa.entity.policy;
+
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Table;
+import javax.persistence.ManyToOne;
+
+@Entity
+@Table(name = JPAAccessPolicy.TABLE)
+public class JPAAccessPolicy extends AbstractPolicy implements AccessPolicy {
+
+    public static final String TABLE = "AccessPolicy";
+
+    private static final long serialVersionUID = -4190607009908888884L;
+
+    @Column(unique = true, nullable = false)
+    private String name;
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    private JPAImplementation configuration;
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public Implementation getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public void setConfiguration(final Implementation configuration) {
+        checkType(configuration, JPAImplementation.class);
+        checkImplementationType(configuration, AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        this.configuration = (JPAImplementation) configuration;
+    }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java
new file mode 100644
index 0000000..6a21edc
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.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.core.persistence.jpa.entity.policy;
+
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Table;
+
+import javax.persistence.ManyToOne;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+
+@Entity
+@Table(name = JPAAttrReleasePolicy.TABLE)
+public class JPAAttrReleasePolicy extends AbstractPolicy implements AttrReleasePolicy {
+
+    public static final String TABLE = "AttrReleasePolicy";
+
+    private static final long serialVersionUID = -4190607669908888884L;
+
+    @Column(unique = true, nullable = false)
+    private String name;
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    private JPAImplementation configuration;
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public Implementation getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public void setConfiguration(final Implementation configuration) {
+        checkType(configuration, JPAImplementation.class);
+        checkImplementationType(configuration, AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        this.configuration = (JPAImplementation) configuration;
+    }
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java
new file mode 100644
index 0000000..b367152
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.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.core.persistence.jpa.entity.policy;
+
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Table;
+import javax.persistence.ManyToOne;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+
+@Entity
+@Table(name = JPAAuthPolicy.TABLE)
+public class JPAAuthPolicy extends AbstractPolicy implements AuthPolicy {
+
+    public static final String TABLE = "AuthPolicy";
+
+    private static final long serialVersionUID = -4190607009908888884L;
+
+    @Column(unique = true, nullable = false)
+    private String name;
+
+    @ManyToOne(fetch = FetchType.EAGER)
+    private JPAImplementation configuration;
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public Implementation getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public void setConfiguration(final Implementation configuration) {
+        checkType(configuration, JPAImplementation.class);
+        checkImplementationType(configuration, AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        this.configuration = (JPAImplementation) configuration;
+    }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
index d87325e..c694eca 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtils.java
@@ -19,12 +19,15 @@
 package org.apache.syncope.core.persistence.jpa.entity.policy;
 
 import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 public class JPAPolicyUtils implements PolicyUtils {
 
@@ -51,10 +54,18 @@ public class JPAPolicyUtils implements PolicyUtils {
             case PULL:
                 return PullPolicy.class;
 
+            case AUTH:
+                return AuthPolicy.class;
+
+            case ATTR_RELEASE:
+                return AttrReleasePolicy.class;
+
+            case ACCESS:
+                return AccessPolicy.class;
+
             case PUSH:
             default:
                 return PushPolicy.class;
         }
     }
-
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
index 2a616c2..1db66e9 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAPolicyUtilsFactory.java
@@ -18,13 +18,18 @@
  */
 package org.apache.syncope.core.persistence.jpa.entity.policy;
 
+import org.apache.syncope.common.lib.to.AccessPolicyTO;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
 import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
+import org.apache.syncope.common.lib.to.AttrReleasePolicyTO;
+import org.apache.syncope.common.lib.to.AuthPolicyTO;
 import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtils;
@@ -32,6 +37,7 @@ import org.apache.syncope.core.persistence.api.entity.policy.PolicyUtilsFactory;
 import org.springframework.stereotype.Component;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 @Component
 public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
@@ -52,6 +58,12 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
             type = PolicyType.PULL;
         } else if (policy instanceof PushPolicy) {
             type = PolicyType.PUSH;
+        } else if (policy instanceof AuthPolicy) {
+            type = PolicyType.AUTH;
+        } else if (policy instanceof AccessPolicy) {
+            type = PolicyType.ACCESS;
+        } else if (policy instanceof AttrReleasePolicy) {
+            type = PolicyType.ATTR_RELEASE;
         } else {
             throw new IllegalArgumentException("Invalid policy: " + policy);
         }
@@ -70,6 +82,12 @@ public class JPAPolicyUtilsFactory implements PolicyUtilsFactory {
             type = PolicyType.PULL;
         } else if (policyClass == PushPolicyTO.class) {
             type = PolicyType.PUSH;
+        } else if (policyClass == AuthPolicyTO.class) {
+            type = PolicyType.AUTH;
+        } else if (policyClass == AccessPolicyTO.class) {
+            type = PolicyType.ACCESS;
+        } else if (policyClass == AttrReleasePolicyTO.class) {
+            type = PolicyType.ATTR_RELEASE;
         } else {
             throw new IllegalArgumentException("Invalid PolicyTO class: " + policyClass.getName());
         }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
index 34ea16e..3e7cdaf 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
@@ -18,37 +18,45 @@
  */
 package org.apache.syncope.core.persistence.jpa.inner;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import java.util.List;
-import java.util.UUID;
+import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
+import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyCriteriaConf;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPullCorrelationRuleConf;
 import org.apache.syncope.common.lib.policy.DefaultPushCorrelationRuleConf;
+import org.apache.syncope.common.lib.types.AMImplementationType;
 import org.apache.syncope.common.lib.types.ConflictResolutionAction;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
-import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
 import org.apache.syncope.core.persistence.api.dao.PolicyDAO;
+import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
+import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
+import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
 import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
-import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
-import org.apache.syncope.core.persistence.api.dao.PullCorrelationRule;
-import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
-import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
-import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.Set;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
 
 @Transactional("Master")
 public class PolicyTest extends AbstractTest {
@@ -93,13 +101,61 @@ public class PolicyTest extends AbstractTest {
         assertNotNull(pushCRConf);
         assertEquals(1, pushCRConf.getSchemas().size());
         assertTrue(pushCRConf.getSchemas().contains("surname"));
+
+        AccessPolicy accessPolicy = policyDAO.find("617735c7-deb3-40b3-8a9a-683037e523a2");
+        assertNull(accessPolicy);
+        accessPolicy = policyDAO.find("419935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(accessPolicy);
+        accessPolicy = policyDAO.find(UUID.randomUUID().toString());
+        assertNull(accessPolicy);
+
+        AuthPolicy authPolicy = policyDAO.find("b912a0d4-a890-416f-9ab8-84ab077eb028");
+        assertNotNull(authPolicy);
+        authPolicy = policyDAO.find("659b9906-4b6e-4bc0-aca0-6809dff346d4");
+        assertNotNull(authPolicy);
+        authPolicy = policyDAO.find(UUID.randomUUID().toString());
+        assertNull(authPolicy);
+
+        AttrReleasePolicy attrReleasePolicy = policyDAO.find("019935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNull(attrReleasePolicy);
+        attrReleasePolicy = policyDAO.find("319935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(attrReleasePolicy);
+        attrReleasePolicy = policyDAO.find(UUID.randomUUID().toString());
+        assertNull(attrReleasePolicy);
+
+    }
+
+    @Test
+    public void findByPolicyImpl() {
+        AccessPolicy accessPolicy = policyDAO.find("419935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(accessPolicy);
+        AuthPolicy authPolicy = policyDAO.find("b912a0d4-a890-416f-9ab8-84ab077eb028");
+        assertNotNull(authPolicy);
+        AttrReleasePolicy attrReleasePolicy = policyDAO.find("319935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(attrReleasePolicy);
+
+        assertFalse(policyDAO.findByAccessPolicy(accessPolicy.getConfiguration()).isEmpty());
+        assertFalse(policyDAO.findByAuthPolicy(authPolicy.getConfiguration()).isEmpty());
+        assertFalse(policyDAO.findByAttrReleasePolicy(attrReleasePolicy.getConfiguration()).isEmpty());
     }
 
     @Test
     public void findByType() {
-        List<PullPolicy> policies = policyDAO.find(PullPolicy.class);
-        assertNotNull(policies);
-        assertFalse(policies.isEmpty());
+        List<PullPolicy> pullPolicies = policyDAO.find(PullPolicy.class);
+        assertNotNull(pullPolicies);
+        assertFalse(pullPolicies.isEmpty());
+
+        List<AccessPolicy> accessPolicies = policyDAO.find(AccessPolicy.class);
+        assertNotNull(accessPolicies);
+        assertEquals(1, accessPolicies.size());
+
+        List<AuthPolicy> authPolicies = policyDAO.find(AuthPolicy.class);
+        assertNotNull(authPolicies);
+        assertEquals(2, authPolicies.size());
+
+        List<AttrReleasePolicy> attrReleasePolicies = policyDAO.find(AttrReleasePolicy.class);
+        assertNotNull(attrReleasePolicies);
+        assertEquals(2, attrReleasePolicies.size());
     }
 
     @Test
@@ -144,6 +200,84 @@ public class PolicyTest extends AbstractTest {
                 policy.getCorrelationRule(anyTypeDAO.findUser()).get().getImplementation().getKey());
         assertEquals(pullGRuleName,
                 policy.getCorrelationRule(anyTypeDAO.findGroup()).get().getImplementation().getKey());
+
+        int beforeCount = policyDAO.findAll().size();
+        AccessPolicy accessPolicy = entityFactory.newEntity(AccessPolicy.class);
+        accessPolicy.setName("AttrReleasePolicyAllowEverything");
+        accessPolicy.setDescription("This is a sample attr release policy that releases everything");
+
+        DefaultAccessPolicyConf conf = new DefaultAccessPolicyConf();
+        conf.getRequiredAttributes().putAll(Map.of("cn", Set.of("syncope")));
+        conf.setName("AttrReleasePolicyAllowEverything");
+
+        Implementation type = entityFactory.newEntity(Implementation.class);
+        type.setKey("AttrReleasePolicyAllowEverything");
+        type.setEngine(ImplementationEngine.JAVA);
+        type.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        type.setBody(POJOHelper.serialize(conf));
+        type = implementationDAO.save(type);
+
+        accessPolicy.setConfiguration(type);
+        accessPolicy = policyDAO.save(accessPolicy);
+
+        assertNotNull(accessPolicy);
+        assertNotNull(accessPolicy.getKey());
+
+        int afterCount = policyDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+
+        beforeCount = policyDAO.findAll().size();
+        AuthPolicy authPolicy = entityFactory.newEntity(AuthPolicy.class);
+        authPolicy.setName("AuthPolicyTest");
+        authPolicy.setDescription("This is a sample authentication policy");
+
+        DefaultAuthPolicyConf authPolicyConf = new DefaultAuthPolicyConf();
+        authPolicyConf.getAuthModules().addAll(List.of("LdapAuthentication1", "DatabaseAuthentication2"));
+        DefaultAuthPolicyCriteriaConf criteria = new DefaultAuthPolicyCriteriaConf();
+        criteria.setName("DefaultConf");
+        criteria.setAll(true);
+        authPolicyConf.setCriteria(criteria);
+        Implementation authPolicyType = entityFactory.newEntity(Implementation.class);
+        authPolicyType.setKey("AuthPolicyConfKey");
+        authPolicyType.setEngine(ImplementationEngine.JAVA);
+        authPolicyType.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        authPolicyType.setBody(POJOHelper.serialize(authPolicyConf));
+        authPolicyType = implementationDAO.save(authPolicyType);
+
+        authPolicy.setConfiguration(authPolicyType);
+        authPolicy = policyDAO.save(authPolicy);
+
+        assertNotNull(authPolicy);
+        assertNotNull(authPolicy.getKey());
+
+        afterCount = policyDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+
+        beforeCount = policyDAO.findAll().size();
+        AttrReleasePolicy attrReleasepolicy = entityFactory.newEntity(AttrReleasePolicy.class);
+        attrReleasepolicy.setName("AttrReleasePolicyAllowEverything");
+        attrReleasepolicy.setDescription("This is a sample attr release policy that releases everything");
+
+        AllowedAttrReleasePolicyConf attrReleasePolicyConf = new AllowedAttrReleasePolicyConf();
+        attrReleasePolicyConf.getAllowedAttributes().addAll(List.of("*"));
+        attrReleasePolicyConf.setName("AttrReleasePolicyAllowEverything");
+
+        Implementation attrReleasePolicyType = entityFactory.newEntity(Implementation.class);
+        attrReleasePolicyType.setKey("AttrReleasePolicyAllowEverything");
+        attrReleasePolicyType.setEngine(ImplementationEngine.JAVA);
+        attrReleasePolicyType.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        attrReleasePolicyType.setBody(POJOHelper.serialize(attrReleasePolicyConf));
+        attrReleasePolicyType = implementationDAO.save(attrReleasePolicyType);
+
+        attrReleasepolicy.setConfiguration(attrReleasePolicyType);
+        attrReleasepolicy = policyDAO.save(attrReleasepolicy);
+
+        assertNotNull(attrReleasepolicy);
+        assertNotNull(attrReleasepolicy.getKey());
+
+        afterCount = policyDAO.findAll().size();
+        assertEquals(afterCount, beforeCount + 1);
+
     }
 
     @Test
@@ -185,5 +319,23 @@ public class PolicyTest extends AbstractTest {
 
         Policy actual = policyDAO.find("66691e96-285f-4464-bc19-e68384ea4c85");
         assertNull(actual);
+
+        AccessPolicy accessPolicy = policyDAO.find("419935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(accessPolicy);
+        policyDAO.delete(accessPolicy);
+        accessPolicy = policyDAO.find("419935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNull(accessPolicy);
+
+        AuthPolicy authPolicy = policyDAO.find("b912a0d4-a890-416f-9ab8-84ab077eb028");
+        assertNotNull(authPolicy);
+        policyDAO.delete(authPolicy);
+        authPolicy = policyDAO.find("b912a0d4-a890-416f-9ab8-84ab077eb028");
+        assertNull(authPolicy);
+
+        AttrReleasePolicy attrReleasepolicy = policyDAO.find("319935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(attrReleasepolicy);
+        policyDAO.delete(attrReleasepolicy);
+        attrReleasepolicy = policyDAO.find("319935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNull(attrReleasepolicy);
     }
 }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PolicyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PolicyTest.java
new file mode 100644
index 0000000..b9e73e1
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/outer/PolicyTest.java
@@ -0,0 +1,122 @@
+/*
+ * 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.outer;
+
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.core.persistence.api.dao.RealmDAO;
+import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+import org.apache.syncope.core.persistence.jpa.inner.AbstractClientAppTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import javax.persistence.PersistenceException;
+import java.util.UUID;
+import org.apache.syncope.core.persistence.api.dao.auth.OIDCRPDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.OIDCRP;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@Transactional("Master")
+public class PolicyTest extends AbstractClientAppTest {
+
+    @Autowired
+    private OIDCRPDAO oidcRelyingPartyDAO;
+
+    @Autowired
+    private RealmDAO realmDAO;
+
+    @Test
+    public void policyCannotBeRemovedForApps() {
+        // Create new policy
+        AccessPolicy accessPolicy = buildAndSaveAccessPolicy();
+        AuthPolicy authPolicy = buildAndSaveAuthPolicy();
+
+        // Create new client app and assign policy
+        OIDCRP rp = entityFactory.newEntity(OIDCRP.class);
+        rp.setName("OIDC");
+        rp.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+        rp.setDescription("This is a sample OIDC RP");
+        rp.setClientId(UUID.randomUUID().toString());
+        rp.setClientSecret("secret");
+        rp.setAccessPolicy(accessPolicy);
+        rp.setAuthPolicy(authPolicy);
+
+        rp = oidcRelyingPartyDAO.save(rp);
+        assertNotNull(rp);
+
+        assertThrows(PersistenceException.class, () -> {
+            this.policyDAO.delete(accessPolicy);
+            entityManager().flush();
+        });
+        assertThrows(PersistenceException.class, () -> {
+            this.policyDAO.delete(authPolicy);
+            entityManager().flush();
+        });
+    }
+
+    @Test
+    public void authPolicyCanBeNull() {
+        Realm realm = realmDAO.findByFullPath("/odd");
+        assertNotNull(realm);
+
+        // Create new client app and assign policy
+        OIDCRP rp = entityFactory.newEntity(OIDCRP.class);
+        rp.setName("OIDC");
+        rp.setClientAppId(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE);
+        rp.setDescription("This is a sample OIDC RP");
+        rp.setClientId(UUID.randomUUID().toString());
+        rp.setClientSecret("secret");
+        rp.setRealm(realm);
+
+        assertDoesNotThrow(() -> {
+            oidcRelyingPartyDAO.save(rp);
+            entityManager().flush();
+        });
+    }
+
+    @Test
+    public void policyForRealmsCanBeRemoved() {
+        AuthPolicy authPolicy = buildAndSaveAuthPolicy();
+        AccessPolicy accessPolicy = buildAndSaveAccessPolicy();
+        AttrReleasePolicy attrPolicy = buildAndSaveAttrRelPolicy();
+
+        Realm realm = realmDAO.findByFullPath(SyncopeConstants.ROOT_REALM);
+        assertNotNull(realm);
+        realm.setAuthPolicy(authPolicy);
+        realm.setAccessPolicy(accessPolicy);
+        realm.setAttrReleasePolicy(attrPolicy);
+        realm = realmDAO.save(realm);
+
+        assertNotNull(realm);
+
+        this.policyDAO.delete(authPolicy);
+        this.policyDAO.delete(accessPolicy);
+        this.policyDAO.delete(attrPolicy);
+        entityManager().flush();
+        assertNull(this.policyDAO.find(authPolicy.getKey()));
+        assertNull(this.policyDAO.find(accessPolicy.getKey()));
+        assertNull(this.policyDAO.find(attrPolicy.getKey()));
+    }
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
index f4fbe76..01093b2 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/PolicyDataBinderImpl.java
@@ -18,13 +18,14 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
-import java.util.stream.Collectors;
-import org.apache.syncope.core.provisioning.api.data.PolicyDataBinder;
-import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
 import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
+import org.apache.syncope.common.lib.policy.PolicyTO;
 import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
+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.core.persistence.api.dao.AnyTypeDAO;
 import org.apache.syncope.core.persistence.api.dao.ExternalResourceDAO;
 import org.apache.syncope.core.persistence.api.dao.ImplementationDAO;
@@ -32,22 +33,28 @@ import org.apache.syncope.core.persistence.api.dao.NotFoundException;
 import org.apache.syncope.core.persistence.api.dao.RealmDAO;
 import org.apache.syncope.core.persistence.api.entity.AnyType;
 import org.apache.syncope.core.persistence.api.entity.Entity;
-import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
-import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
-import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.Realm;
+import org.apache.syncope.core.persistence.api.entity.policy.AccessPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AccountPolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.AttrReleasePolicy;
+import org.apache.syncope.core.persistence.api.entity.policy.PasswordPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.Policy;
 import org.apache.syncope.core.persistence.api.entity.policy.PullCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PullPolicy;
 import org.apache.syncope.core.persistence.api.entity.policy.PushCorrelationRuleEntity;
 import org.apache.syncope.core.persistence.api.entity.policy.PushPolicy;
+import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
+import org.apache.syncope.core.provisioning.api.data.PolicyDataBinder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import java.util.stream.Collectors;
+import org.apache.syncope.core.persistence.api.entity.policy.AuthPolicy;
+
 @Component
 public class PolicyDataBinderImpl implements PolicyDataBinder {
 
@@ -192,6 +199,54 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
             // remove all rules not contained in the TO
             pushPolicy.getCorrelationRules().removeIf(anyFilter
                     -> !pushPolicyTO.getCorrelationRules().containsKey(anyFilter.getAnyType().getKey()));
+        } else if (policyTO instanceof AuthPolicyTO) {
+            if (result == null) {
+                result = (T) entityFactory.newEntity(AuthPolicy.class);
+            }
+
+            AuthPolicy authPolicy = AuthPolicy.class.cast(result);
+            AuthPolicyTO authPolicyTO = AuthPolicyTO.class.cast(policyTO);
+
+            authPolicy.setName(authPolicyTO.getKey());
+            Implementation configuration = implementationDAO.find(authPolicyTO.getConfiguration());
+            if (configuration == null) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...",
+                        authPolicyTO.getConfiguration());
+            } else {
+                authPolicy.setConfiguration(configuration);
+            }
+        } else if (policyTO instanceof AccessPolicyTO) {
+            if (result == null) {
+                result = (T) entityFactory.newEntity(AccessPolicy.class);
+            }
+
+            AccessPolicy accessPolicy = AccessPolicy.class.cast(result);
+            AccessPolicyTO accessPolicyTO = AccessPolicyTO.class.cast(policyTO);
+
+            accessPolicy.setName(accessPolicyTO.getKey());
+            Implementation configuration = implementationDAO.find(accessPolicyTO.getConfiguration());
+            if (configuration == null) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...",
+                        accessPolicyTO.getConfiguration());
+            } else {
+                accessPolicy.setConfiguration(configuration);
+            }
+        } else if (policyTO instanceof AttrReleasePolicyTO) {
+            if (result == null) {
+                result = (T) entityFactory.newEntity(AttrReleasePolicy.class);
+            }
+
+            AttrReleasePolicy attrReleasePolicy = AttrReleasePolicy.class.cast(result);
+            AttrReleasePolicyTO attrReleasePolicyTO = AttrReleasePolicyTO.class.cast(policyTO);
+
+            attrReleasePolicy.setName(attrReleasePolicyTO.getKey());
+            Implementation configuration = implementationDAO.find(attrReleasePolicyTO.getConfiguration());
+            if (configuration == null) {
+                LOG.debug("Invalid " + Implementation.class.getSimpleName() + " {}, ignoring...",
+                        attrReleasePolicyTO.getConfiguration());
+            } else {
+                attrReleasePolicy.setConfiguration(configuration);
+            }
         }
 
         if (result != null) {
@@ -257,14 +312,33 @@ public class PolicyDataBinderImpl implements PolicyDataBinder {
             pushPolicy.getCorrelationRules().
                     forEach(rule -> pushPolicyTO.getCorrelationRules().
                     put(rule.getAnyType().getKey(), rule.getImplementation().getKey()));
+        } else if (policy instanceof AuthPolicy) {
+            AuthPolicyTO authPolicyTO = new AuthPolicyTO();
+            authPolicyTO.setConfiguration(((AuthPolicy) policy).getConfiguration() == null
+                    ? null : ((AuthPolicy) policy).getConfiguration().getKey());
+            policyTO = (T) authPolicyTO;
+        } else if (policy instanceof AccessPolicy) {
+            AccessPolicyTO accessPolicyTO = new AccessPolicyTO();
+            accessPolicyTO.setConfiguration(((AccessPolicy) policy).getConfiguration() == null
+                    ? null : ((AccessPolicy) policy).getConfiguration().getKey());
+            policyTO = (T) accessPolicyTO;
+        } else if (policy instanceof AttrReleasePolicy) {
+            AttrReleasePolicyTO attrReleasePolicyTO = new AttrReleasePolicyTO();
+            attrReleasePolicyTO.setConfiguration(((AttrReleasePolicy) policy).getConfiguration() == null
+                    ? null : ((AttrReleasePolicy) policy).getConfiguration().getKey());
+            policyTO = (T) attrReleasePolicyTO;
         }
 
         if (policyTO != null) {
             policyTO.setKey(policy.getKey());
             policyTO.setDescription(policy.getDescription());
 
-            for (ExternalResource resource : resourceDAO.findByPolicy(policy)) {
-                policyTO.getUsedByResources().add(resource.getKey());
+            if (!(policy instanceof AuthPolicy)
+                    && !(policy instanceof AccessPolicy)
+                    && !(policy instanceof AttrReleasePolicy)) {
+                for (ExternalResource resource : resourceDAO.findByPolicy(policy)) {
+                    policyTO.getUsedByResources().add(resource.getKey());
+                }
             }
             for (Realm realm : realmDAO.findByPolicy(policy)) {
                 policyTO.getUsedByRealms().add(realm.getFullPath());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
index 83ecf39..4b65c75 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
@@ -20,40 +20,147 @@ package org.apache.syncope.fit.core;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.fail;
 
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
-import java.util.Set;
-import javax.ws.rs.core.Response;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
+import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
 import org.apache.syncope.common.lib.policy.AccountPolicyTO;
-import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
-import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.DefaultAccountRuleConf;
-import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.policy.DefaultPasswordRuleConf;
+import org.apache.syncope.common.lib.policy.PasswordPolicyTO;
+import org.apache.syncope.common.lib.policy.PullPolicyTO;
 import org.apache.syncope.common.lib.policy.PushPolicyTO;
-import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.syncope.common.lib.to.AccessPolicyTO;
+import org.apache.syncope.common.lib.to.AttrReleasePolicyTO;
 import org.apache.syncope.common.lib.to.ImplementationTO;
+import org.apache.syncope.common.lib.types.AMImplementationType;
+import org.apache.syncope.common.lib.types.AnyTypeKind;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
+import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.rest.api.RESTHeaders;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 import org.apache.syncope.fit.AbstractITCase;
 import org.apache.syncope.fit.core.reference.DummyPullCorrelationRule;
 import org.apache.syncope.fit.core.reference.DummyPushCorrelationRule;
 import org.junit.jupiter.api.Test;
+import org.junit.platform.commons.util.StringUtils;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.to.AuthPolicyTO;
 
 public class PolicyITCase extends AbstractITCase {
 
+    private static AuthPolicyTO buildAuthPolicyTO() {
+        String authPolicyName = "TestAuthPolicy" + getUUIDString();
+        ImplementationTO implementationTO = null;
+        try {
+            implementationTO = implementationService.read(
+                    AMImplementationType.AUTH_POLICY_CONFIGURATIONS, authPolicyName);
+        } catch (SyncopeClientException e) {
+            if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
+                implementationTO = new ImplementationTO();
+                implementationTO.setKey(authPolicyName);
+                implementationTO.setEngine(ImplementationEngine.JAVA);
+                implementationTO.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+
+                DefaultAuthPolicyConf conf = new DefaultAuthPolicyConf();
+                conf.getAuthModules().addAll(List.of("LdapAuthentication1"));
+                implementationTO.setBody(POJOHelper.serialize(conf));
+
+                Response response = implementationService.create(implementationTO);
+                implementationTO = implementationService.read(
+                        implementationTO.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                assertNotNull(implementationTO);
+            }
+        }
+        assertNotNull(implementationTO);
+
+        AuthPolicyTO policy = new AuthPolicyTO();
+        policy.setDescription("Test Authentication policy");
+        policy.setKey(implementationTO.getKey());
+
+        return policy;
+    }
+
+    private static AttrReleasePolicyTO buildAttributeReleasePolicyTO(final String policyName) {
+        ImplementationTO implementationTO = null;
+        try {
+            implementationTO = implementationService.read(
+                    AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS, policyName);
+        } catch (SyncopeClientException e) {
+            if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
+                implementationTO = new ImplementationTO();
+                implementationTO.setKey(policyName);
+                implementationTO.setEngine(ImplementationEngine.JAVA);
+                implementationTO.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+
+                AllowedAttrReleasePolicyConf conf = new AllowedAttrReleasePolicyConf();
+                conf.setName("MyDefaultAttrReleasePolicyConf");
+                conf.getAllowedAttributes().addAll(List.of("cn", "givenName"));
+                implementationTO.setBody(POJOHelper.serialize(conf));
+
+                Response response = implementationService.create(implementationTO);
+                implementationTO = implementationService.read(
+                        implementationTO.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                assertNotNull(implementationTO);
+            }
+        }
+        assertNotNull(implementationTO);
+
+        AttrReleasePolicyTO policy = new AttrReleasePolicyTO();
+        policy.setDescription("Test Attribute Release policy");
+        policy.setKey(implementationTO.getKey());
+
+        return policy;
+    }
+
+    private static AccessPolicyTO buildAccessPolicyTO() {
+        final String accessPolicyName = "TestAccessPolicy" + getUUIDString();
+
+        ImplementationTO implementationTO = null;
+        try {
+            implementationTO = implementationService.read(
+                    AMImplementationType.ACCESS_POLICY_CONFIGURATIONS, accessPolicyName);
+        } catch (SyncopeClientException e) {
+            if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
+                implementationTO = new ImplementationTO();
+                implementationTO.setKey(accessPolicyName);
+                implementationTO.setEngine(ImplementationEngine.JAVA);
+                implementationTO.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+
+                DefaultAccessPolicyConf conf = new DefaultAccessPolicyConf();
+                conf.setEnabled(true);
+                conf.setName("TestAccessPolicyConf");
+                conf.getRequiredAttributes().put("cn", Set.of("admin", "Admin", "TheAdmin"));
+                implementationTO.setBody(POJOHelper.serialize(conf));
+
+                Response response = implementationService.create(implementationTO);
+                implementationTO = implementationService.read(
+                        implementationTO.getType(), response.getHeaderString(RESTHeaders.RESOURCE_KEY));
+                assertNotNull(implementationTO);
+            }
+        }
+        assertNotNull(implementationTO);
+
+        AccessPolicyTO policy = new AccessPolicyTO();
+        policy.setDescription("Test Access policy");
+        policy.setKey(implementationTO.getKey());
+
+        return policy;
+    }
+
     private PullPolicyTO buildPullPolicyTO() throws IOException {
         ImplementationTO corrRule = null;
         try {
@@ -143,6 +250,33 @@ public class PolicyITCase extends AbstractITCase {
     }
 
     @Test
+    public void getAuthPolicy() {
+        AuthPolicyTO policyTO =
+                policyService.read(PolicyType.AUTH, "659b9906-4b6e-4bc0-aca0-6809dff346d4");
+
+        assertNotNull(policyTO);
+        assertTrue(policyTO.getUsedByRealms().isEmpty());
+    }
+
+    @Test
+    public void getAccessPolicy() {
+        AccessPolicyTO policyTO =
+                policyService.read(PolicyType.ACCESS, "419935c7-deb3-40b3-8a9a-683037e523a2");
+
+        assertNotNull(policyTO);
+        assertTrue(policyTO.getUsedByRealms().isEmpty());
+    }
+
+    @Test
+    public void getAttributeReleasePolicy() {
+        AttrReleasePolicyTO policyTO =
+                policyService.read(PolicyType.ATTR_RELEASE, "319935c7-deb3-40b3-8a9a-683037e523a2");
+
+        assertNotNull(policyTO);
+        assertTrue(policyTO.getUsedByRealms().isEmpty());
+    }
+
+    @Test
     public void create() throws IOException {
         PullPolicyTO pullPolicyTO = createPolicy(PolicyType.PULL, buildPullPolicyTO());
         assertNotNull(pullPolicyTO);
@@ -151,10 +285,19 @@ public class PolicyITCase extends AbstractITCase {
         PushPolicyTO pushPolicyTO = createPolicy(PolicyType.PUSH, buildPushPolicyTO());
         assertNotNull(pushPolicyTO);
         assertEquals("TestPushRule", pushPolicyTO.getCorrelationRules().get(AnyTypeKind.USER.name()));
+
+        AuthPolicyTO authPolicyTO = createPolicy(PolicyType.AUTH,
+                buildAuthPolicyTO());
+        assertNotNull(authPolicyTO);
+        assertEquals("Test Authentication policy", authPolicyTO.getDescription());
+
+        AccessPolicyTO accessPolicyTO = createPolicy(PolicyType.ACCESS, buildAccessPolicyTO());
+        assertNotNull(accessPolicyTO);
+        assertEquals("Test Access policy", accessPolicyTO.getDescription());
     }
 
     @Test
-    public void update() {
+    public void updatePasswordPolicy() {
         PasswordPolicyTO globalPolicy = policyService.read(PolicyType.PASSWORD, "ce93fcda-dc3a-4369-a7b0-a6108c261c85");
 
         PasswordPolicyTO policy = SerializationUtils.clone(globalPolicy);
@@ -184,6 +327,104 @@ public class PolicyITCase extends AbstractITCase {
     }
 
     @Test
+    public void updateAuthPolicy() {
+        AuthPolicyTO newAuthPolicyTO = buildAuthPolicyTO();
+        assertNotNull(newAuthPolicyTO);
+        newAuthPolicyTO = createPolicy(PolicyType.AUTH, newAuthPolicyTO);
+
+        ImplementationTO authPolicyImplementationTO = implementationService.read(
+                AMImplementationType.AUTH_POLICY_CONFIGURATIONS, "MyDefaultAuthPolicyConf");
+        assertNotNull(authPolicyImplementationTO);
+        assertFalse(StringUtils.isBlank(authPolicyImplementationTO.getBody()));
+
+        DefaultAuthPolicyConf authPolicyConf =
+                POJOHelper.deserialize(authPolicyImplementationTO.getBody(), DefaultAuthPolicyConf.class);
+        assertNotNull(authPolicyConf);
+        authPolicyConf.getAuthModules().add("LdapAuthentication");
+        authPolicyImplementationTO.setBody(POJOHelper.serialize(authPolicyConf));
+
+        // update new authentication policy
+        policyService.update(PolicyType.AUTH, newAuthPolicyTO);
+        newAuthPolicyTO = policyService.read(PolicyType.AUTH, newAuthPolicyTO.getKey());
+        assertNotNull(newAuthPolicyTO);
+
+        authPolicyConf = POJOHelper.deserialize(authPolicyImplementationTO.getBody(),
+                DefaultAuthPolicyConf.class);
+        assertNotNull(authPolicyConf);
+        assertEquals(2, authPolicyConf.getAuthModules().size());
+        assertTrue(authPolicyConf.getAuthModules().contains("LdapAuthentication"));
+    }
+
+    @Test
+    public void updateAccessPolicy() {
+        AccessPolicyTO globalAccessPolicyTO =
+                policyService.read(PolicyType.ACCESS, "419935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(globalAccessPolicyTO);
+
+        AccessPolicyTO newAccessPolicyTO = buildAccessPolicyTO();
+        newAccessPolicyTO = createPolicy(PolicyType.ACCESS, newAccessPolicyTO);
+        assertNotNull(newAccessPolicyTO);
+
+        ImplementationTO accessPolicyImplementationTO = implementationService.read(
+                AMImplementationType.ACCESS_POLICY_CONFIGURATIONS, "MyDefaultAccessPolicyConf");
+        assertNotNull(accessPolicyImplementationTO);
+        assertFalse(StringUtils.isBlank(accessPolicyImplementationTO.getBody()));
+
+        DefaultAccessPolicyConf accessPolicyConf =
+                POJOHelper.deserialize(accessPolicyImplementationTO.getBody(), DefaultAccessPolicyConf.class);
+        assertNotNull(accessPolicyConf);
+        accessPolicyConf.getRequiredAttributes().put("ou", Set.of("test"));
+        accessPolicyConf.getRequiredAttributes().put("cn", Set.of("admin", "Admin"));
+        accessPolicyImplementationTO.setBody(POJOHelper.serialize(accessPolicyConf));
+
+        // update new authentication policy
+        policyService.update(PolicyType.ACCESS, newAccessPolicyTO);
+        newAccessPolicyTO = policyService.read(PolicyType.ACCESS, newAccessPolicyTO.getKey());
+        assertNotNull(newAccessPolicyTO);
+
+        accessPolicyConf = POJOHelper.deserialize(accessPolicyImplementationTO.getBody(), DefaultAccessPolicyConf.class);
+        assertEquals(2, accessPolicyConf.getRequiredAttributes().size());
+        assertNotNull(accessPolicyConf.getRequiredAttributes().get("cn"));
+        assertNotNull(accessPolicyConf.getRequiredAttributes().get("ou"));
+
+    }
+
+    @Test
+    public void updateAttrReleasePolicy() {
+        AttrReleasePolicyTO policyTO =
+                policyService.read(PolicyType.ATTR_RELEASE, "319935c7-deb3-40b3-8a9a-683037e523a2");
+        assertNotNull(policyTO);
+
+        final String policyName = "TestAttrReleasePolicy" + getUUIDString();
+        AttrReleasePolicyTO newPolicyTO = buildAttributeReleasePolicyTO(policyName);
+        newPolicyTO = createPolicy(PolicyType.ATTR_RELEASE, newPolicyTO);
+        assertNotNull(newPolicyTO);
+
+        ImplementationTO implementationTO = implementationService.read(
+                AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS, policyName);
+        assertNotNull(implementationTO);
+        assertFalse(StringUtils.isBlank(implementationTO.getBody()));
+
+        AllowedAttrReleasePolicyConf policyConf =
+                POJOHelper.deserialize(implementationTO.getBody(), AllowedAttrReleasePolicyConf.class);
+        assertNotNull(policyConf);
+        policyConf.getAllowedAttributes().add("postalCode");
+        implementationTO.setBody(POJOHelper.serialize(policyConf));
+
+        // update new policy
+        policyService.update(PolicyType.ATTR_RELEASE, newPolicyTO);
+        newPolicyTO = policyService.read(PolicyType.ATTR_RELEASE, newPolicyTO.getKey());
+        assertNotNull(newPolicyTO);
+
+        policyConf = POJOHelper.deserialize(implementationTO.getBody(), AllowedAttrReleasePolicyConf.class);
+        assertEquals(3, policyConf.getAllowedAttributes().size());
+        assertTrue(policyConf.getAllowedAttributes().contains("cn"));
+        assertTrue(policyConf.getAllowedAttributes().contains("postalCode"));
+        assertTrue(policyConf.getAllowedAttributes().contains("givenName"));
+
+    }
+
+    @Test
     public void delete() throws IOException {
         PullPolicyTO policy = buildPullPolicyTO();
 
@@ -198,6 +439,34 @@ public class PolicyITCase extends AbstractITCase {
         } catch (SyncopeClientException e) {
             assertNotNull(e);
         }
+
+        AuthPolicyTO authPolicy = buildAuthPolicyTO();
+
+        AuthPolicyTO authPolicyTO = createPolicy(PolicyType.AUTH, authPolicy);
+        assertNotNull(authPolicyTO);
+
+        policyService.delete(PolicyType.AUTH, authPolicyTO.getKey());
+
+        try {
+            policyService.read(PolicyType.AUTH, authPolicyTO.getKey());
+            fail("This should not happen");
+        } catch (SyncopeClientException e) {
+            assertNotNull(e);
+        }
+
+        AccessPolicyTO accessPolicy = buildAccessPolicyTO();
+
+        AccessPolicyTO accessPolicyTO = createPolicy(PolicyType.ACCESS, accessPolicy);
+        assertNotNull(accessPolicyTO);
+
+        policyService.delete(PolicyType.ACCESS, accessPolicyTO.getKey());
+
+        try {
+            policyService.read(PolicyType.ACCESS, accessPolicyTO.getKey());
+            fail("This should not happen");
+        } catch (SyncopeClientException e) {
+            assertNotNull(e);
+        }
     }
 
     @Test


[syncope] 08/08: [SYNCOPE-160] Adding missing support for AuthModule items

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit f7e75bbe77d44d1c0a6b2aaa83001d920c757ffc
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Fri Apr 10 09:18:41 2020 +0200

    [SYNCOPE-160] Adding missing support for AuthModule items
---
 .../commons/IdMImplementationInfoProvider.java     |   8 +-
 .../resources/ItemTransformersTogglePanel.java     |   4 +-
 .../commons/IdRepoImplementationInfoProvider.java  |   4 +
 .../apache/syncope/common/lib/to/AuthModuleTO.java |  26 ++---
 .../common/lib/types/AMImplementationType.java     |  15 ++-
 .../common/rest/api/service/AuthModuleService.java |  12 +--
 .../common/lib/types/IdMImplementationType.java    |  13 +--
 .../common/lib/types/IdRepoImplementationType.java |   5 +-
 .../init/ClassPathScanImplementationLookup.java    |   4 +-
 .../syncope/core/logic/ImplementationLogic.java    |   2 +-
 .../init/ClassPathScanImplementationLookup.java    |   2 +-
 .../persistence/api/entity/auth/AuthModule.java    |  14 +--
 .../api/entity/auth/AuthModuleItem.java            |   3 +
 .../src/test/resources/domains/MasterContent.xml   |   8 +-
 .../persistence/jpa/dao/auth/JPAAuthModuleDAO.java |  16 ++--
 .../persistence/jpa/entity/auth/JPAAuthModule.java |  18 ++--
 .../jpa/entity/auth/JPAAuthModuleItem.java         |  31 +++++-
 .../jpa/entity/policy/JPAAccessPolicy.java         |   2 +-
 .../jpa/entity/policy/JPAAttrReleasePolicy.java    |   2 +-
 .../jpa/entity/policy/JPAAuthPolicy.java           |   2 +-
 .../jpa/entity/resource/JPAMappingItem.java        |   4 +-
 .../jpa/entity/resource/JPAOrgUnitItem.java        |   4 +-
 .../jpa/inner/AbstractClientAppTest.java           |   6 +-
 .../core/persistence/jpa/inner/AuthModuleTest.java |  44 ++++-----
 .../persistence/jpa/inner/ImplementationTest.java  |   4 +-
 .../core/persistence/jpa/inner/PolicyTest.java     |   6 +-
 .../src/test/resources/domains/MasterContent.xml   |   8 +-
 .../java/data/AuthModuleDataBinderImpl.java        |  99 +++++++++++++++----
 .../java/data/ImplementationDataBinderImpl.java    |  14 +--
 .../jpa/entity/JPAOIDCProviderItem.java            |   4 +-
 .../java/data/OIDCProviderDataBinderImpl.java      | 105 +++++++--------------
 .../core/persistence/jpa/dao/JPASAML2IdPDAO.java   |   1 -
 .../persistence/jpa/entity/JPASAML2IdPItem.java    |   4 +-
 .../java/data/SAML2IdPDataBinderImpl.java          | 104 +++++++-------------
 .../fit/core/reference/ITImplementationLookup.java |   2 +-
 .../apache/syncope/fit/core/AuthModuleITCase.java  |  35 +++----
 .../org/apache/syncope/fit/core/PolicyITCase.java  |  24 ++---
 .../syncope/fit/core/PropagationTaskITCase.java    |  10 +-
 .../apache/syncope/fit/core/PullTaskITCase.java    |   5 +-
 .../org/apache/syncope/fit/core/RealmITCase.java   |   6 +-
 .../view/ResourceExplorerTopComponent.java         |   2 +-
 41 files changed, 332 insertions(+), 350 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
index 174c813..3a7196e 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/IdMImplementationInfoProvider.java
@@ -71,10 +71,6 @@ public class IdMImplementationInfoProvider extends IdRepoImplementationInfoProvi
         String templateClassName;
 
         switch (implementationType) {
-            case IdMImplementationType.ITEM_TRANSFORMER:
-                templateClassName = "MyItemTransformer";
-                break;
-
             case IdMImplementationType.RECON_FILTER_BUILDER:
                 templateClassName = "MyReconFilterBuilder";
                 break;
@@ -100,9 +96,9 @@ public class IdMImplementationInfoProvider extends IdRepoImplementationInfoProvi
                 break;
 
             case IdMImplementationType.PROVISION_SORTER:
-                templateClassName = "MyProvisionSorter";                
+                templateClassName = "MyProvisionSorter";
                 break;
-                
+
             default:
                 templateClassName = super.getGroovyTemplateClassName(implementationType);
         }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java
index 912174d..bf77409 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ItemTransformersTogglePanel.java
@@ -28,7 +28,7 @@ import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxPalettePanel;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.ItemTO;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
@@ -64,7 +64,7 @@ public class ItemTransformersTogglePanel extends TogglePanel<Serializable> {
         Form<?> form = new Form<>("form");
         addInnerObject(form);
 
-        List<String> choices = ImplementationRestClient.list(IdMImplementationType.ITEM_TRANSFORMER).stream().
+        List<String> choices = ImplementationRestClient.list(IdRepoImplementationType.ITEM_TRANSFORMER).stream().
                 map(EntityTO::getKey).sorted().collect(Collectors.toList());
 
         form.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).setRenderer(new IChoiceRenderer<String>() {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
index ea08367..6b4d2f1 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/commons/IdRepoImplementationInfoProvider.java
@@ -114,6 +114,10 @@ public class IdRepoImplementationInfoProvider implements ImplementationInfoProvi
                 templateClassName = "MyRecipientsProvider";
                 break;
 
+            case IdRepoImplementationType.ITEM_TRANSFORMER:
+                templateClassName = "MyItemTransformer";
+                break;
+
             default:
         }
 
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
index 492227f..3f499bd 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.annotation.JsonProperty;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import javax.ws.rs.PathParam;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import org.apache.commons.lang3.builder.EqualsBuilder;
@@ -43,15 +44,16 @@ public class AuthModuleTO extends BaseBean implements EntityTO {
 
     private String description;
 
-    private final List<ItemTO> profileItems = new ArrayList<>();
-
     private AuthModuleConf conf;
 
+    private final List<ItemTO> items = new ArrayList<>();
+
     @Override
     public String getKey() {
         return key;
     }
 
+    @PathParam("key")
     @Override
     public void setKey(final String key) {
         this.key = key;
@@ -81,20 +83,19 @@ public class AuthModuleTO extends BaseBean implements EntityTO {
         this.conf = conf;
     }
 
-    @XmlElementWrapper(name = "profileItems")
-    @XmlElement(name = "profileItem")
-    @JsonProperty("profileItems")
-    public List<ItemTO> getProfileItems() {
-        return profileItems;
+    @XmlElementWrapper(name = "items")
+    @XmlElement(name = "item")
+    @JsonProperty("items")
+    public List<ItemTO> getItems() {
+        return items;
     }
 
     public boolean add(final ItemTO item) {
-        return Optional.ofNullable(item)
-                .filter(itemTO -> this.profileItems.contains(itemTO) || this.profileItems.add(itemTO)).isPresent();
+        return Optional.ofNullable(item).filter(itemTO -> items.contains(itemTO) || items.add(itemTO)).isPresent();
     }
 
     public boolean remove(final ItemTO item) {
-        return this.profileItems.remove(item);
+        return this.items.remove(item);
     }
 
     @Override
@@ -113,7 +114,7 @@ public class AuthModuleTO extends BaseBean implements EntityTO {
                 append(key, other.key).
                 append(name, other.name).
                 append(description, other.description).
-                append(profileItems, other.profileItems).
+                append(items, other.items).
                 append(conf, other.conf).
                 build();
     }
@@ -124,9 +125,8 @@ public class AuthModuleTO extends BaseBean implements EntityTO {
                 append(key).
                 append(name).
                 append(description).
-                append(profileItems).
+                append(items).
                 append(conf).
                 build();
     }
-
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java
index 1c05727..1a6a7a0 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/types/AMImplementationType.java
@@ -23,23 +23,20 @@ import java.util.Map;
 
 public final class AMImplementationType {
 
-    public static final String AUTH_POLICY_CONFIGURATIONS = "AUTH_POLICY_CONFIGURATIONS";
+    public static final String AUTH_POLICY_CONF = "AUTH_POLICY_CONF";
 
-    public static final String ACCESS_POLICY_CONFIGURATIONS = "ACCESS_POLICY_CONFIGURATIONS";
+    public static final String ACCESS_POLICY_CONF = "ACCESS_POLICY_CONF";
 
-    public static final String ATTR_RELEASE_POLICY_CONFIGURATIONS = "ATTR_RELEASE_CONFIGURATIONS";
+    public static final String ATTR_RELEASE_POLICY_CONF = "ATTR_RELEASE_POLICY_CONF";
 
     private AMImplementationType() {
         // private constructor for static utility class
     }
 
     private static final Map<String, String> VALUES = Map.ofEntries(
-            Pair.of(AUTH_POLICY_CONFIGURATIONS,
-                    "org.apache.syncope.common.lib.policy.AuthPolicyConf"),
-            Pair.of(ATTR_RELEASE_POLICY_CONFIGURATIONS,
-                    "org.apache.syncope.common.lib.policy.AttrReleasePolicyConf"),
-            Pair.of(ACCESS_POLICY_CONFIGURATIONS,
-                    "org.apache.syncope.common.lib.policy.AccessPolicyConf"));
+            Pair.of(AUTH_POLICY_CONF, "org.apache.syncope.common.lib.policy.AuthPolicyConf"),
+            Pair.of(ACCESS_POLICY_CONF, "org.apache.syncope.common.lib.policy.AccessPolicyConf"),
+            Pair.of(ATTR_RELEASE_POLICY_CONF, "org.apache.syncope.common.lib.policy.AttrReleasePolicyConf"));
 
     public static Map<String, String> values() {
         return VALUES;
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
index 83e6015..d617b42 100644
--- a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
@@ -62,8 +62,7 @@ public interface AuthModuleService extends JAXRSService {
     @GET
     @Path("{key}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    AuthModuleTO read(
-            @NotNull @PathParam("key") String key);
+    AuthModuleTO read(@NotNull @PathParam("key") String key);
 
     /**
      * Returns a list of authentication modules of the matching type.
@@ -92,8 +91,7 @@ public interface AuthModuleService extends JAXRSService {
     @POST
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    Response create(
-            @NotNull AuthModuleTO authModuleTO);
+    Response create(@NotNull AuthModuleTO authModuleTO);
 
     /**
      * Updates authentication module matching the given key.
@@ -108,8 +106,7 @@ public interface AuthModuleService extends JAXRSService {
     @Path("{key}")
     @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    void update(
-            @NotNull AuthModuleTO authModuleTO);
+    void update(@NotNull AuthModuleTO authModuleTO);
 
     /**
      * Delete authentication module matching the given key.
@@ -121,6 +118,5 @@ public interface AuthModuleService extends JAXRSService {
     @DELETE
     @Path("{key}")
     @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
-    void delete(
-            @NotNull @PathParam("key") String key);
+    void delete(@NotNull @PathParam("key") String key);
 }
diff --git a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java
index a3aff0a..37765b2 100644
--- a/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java
+++ b/common/idm/lib/src/main/java/org/apache/syncope/common/lib/types/IdMImplementationType.java
@@ -23,8 +23,6 @@ import org.apache.commons.lang3.tuple.Pair;
 
 public final class IdMImplementationType {
 
-    public static final String ITEM_TRANSFORMER = "ITEM_TRANSFORMER";
-
     public static final String RECON_FILTER_BUILDER = "RECON_FILTER_BUILDER";
 
     public static final String PROPAGATION_ACTIONS = "PROPAGATION_ACTIONS";
@@ -40,13 +38,12 @@ public final class IdMImplementationType {
     public static final String PROVISION_SORTER = "PROVISION_SORTER";
 
     private static final Map<String, String> VALUES = Map.ofEntries(
-            Pair.of(ITEM_TRANSFORMER, "org.apache.syncope.core.spring.security.JWTSSOProvider"),
             Pair.of(RECON_FILTER_BUILDER, "org.apache.syncope.core.persistence.api.dao.Reportlet"),
-            Pair.of(PROPAGATION_ACTIONS, "org.apache.syncope.core.persistence.api.dao.AccountRule"),
-            Pair.of(PULL_ACTIONS, "org.apache.syncope.core.persistence.api.dao.PasswordRule"),
-            Pair.of(PUSH_ACTIONS, "org.apache.syncope.core.provisioning.api.job.SchedTaskJobDelegate"),
-            Pair.of(PULL_CORRELATION_RULE, "org.apache.syncope.core.provisioning.api.LogicActions"),
-            Pair.of(PUSH_CORRELATION_RULE, "org.apache.syncope.core.persistence.api.attrvalue.validation.Validator"),
+            Pair.of(PROPAGATION_ACTIONS, "org.apache.syncope.core.provisioning.api.propagation.PropagationActions"),
+            Pair.of(PULL_ACTIONS, "org.apache.syncope.core.provisioning.api.pushpull.PullActions"),
+            Pair.of(PUSH_ACTIONS, "org.apache.syncope.core.provisioning.api.pushpull.PushActions"),
+            Pair.of(PULL_CORRELATION_RULE, "org.apache.syncope.common.lib.policy.PullCorrelationRuleConf"),
+            Pair.of(PUSH_CORRELATION_RULE, "org.apache.syncope.common.lib.policy.PushCorrelationRuleConf"),
             Pair.of(PROVISION_SORTER, "org.apache.syncope.core.provisioning.api.ProvisionSorter"));
 
     public static Map<String, String> values() {
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoImplementationType.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoImplementationType.java
index 013cb06..eb374be 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoImplementationType.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/IdRepoImplementationType.java
@@ -41,6 +41,8 @@ public final class IdRepoImplementationType {
 
     public static final String AUDIT_APPENDER = "AUDIT_APPENDER";
 
+    public static final String ITEM_TRANSFORMER = "ITEM_TRANSFORMER";
+
     private static final Map<String, String> VALUES = Map.ofEntries(
             Pair.of(JWT_SSO_PROVIDER, "org.apache.syncope.core.spring.security.JWTSSOProvider"),
             Pair.of(REPORTLET, "org.apache.syncope.core.persistence.api.dao.Reportlet"),
@@ -50,7 +52,8 @@ public final class IdRepoImplementationType {
             Pair.of(LOGIC_ACTIONS, "org.apache.syncope.core.provisioning.api.LogicActions"),
             Pair.of(VALIDATOR, "org.apache.syncope.core.persistence.api.attrvalue.validation.Validator"),
             Pair.of(RECIPIENTS_PROVIDER, "org.apache.syncope.core.provisioning.api.notification.RecipientsProvider"),
-            Pair.of(AUDIT_APPENDER, "org.apache.syncope.core.logic.audit.AuditAppender"));
+            Pair.of(AUDIT_APPENDER, "org.apache.syncope.core.logic.audit.AuditAppender"),
+            Pair.of(ITEM_TRANSFORMER, "org.apache.syncope.core.provisioning.api.data.ItemTransformer"));
 
     public static Map<String, String> values() {
         return VALUES;
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index e887aef..04f4463 100644
--- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -92,10 +92,10 @@ public class ClassPathScanImplementationLookup implements SyncopeCoreLoader {
                 boolean isAbstractClazz = Modifier.isAbstract(clazz.getModifiers());
 
                 if (AuthPolicyConf.class.isAssignableFrom(clazz) && !isAbstractClazz) {
-                    classNames.get(AMImplementationType.AUTH_POLICY_CONFIGURATIONS).add(bd.getBeanClassName());
+                    classNames.get(AMImplementationType.AUTH_POLICY_CONF).add(bd.getBeanClassName());
                 }
                 if (AccessPolicyConf.class.isAssignableFrom(clazz) && !isAbstractClazz) {
-                    classNames.get(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS).add(bd.getBeanClassName());
+                    classNames.get(AMImplementationType.ACCESS_POLICY_CONF).add(bd.getBeanClassName());
                 }
             } catch (Throwable t) {
                 LOG.warn("Could not inspect class {}", bd.getBeanClassName(), t);
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
index 40380cd..a2fd3d9 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/ImplementationLogic.java
@@ -179,7 +179,7 @@ public class ImplementationLogic extends AbstractTransactionalLogic<Implementati
                 inUse = !policyDAO.findByPasswordRule(implementation).isEmpty();
                 break;
 
-            case IdMImplementationType.ITEM_TRANSFORMER:
+            case IdRepoImplementationType.ITEM_TRANSFORMER:
                 inUse = !resourceDAO.findByTransformer(implementation).isEmpty();
                 break;
 
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
index 5baf2e5..6b626db 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/init/ClassPathScanImplementationLookup.java
@@ -194,7 +194,7 @@ public class ClassPathScanImplementationLookup implements ImplementationLookup {
                 if (ItemTransformer.class.isAssignableFrom(clazz) && !isAbstractClazz
                         && !clazz.equals(JEXLItemTransformerImpl.class)) {
 
-                    classNames.get(IdMImplementationType.ITEM_TRANSFORMER).add(clazz.getName());
+                    classNames.get(IdRepoImplementationType.ITEM_TRANSFORMER).add(clazz.getName());
                 }
 
                 if (SchedTaskJobDelegate.class.isAssignableFrom(clazz) && !isAbstractClazz
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
index 5d2393e..8ba362c 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
@@ -21,7 +21,6 @@ package org.apache.syncope.core.persistence.api.entity.auth;
 import java.util.List;
 import org.apache.syncope.common.lib.auth.AuthModuleConf;
 import org.apache.syncope.core.persistence.api.entity.Entity;
-import org.apache.syncope.core.persistence.api.entity.resource.Item;
 
 public interface AuthModule extends Entity {
 
@@ -33,16 +32,11 @@ public interface AuthModule extends Entity {
 
     void setDescription(String description);
 
-    /**
-     * Specify the mapping items for the attributes fetched from the source.
-     *
-     * @return list of mapping items
-     */
-    List<? extends Item> getProfileItems();
-
-    boolean add(Item profileItem);
-
     AuthModuleConf getConf();
 
     void setConf(AuthModuleConf description);
+
+    List<? extends AuthModuleItem> getItems();
+
+    boolean add(AuthModuleItem item);
 }
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
index 9f249e4..2754b02 100644
--- a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
@@ -22,4 +22,7 @@ import org.apache.syncope.core.persistence.api.entity.resource.Item;
 
 public interface AuthModuleItem extends Item {
 
+    AuthModule getAuthModule();
+
+    void setAuthModule(AuthModule authModule);
 }
diff --git a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
index 8e82492..d58f29a 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -45,22 +45,22 @@ under the License.
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
   
   <!-- authentication policies -->
-  <Implementation id="MyDefaultAuthPolicyConf" type="AUTH_POLICY_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="MyDefaultAuthPolicyConf" type="AUTH_POLICY_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
   <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf" description="an authentication policy"/>
   <AuthPolicy id="b912a0d4-a890-416f-9ab8-84ab077eb028" name="DefaultAuthPolicy" description="Default authentication policy"/>
 
   <!-- access policies -->
-  <Implementation id="MyDefaultAccessPolicyConf" type="ACCESS_POLICY_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="MyDefaultAccessPolicyConf" type="ACCESS_POLICY_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf","name":"MyDefaultAccessPolicyConf","enabled":true,"ssoEnabled":true}'/>
   <AccessPolicy id="419935c7-deb3-40b3-8a9a-683037e523a2" name="MyDefaultAccessPolicyConf" description="an access policy"/>
 
   <!-- Attr Release Policies -->
-  <Implementation id="AllowedAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="AllowedAttrReleasePolicyConf" type="ATTR_RELEASE_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"AllowedAttrReleasePolicy","allowedAttributes":["cn","givenName","uid"]}'/>
   <AttrReleasePolicy id="319935c7-deb3-40b3-8a9a-683037e523a2" name="AllowedAttrReleasePolicy" description="allowed attribute release policy policy"/>
 
-  <Implementation id="DenyAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="DenyAttrReleasePolicyConf" type="ATTR_RELEASE_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"DenyAttrReleasePolicyConf"}'/>
   <AttrReleasePolicy id="219935c7-deb3-40b3-8a9a-683037e523a2" name="DenyAttrReleasePolicy" description="deny attribute release policy policy"/>
   
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
index dd765ad..8106222 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
@@ -18,18 +18,19 @@
  */
 package org.apache.syncope.core.persistence.jpa.dao.auth;
 
-import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
-import org.springframework.stereotype.Repository;
-import org.springframework.transaction.annotation.Transactional;
-import javax.persistence.TypedQuery;
 import java.util.List;
+import javax.persistence.TypedQuery;
 import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
 import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
 import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
 
 @Repository
 public class JPAAuthModuleDAO extends AbstractDAO<AuthModule> implements AuthModuleDAO {
 
+    @Transactional(readOnly = true)
     @Override
     public AuthModule find(final String key) {
         return entityManager().find(JPAAuthModule.class, key);
@@ -38,13 +39,11 @@ public class JPAAuthModuleDAO extends AbstractDAO<AuthModule> implements AuthMod
     @Transactional(readOnly = true)
     @Override
     public List<AuthModule> findAll() {
-        TypedQuery<AuthModule> query = entityManager().createQuery("SELECT e FROM " + JPAAuthModule.class.
-                getSimpleName() + " e", AuthModule.class);
-
+        TypedQuery<AuthModule> query = entityManager().createQuery(
+                "SELECT e FROM " + JPAAuthModule.class.getSimpleName() + " e", AuthModule.class);
         return query.getResultList();
     }
 
-    @Transactional(readOnly = true)
     @Override
     public AuthModule save(final AuthModule authModule) {
         return entityManager().merge(authModule);
@@ -64,5 +63,4 @@ public class JPAAuthModuleDAO extends AbstractDAO<AuthModule> implements AuthMod
     public void delete(final AuthModule authModule) {
         entityManager().remove(authModule);
     }
-
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
index e2b6586..cbbb96b 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
@@ -30,7 +30,7 @@ import javax.persistence.Table;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.common.lib.auth.AuthModuleConf;
 import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
-import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
 import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
 import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
 
@@ -48,8 +48,8 @@ public class JPAAuthModule extends AbstractGeneratedKeyEntity implements AuthMod
     @Column(nullable = false)
     private String description;
 
-    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "mapping")
-    private final List<JPAAuthModuleItem> profileItems = new ArrayList<>();
+    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "authModule")
+    private List<JPAAuthModuleItem> items = new ArrayList<>();
 
     @Lob
     private String jsonConf;
@@ -75,15 +75,14 @@ public class JPAAuthModule extends AbstractGeneratedKeyEntity implements AuthMod
     }
 
     @Override
-    public List<? extends Item> getProfileItems() {
-        return profileItems;
+    public List<? extends AuthModuleItem> getItems() {
+        return items;
     }
 
     @Override
-    public boolean add(final Item profileItem) {
-        checkType(profileItem, JPAAuthModuleItem.class);
-        return profileItems.contains((JPAAuthModuleItem) profileItem)
-                || profileItems.add((JPAAuthModuleItem) profileItem);
+    public boolean add(final AuthModuleItem item) {
+        checkType(item, JPAAuthModuleItem.class);
+        return items.contains((JPAAuthModuleItem) item) || items.add((JPAAuthModuleItem) item);
     }
 
     @Override
@@ -100,5 +99,4 @@ public class JPAAuthModule extends AbstractGeneratedKeyEntity implements AuthMod
     public void setConf(final AuthModuleConf conf) {
         jsonConf = POJOHelper.serialize(conf);
     }
-
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
index bc92d49..c851289 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
@@ -26,10 +26,13 @@ import javax.persistence.FetchType;
 import javax.persistence.JoinColumn;
 import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
 import javax.persistence.Table;
 import javax.persistence.UniqueConstraint;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
+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.jpa.entity.JPAImplementation;
 import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractItem;
@@ -43,6 +46,9 @@ public class JPAAuthModuleItem extends AbstractItem implements AuthModuleItem {
 
     private static final long serialVersionUID = 3165440920144995781L;
 
+    @ManyToOne
+    private JPAAuthModule authModule;
+
     @ManyToMany(fetch = FetchType.EAGER)
     @JoinTable(name = TABLE + "Transformer",
             joinColumns =
@@ -53,10 +59,30 @@ public class JPAAuthModuleItem extends AbstractItem implements AuthModuleItem {
             @UniqueConstraint(columnNames = { "item_id", "implementation_id" }))
     private final List<JPAImplementation> transformers = new ArrayList<>();
 
+    public JPAAuthModuleItem() {
+        super.setPurpose(MappingPurpose.NONE);
+    }
+
+    @Override
+    public void setPurpose(final MappingPurpose purpose) {
+        // cannot be changed
+    }
+
+    @Override
+    public AuthModule getAuthModule() {
+        return authModule;
+    }
+
+    @Override
+    public void setAuthModule(final AuthModule authModule) {
+        checkType(authModule, JPAAuthModule.class);
+        this.authModule = (JPAAuthModule) authModule;
+    }
+
     @Override
     public boolean add(final Implementation transformer) {
         checkType(transformer, JPAImplementation.class);
-        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        checkImplementationType(transformer, IdRepoImplementationType.ITEM_TRANSFORMER);
         return transformers.contains((JPAImplementation) transformer)
                 || this.transformers.add((JPAImplementation) transformer);
     }
@@ -65,5 +91,4 @@ public class JPAAuthModuleItem extends AbstractItem implements AuthModuleItem {
     public List<? extends Implementation> getTransformers() {
         return transformers;
     }
-
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java
index 261c176..b5843a9 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAccessPolicy.java
@@ -61,7 +61,7 @@ public class JPAAccessPolicy extends AbstractPolicy implements AccessPolicy {
     @Override
     public void setConfiguration(final Implementation configuration) {
         checkType(configuration, JPAImplementation.class);
-        checkImplementationType(configuration, AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        checkImplementationType(configuration, AMImplementationType.ACCESS_POLICY_CONF);
         this.configuration = (JPAImplementation) configuration;
     }
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java
index 6a21edc..d6d0ba3 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAttrReleasePolicy.java
@@ -62,7 +62,7 @@ public class JPAAttrReleasePolicy extends AbstractPolicy implements AttrReleaseP
     @Override
     public void setConfiguration(final Implementation configuration) {
         checkType(configuration, JPAImplementation.class);
-        checkImplementationType(configuration, AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        checkImplementationType(configuration, AMImplementationType.ATTR_RELEASE_POLICY_CONF);
         this.configuration = (JPAImplementation) configuration;
     }
 }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java
index b367152..59263c2 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/policy/JPAAuthPolicy.java
@@ -61,7 +61,7 @@ public class JPAAuthPolicy extends AbstractPolicy implements AuthPolicy {
     @Override
     public void setConfiguration(final Implementation configuration) {
         checkType(configuration, JPAImplementation.class);
-        checkImplementationType(configuration, AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        checkImplementationType(configuration, AMImplementationType.AUTH_POLICY_CONF);
         this.configuration = (JPAImplementation) configuration;
     }
 
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAMappingItem.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAMappingItem.java
index e81450f..a197e11 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAMappingItem.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAMappingItem.java
@@ -28,8 +28,8 @@ import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import javax.persistence.UniqueConstraint;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.resource.Mapping;
 import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
@@ -71,7 +71,7 @@ public class JPAMappingItem extends AbstractItem implements MappingItem {
     @Override
     public boolean add(final Implementation transformer) {
         checkType(transformer, JPAImplementation.class);
-        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        checkImplementationType(transformer, IdRepoImplementationType.ITEM_TRANSFORMER);
         return transformers.contains((JPAImplementation) transformer)
                 || transformers.add((JPAImplementation) transformer);
     }
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAOrgUnitItem.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAOrgUnitItem.java
index 90423b4..1882a13 100644
--- a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAOrgUnitItem.java
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/resource/JPAOrgUnitItem.java
@@ -28,8 +28,8 @@ import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import javax.persistence.UniqueConstraint;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnit;
 import org.apache.syncope.core.persistence.api.entity.resource.OrgUnitItem;
@@ -76,7 +76,7 @@ public class JPAOrgUnitItem extends AbstractItem implements OrgUnitItem {
     @Override
     public boolean add(final Implementation transformer) {
         checkType(transformer, JPAImplementation.class);
-        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        checkImplementationType(transformer, IdRepoImplementationType.ITEM_TRANSFORMER);
         return transformers.contains((JPAImplementation) transformer)
                 || transformers.add((JPAImplementation) transformer);
     }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java
index d2eea8f..c4ea282 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AbstractClientAppTest.java
@@ -56,7 +56,7 @@ public class AbstractClientAppTest extends AbstractTest {
         Implementation type = entityFactory.newEntity(Implementation.class);
         type.setKey("AttrRelPolicyTest");
         type.setEngine(ImplementationEngine.JAVA);
-        type.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        type.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONF);
         type.setBody(POJOHelper.serialize(conf));
         type = implementationDAO.save(type);
         attrRelPolicy.setConfiguration(type);
@@ -78,7 +78,7 @@ public class AbstractClientAppTest extends AbstractTest {
         Implementation type = entityFactory.newEntity(Implementation.class);
         type.setKey("AccessPolicyConfKey");
         type.setEngine(ImplementationEngine.JAVA);
-        type.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        type.setType(AMImplementationType.ACCESS_POLICY_CONF);
         type.setBody(POJOHelper.serialize(conf));
         type = implementationDAO.save(type);
 
@@ -98,7 +98,7 @@ public class AbstractClientAppTest extends AbstractTest {
         Implementation type = entityFactory.newEntity(Implementation.class);
         type.setKey("AuthPolicyConfKey");
         type.setEngine(ImplementationEngine.JAVA);
-        type.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        type.setType(AMImplementationType.AUTH_POLICY_CONF);
         type.setBody(POJOHelper.serialize(conf));
         type = implementationDAO.save(type);
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
index 27c0d22..8bd2c13 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
@@ -303,6 +303,7 @@ public class AuthModuleTest extends AbstractTest {
         module = authModuleDAO.save(module);
         assertNotNull(module);
         assertNotNull(module.getKey());
+
         AuthModule found = authModuleDAO.find(module.getKey());
         assertNotNull(found);
         assertEquals("dc=example2,dc=org", LDAPAuthModuleConf.class.cast(found.getConf()).getBaseDn());
@@ -322,8 +323,7 @@ public class AuthModuleTest extends AbstractTest {
         assertNotNull(module.getKey());
         AuthModule found = authModuleDAO.find(module.getKey());
         assertNotNull(found);
-        assertEquals("SELECT * FROM otherTable WHERE name=?",
-                JDBCAuthModuleConf.class.cast(found.getConf()).getSql());
+        assertEquals("SELECT * FROM otherTable WHERE name=?", JDBCAuthModuleConf.class.cast(found.getConf()).getSql());
     }
 
     @Test
@@ -443,6 +443,7 @@ public class AuthModuleTest extends AbstractTest {
     public void updateWithSyncopeModule() {
         AuthModule module = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
         assertNotNull(module);
+
         AuthModuleConf conf = module.getConf();
         SyncopeAuthModuleConf.class.cast(conf).setDomain("Two");
         module.setConf(conf);
@@ -457,16 +458,13 @@ public class AuthModuleTest extends AbstractTest {
 
     @Test
     public void delete() {
-        testDelete("be456831-593d-4003-b273-4c3fb61700df");
-        testDelete("4c3ed8f6-7008-11ea-bc55-0242ac130003");
-        testDelete("4c3edbbc-7008-11ea-bc55-0242ac130003");
-        testDelete("4c3ed7e8-7008-11ea-bc55-0242ac130003");
-        testDelete("4c3ed4e6-7008-11ea-bc55-0242ac130003");
-        testDelete("4c3edc98-7008-11ea-bc55-0242ac130003");
-        testDelete("4c3ed9d2-7008-11ea-bc55-0242ac130003");
-        testDelete("4c3edd60-7008-11ea-bc55-0242ac130003");
-        testDelete("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
-        testDelete("f6e1288d-50d9-45fe-82ee-597c42242205");
+        AuthModule authModule = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(authModule);
+
+        authModuleDAO.delete("be456831-593d-4003-b273-4c3fb61700df");
+
+        authModule = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNull(authModule);
     }
 
     private void saveAuthModule(final String name, final AuthModuleConf conf) {
@@ -474,29 +472,27 @@ public class AuthModuleTest extends AbstractTest {
         module.setName(name);
         module.setDescription("An authentication module");
         module.setConf(conf);
+
         AuthModuleItem keyMapping = entityFactory.newEntity(AuthModuleItem.class);
         keyMapping.setIntAttrName("uid");
         keyMapping.setExtAttrName("username");
+        keyMapping.setAuthModule(module);
+        module.add(keyMapping);
+
         AuthModuleItem fullnameMapping = entityFactory.newEntity(AuthModuleItem.class);
         fullnameMapping.setIntAttrName("cn");
         fullnameMapping.setExtAttrName("fullname");
-        module.add(keyMapping);
+        fullnameMapping.setAuthModule(module);
         module.add(fullnameMapping);
-        authModuleDAO.save(module);
+
+        module = authModuleDAO.save(module);
         assertNotNull(module);
         assertNotNull(module.getKey());
-        assertNotNull(authModuleDAO.find(module.getKey()));
-    }
-
-    private void testDelete(final String key) {
-        AuthModule authModule = authModuleDAO.find(key);
-        assertNotNull(authModule);
-        authModuleDAO.delete(key);
-        authModule = authModuleDAO.find(key);
-        assertNull(authModule);
+        assertEquals(module, authModuleDAO.find(module.getKey()));
+        assertEquals(2, module.getItems().size());
     }
 
-    private boolean isSpecificConf(final AuthModuleConf conf, final Class<? extends AuthModuleConf> clazz) {
+    private static boolean isSpecificConf(final AuthModuleConf conf, final Class<? extends AuthModuleConf> clazz) {
         return ClassUtils.isAssignable(clazz, conf.getClass());
     }
 }
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
index f34a7e7..bf6ff38 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/ImplementationTest.java
@@ -74,10 +74,10 @@ public class ImplementationTest extends AbstractTest {
         implementations = implementationDAO.findByType(IdMImplementationType.PUSH_CORRELATION_RULE);
         assertEquals(1, implementations.size());
 
-        implementations = implementationDAO.findByType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        implementations = implementationDAO.findByType(AMImplementationType.AUTH_POLICY_CONF);
         assertEquals(1, implementations.size());
 
-        implementations = implementationDAO.findByType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        implementations = implementationDAO.findByType(AMImplementationType.ACCESS_POLICY_CONF);
         assertEquals(1, implementations.size());
     }
 
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
index 3e7cdaf..1ebc7c3 100644
--- a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/PolicyTest.java
@@ -213,7 +213,7 @@ public class PolicyTest extends AbstractTest {
         Implementation type = entityFactory.newEntity(Implementation.class);
         type.setKey("AttrReleasePolicyAllowEverything");
         type.setEngine(ImplementationEngine.JAVA);
-        type.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        type.setType(AMImplementationType.ACCESS_POLICY_CONF);
         type.setBody(POJOHelper.serialize(conf));
         type = implementationDAO.save(type);
 
@@ -240,7 +240,7 @@ public class PolicyTest extends AbstractTest {
         Implementation authPolicyType = entityFactory.newEntity(Implementation.class);
         authPolicyType.setKey("AuthPolicyConfKey");
         authPolicyType.setEngine(ImplementationEngine.JAVA);
-        authPolicyType.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        authPolicyType.setType(AMImplementationType.AUTH_POLICY_CONF);
         authPolicyType.setBody(POJOHelper.serialize(authPolicyConf));
         authPolicyType = implementationDAO.save(authPolicyType);
 
@@ -265,7 +265,7 @@ public class PolicyTest extends AbstractTest {
         Implementation attrReleasePolicyType = entityFactory.newEntity(Implementation.class);
         attrReleasePolicyType.setKey("AttrReleasePolicyAllowEverything");
         attrReleasePolicyType.setEngine(ImplementationEngine.JAVA);
-        attrReleasePolicyType.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        attrReleasePolicyType.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONF);
         attrReleasePolicyType.setBody(POJOHelper.serialize(attrReleasePolicyConf));
         attrReleasePolicyType = implementationDAO.save(attrReleasePolicyType);
 
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index b484956..a222994 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -45,22 +45,22 @@ under the License.
   <PasswordPolicyRule policy_id="55e5de0b-c79c-4e66-adda-251b6fb8579a" implementation_id="DefaultPasswordRuleConf3"/>
   
   <!-- Authentication policies -->
-  <Implementation id="MyDefaultAuthPolicyConf" type="AUTH_POLICY_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="MyDefaultAuthPolicyConf" type="AUTH_POLICY_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf","authModules":["LdapAuthenticationTest"]}'/>
   <AuthPolicy id="659b9906-4b6e-4bc0-aca0-6809dff346d4" name="MyDefaultAuthPolicyConf" description="an authentication policy"/>
   <AuthPolicy id="b912a0d4-a890-416f-9ab8-84ab077eb028" name="DefaultAuthPolicy" description="Default authentication policy"/>
 
   <!-- Access policies -->
-  <Implementation id="MyDefaultAccessPolicyConf" type="ACCESS_POLICY_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="MyDefaultAccessPolicyConf" type="ACCESS_POLICY_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf","name":"MyDefaultAccessPolicyConf","enabled":true,"ssoEnabled":true}'/>
   <AccessPolicy id="419935c7-deb3-40b3-8a9a-683037e523a2" name="MyDefaultAccessPolicyConf" description="an access policy"/>
 
   <!-- Attr Release Policies -->
-  <Implementation id="AllowedAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="AllowedAttrReleasePolicyConf" type="ATTR_RELEASE_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"AllowedAttrReleasePolicy","allowedAttributes":["cn","givenName","uid"]}'/>
   <AttrReleasePolicy id="319935c7-deb3-40b3-8a9a-683037e523a2" name="AllowedAttrReleasePolicy" description="allowed attribute release policy policy"/>
 
-  <Implementation id="DenyAttrReleasePolicyConf" type="ATTR_RELEASE_CONFIGURATIONS" engine="JAVA"
+  <Implementation id="DenyAttrReleasePolicyConf" type="ATTR_RELEASE_CONF" engine="JAVA"
                   body='{"@class":"org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf","name":"DenyAttrReleasePolicyConf"}'/>
   <AttrReleasePolicy id="219935c7-deb3-40b3-8a9a-683037e523a2" name="DenyAttrReleasePolicy" description="deny attribute release policy policy"/>
   
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
index 0b0e38b..058e58d 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
@@ -18,48 +18,108 @@
  */
 package org.apache.syncope.core.provisioning.java.data;
 
+import org.apache.syncope.common.lib.SyncopeClientCompositeException;
+import org.apache.syncope.common.lib.SyncopeClientException;
 import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.lib.to.ItemTO;
+import org.apache.syncope.common.lib.types.ClientExceptionType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
 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.provisioning.api.data.AuthModuleDataBinder;
+import org.apache.syncope.core.provisioning.api.jexl.JexlUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Component
 public class AuthModuleDataBinderImpl implements AuthModuleDataBinder {
 
+    private static final Logger LOG = LoggerFactory.getLogger(AuthModuleDataBinder.class);
+
     @Autowired
     private EntityFactory entityFactory;
 
-    private AuthModule getAuthModule(final AuthModule authModule, final AuthModuleTO authModuleTO) {
-        AuthModule result = authModule;
+    private void populateItems(final AuthModuleTO authModuleTO, final AuthModule authModule) {
+        SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
+        SyncopeClientException invalidMapping =
+                SyncopeClientException.build(ClientExceptionType.InvalidMapping);
+        SyncopeClientException requiredValuesMissing =
+                SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
 
-        if (result == null) {
-            result = entityFactory.newEntity(AuthModule.class);
-        }
+        authModuleTO.getItems().forEach(itemTO -> {
+            if (itemTO == null) {
+                LOG.error("Null {}", ItemTO.class.getSimpleName());
+                invalidMapping.getElements().add("Null " + ItemTO.class.getSimpleName());
+            } else if (itemTO.getIntAttrName() == null) {
+                requiredValuesMissing.getElements().add("intAttrName");
+                scce.addException(requiredValuesMissing);
+            } else {
+                // no mandatory condition implies mandatory condition false
+                if (!JexlUtils.isExpressionValid(itemTO.getMandatoryCondition() == null
+                        ? "false" : itemTO.getMandatoryCondition())) {
 
-        AuthModule authenticationModule = AuthModule.class.cast(result);
-        AuthModuleTO authenticationModuleTO = AuthModuleTO.class.cast(authModuleTO);
+                    SyncopeClientException invalidMandatoryCondition =
+                            SyncopeClientException.build(ClientExceptionType.InvalidValues);
+                    invalidMandatoryCondition.getElements().add(itemTO.getMandatoryCondition());
+                    scce.addException(invalidMandatoryCondition);
+                }
 
-        authenticationModule.setName(authenticationModuleTO.getName());
-        authenticationModule.setConf(authenticationModuleTO.getConf());
-        authenticationModule.setDescription(authenticationModuleTO.getDescription());
-        // remove all profile items not contained in the TO
-        authenticationModule.getProfileItems().
-                removeIf(item -> !authenticationModuleTO.getProfileItems().stream().
-                anyMatch(otherItem -> item.getKey().equals(otherItem.getKey())));
+                AuthModuleItem item = entityFactory.newEntity(AuthModuleItem.class);
+                item.setIntAttrName(itemTO.getIntAttrName());
+                item.setExtAttrName(itemTO.getExtAttrName());
+                item.setMandatoryCondition(itemTO.getMandatoryCondition());
+                item.setConnObjectKey(itemTO.isConnObjectKey());
+                item.setPassword(itemTO.isPassword());
+                item.setPropagationJEXLTransformer(itemTO.getPropagationJEXLTransformer());
+                item.setPullJEXLTransformer(itemTO.getPullJEXLTransformer());
+                item.setAuthModule(authModule);
+                authModule.add(item);
+            }
+        });
 
-        return result;
+        if (!invalidMapping.getElements().isEmpty()) {
+            scce.addException(invalidMapping);
+        }
+        if (scce.hasExceptions()) {
+            throw scce;
+        }
     }
 
     @Override
     public AuthModule create(final AuthModuleTO authModuleTO) {
-        return getAuthModule(null, authModuleTO);
+        return update(entityFactory.newEntity(AuthModule.class), authModuleTO);
     }
 
     @Override
     public AuthModule update(final AuthModule authModule, final AuthModuleTO authModuleTO) {
-        return getAuthModule(authModule, authModuleTO);
+        authModule.setName(authModuleTO.getName());
+        authModule.setDescription(authModuleTO.getDescription());
+        authModule.setConf(authModuleTO.getConf());
+
+        authModule.getItems().clear();
+        populateItems(authModuleTO, authModule);
+
+        return authModule;
+    }
+
+    private static void populateItems(final AuthModule authModule, final AuthModuleTO authModuleTO) {
+        authModule.getItems().forEach(item -> {
+            ItemTO itemTO = new ItemTO();
+            itemTO.setKey(item.getKey());
+            itemTO.setIntAttrName(item.getIntAttrName());
+            itemTO.setExtAttrName(item.getExtAttrName());
+            itemTO.setMandatoryCondition(item.getMandatoryCondition());
+            itemTO.setConnObjectKey(item.isConnObjectKey());
+            itemTO.setPassword(item.isPassword());
+            itemTO.setPropagationJEXLTransformer(item.getPropagationJEXLTransformer());
+            itemTO.setPullJEXLTransformer(item.getPullJEXLTransformer());
+            itemTO.setPurpose(MappingPurpose.NONE);
+
+            authModuleTO.add(itemTO);
+        });
     }
 
     @Override
@@ -70,9 +130,8 @@ public class AuthModuleDataBinderImpl implements AuthModuleDataBinder {
         authModuleTO.setKey(authModule.getKey());
         authModuleTO.setDescription(authModule.getDescription());
         authModuleTO.setConf(authModule.getConf());
-        authModuleTO.getProfileItems().forEach(item -> {
-            authModuleTO.add(item);
-        });
+
+        populateItems(authModule, authModuleTO);
 
         return authModuleTO;
     }
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
index 9df9f8c..5de0f13 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/ImplementationDataBinderImpl.java
@@ -108,7 +108,7 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                     base = PasswordRule.class;
                     break;
 
-                case IdMImplementationType.ITEM_TRANSFORMER:
+                case IdRepoImplementationType.ITEM_TRANSFORMER:
                     base = ItemTransformer.class;
                     break;
 
@@ -156,15 +156,15 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                     base = ProvisionSorter.class;
                     break;
 
-                case AMImplementationType.ACCESS_POLICY_CONFIGURATIONS:
+                case AMImplementationType.ACCESS_POLICY_CONF:
                     base = AccessPolicy.class;
                     break;
 
-                case AMImplementationType.AUTH_POLICY_CONFIGURATIONS:
+                case AMImplementationType.AUTH_POLICY_CONF:
                     base = AuthPolicy.class;
                     break;
 
-                case AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS:
+                case AMImplementationType.ATTR_RELEASE_POLICY_CONF:
                     base = AttrReleasePolicy.class;
                     break;
                 default:
@@ -176,7 +176,7 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
             }
 
             switch (implementation.getType()) {
-                case AMImplementationType.ACCESS_POLICY_CONFIGURATIONS:
+                case AMImplementationType.ACCESS_POLICY_CONF:
                     AccessPolicyConf accessPolicyConf =
                             POJOHelper.deserialize(implementation.getBody(), AccessPolicyConf.class);
                     if (accessPolicyConf == null) {
@@ -184,7 +184,7 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                         throw sce;
                     }
                     break;
-                case AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS:
+                case AMImplementationType.ATTR_RELEASE_POLICY_CONF:
                     AttrReleasePolicyConf policyConf =
                             POJOHelper.deserialize(implementation.getBody(), AttrReleasePolicyConf.class);
                     if (policyConf == null) {
@@ -192,7 +192,7 @@ public class ImplementationDataBinderImpl implements ImplementationDataBinder {
                         throw sce;
                     }
                     break;
-                case AMImplementationType.AUTH_POLICY_CONFIGURATIONS:
+                case AMImplementationType.AUTH_POLICY_CONF:
                     AuthPolicyConf authPolicyConf =
                             POJOHelper.deserialize(implementation.getBody(), AuthPolicyConf.class);
                     if (authPolicyConf == null) {
diff --git a/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProviderItem.java b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProviderItem.java
index f707620..f2afe3a 100644
--- a/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProviderItem.java
+++ b/ext/oidcclient/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPAOIDCProviderItem.java
@@ -28,8 +28,8 @@ import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import javax.persistence.UniqueConstraint;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.OIDCProvider;
 import org.apache.syncope.core.persistence.api.entity.OIDCProviderItem;
@@ -71,7 +71,7 @@ public class JPAOIDCProviderItem extends AbstractItem implements OIDCProviderIte
     @Override
     public boolean add(final Implementation transformer) {
         checkType(transformer, JPAImplementation.class);
-        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        checkImplementationType(transformer, IdRepoImplementationType.ITEM_TRANSFORMER);
         return transformers.contains((JPAImplementation) transformer)
                 || this.transformers.add((JPAImplementation) transformer);
     }
diff --git a/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java b/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java
index a206cf9..c978a97 100644
--- a/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java
+++ b/ext/oidcclient/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/OIDCProviderDataBinderImpl.java
@@ -22,7 +22,6 @@ import java.text.ParseException;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.OIDCProviderTO;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -71,21 +70,16 @@ public class OIDCProviderDataBinderImpl implements OIDCProviderDataBinder {
     @Override
     public OIDCProvider create(final OIDCProviderTO opTO) {
         return update(entityFactory.newEntity(OIDCProvider.class), opTO);
-
     }
 
-    private void populateItems(
-            final OIDCProviderTO opTO,
-            final OIDCProvider op,
-            final AnyTypeClassTO allowedSchemas) {
-
+    private void populateItems(final OIDCProviderTO opTO, final OIDCProvider op) {
         SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
         SyncopeClientException invalidMapping =
                 SyncopeClientException.build(ClientExceptionType.InvalidMapping);
         SyncopeClientException requiredValuesMissing =
                 SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
 
-        for (ItemTO itemTO : opTO.getItems()) {
+        opTO.getItems().forEach(itemTO -> {
             if (itemTO == null) {
                 LOG.error("Null {}", ItemTO.class.getSimpleName());
                 invalidMapping.getElements().add("Null " + ItemTO.class.getSimpleName());
@@ -104,69 +98,43 @@ public class OIDCProviderDataBinderImpl implements OIDCProviderDataBinder {
                     LOG.error("'{}' not existing", itemTO.getIntAttrName());
                     invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not existing");
                 } else {
-                    boolean allowed = true;
-                    if (intAttrName.getSchemaType() != null
-                            && intAttrName.getEnclosingGroup() == null
-                            && intAttrName.getRelatedAnyObject() == null) {
-                        switch (intAttrName.getSchemaType()) {
-                            case PLAIN:
-                                allowed = allowedSchemas.getPlainSchemas().contains(intAttrName.getSchema().getKey());
-                                break;
-
-                            case DERIVED:
-                                allowed = allowedSchemas.getDerSchemas().contains(intAttrName.getSchema().getKey());
-                                break;
-
-                            case VIRTUAL:
-                                allowed = allowedSchemas.getVirSchemas().contains(intAttrName.getSchema().getKey());
-                                break;
-
-                            default:
-                        }
+                    // no mandatory condition implies mandatory condition false
+                    if (!JexlUtils.isExpressionValid(itemTO.getMandatoryCondition() == null
+                            ? "false" : itemTO.getMandatoryCondition())) {
+
+                        SyncopeClientException invalidMandatoryCondition = SyncopeClientException.build(
+                                ClientExceptionType.InvalidValues);
+                        invalidMandatoryCondition.getElements().add(itemTO.getMandatoryCondition());
+                        scce.addException(invalidMandatoryCondition);
                     }
 
-                    if (allowed) {
-                        // no mandatory condition implies mandatory condition false
-                        if (!JexlUtils.isExpressionValid(itemTO.getMandatoryCondition() == null
-                                ? "false" : itemTO.getMandatoryCondition())) {
-
-                            SyncopeClientException invalidMandatoryCondition = SyncopeClientException.build(
-                                    ClientExceptionType.InvalidValues);
-                            invalidMandatoryCondition.getElements().add(itemTO.getMandatoryCondition());
-                            scce.addException(invalidMandatoryCondition);
+                    OIDCProviderItem item = entityFactory.newEntity(OIDCProviderItem.class);
+                    item.setIntAttrName(itemTO.getIntAttrName());
+                    item.setExtAttrName(itemTO.getExtAttrName());
+                    item.setMandatoryCondition(itemTO.getMandatoryCondition());
+                    item.setConnObjectKey(itemTO.isConnObjectKey());
+                    item.setPassword(itemTO.isPassword());
+                    item.setPropagationJEXLTransformer(itemTO.getPropagationJEXLTransformer());
+                    item.setPullJEXLTransformer(itemTO.getPullJEXLTransformer());
+                    item.setOP(op);
+                    item.setPurpose(MappingPurpose.NONE);
+                    if (item.isConnObjectKey()) {
+                        if (intAttrName.getSchemaType() == SchemaType.VIRTUAL) {
+                            invalidMapping.getElements().
+                                    add("Virtual attributes cannot be set as ConnObjectKey");
                         }
-
-                        OIDCProviderItem item = entityFactory.newEntity(OIDCProviderItem.class);
-                        item.setIntAttrName(itemTO.getIntAttrName());
-                        item.setExtAttrName(itemTO.getExtAttrName());
-                        item.setMandatoryCondition(itemTO.getMandatoryCondition());
-                        item.setConnObjectKey(itemTO.isConnObjectKey());
-                        item.setPassword(itemTO.isPassword());
-                        item.setPropagationJEXLTransformer(itemTO.getPropagationJEXLTransformer());
-                        item.setPullJEXLTransformer(itemTO.getPullJEXLTransformer());
-                        item.setOP(op);
-                        item.setPurpose(MappingPurpose.NONE);
-                        if (item.isConnObjectKey()) {
-                            if (intAttrName.getSchemaType() == SchemaType.VIRTUAL) {
-                                invalidMapping.getElements().
-                                        add("Virtual attributes cannot be set as ConnObjectKey");
-                            }
-                            if ("password".equals(intAttrName.getField())) {
-                                invalidMapping.getElements().add(
-                                        "Password attributes cannot be set as ConnObjectKey");
-                            }
-
-                            op.setConnObjectKeyItem(item);
-                        } else {
-                            op.add(item);
+                        if ("password".equals(intAttrName.getField())) {
+                            invalidMapping.getElements().add(
+                                    "Password attributes cannot be set as ConnObjectKey");
                         }
+
+                        op.setConnObjectKeyItem(item);
                     } else {
-                        LOG.error("'{}' not allowed", itemTO.getIntAttrName());
-                        invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not allowed");
+                        op.add(item);
                     }
                 }
             }
-        }
+        });
 
         if (!invalidMapping.getElements().isEmpty()) {
             scce.addException(invalidMapping);
@@ -206,16 +174,7 @@ public class OIDCProviderDataBinderImpl implements OIDCProviderDataBinder {
         }
 
         op.getItems().clear();
-        AnyTypeClassTO allowedSchemas = new AnyTypeClassTO();
-        anyTypeDAO.findUser().getClasses().forEach(anyTypeClass -> {
-            allowedSchemas.getPlainSchemas().addAll(anyTypeClass.getPlainSchemas().stream().
-                    map(Entity::getKey).collect(Collectors.toList()));
-            allowedSchemas.getDerSchemas().addAll(anyTypeClass.getDerSchemas().stream().
-                    map(Entity::getKey).collect(Collectors.toList()));
-            allowedSchemas.getVirSchemas().addAll(anyTypeClass.getVirSchemas().stream().
-                    map(Entity::getKey).collect(Collectors.toList()));
-        });
-        populateItems(opTO, op, allowedSchemas);
+        populateItems(opTO, op);
 
         opTO.getActions().forEach(action -> {
             Implementation implementation = implementationDAO.find(action);
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASAML2IdPDAO.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASAML2IdPDAO.java
index ccf67b6..0a2fbef 100644
--- a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASAML2IdPDAO.java
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/JPASAML2IdPDAO.java
@@ -73,5 +73,4 @@ public class JPASAML2IdPDAO extends AbstractDAO<SAML2IdP> implements SAML2IdPDAO
             entityManager().remove(idp);
         }
     }
-
 }
diff --git a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdPItem.java b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdPItem.java
index 0028789..cb029e6 100644
--- a/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdPItem.java
+++ b/ext/saml2sp/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/JPASAML2IdPItem.java
@@ -28,8 +28,8 @@ import javax.persistence.JoinTable;
 import javax.persistence.ManyToMany;
 import javax.persistence.ManyToOne;
 import javax.persistence.Table;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import javax.persistence.UniqueConstraint;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.core.persistence.api.entity.Implementation;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdP;
 import org.apache.syncope.core.persistence.api.entity.SAML2IdPItem;
@@ -71,7 +71,7 @@ public class JPASAML2IdPItem extends AbstractItem implements SAML2IdPItem {
     @Override
     public boolean add(final Implementation transformer) {
         checkType(transformer, JPAImplementation.class);
-        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        checkImplementationType(transformer, IdRepoImplementationType.ITEM_TRANSFORMER);
         return transformers.contains((JPAImplementation) transformer)
                 || this.transformers.add((JPAImplementation) transformer);
     }
diff --git a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
index 992a8ce..93bb396 100644
--- a/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
+++ b/ext/saml2sp/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/SAML2IdPDataBinderImpl.java
@@ -23,7 +23,6 @@ import java.util.Base64;
 import java.util.stream.Collectors;
 import org.apache.syncope.common.lib.SyncopeClientCompositeException;
 import org.apache.syncope.common.lib.SyncopeClientException;
-import org.apache.syncope.common.lib.to.AnyTypeClassTO;
 import org.apache.syncope.common.lib.to.ItemTO;
 import org.apache.syncope.common.lib.to.SAML2IdPTO;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -74,18 +73,14 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         return update(entityFactory.newEntity(SAML2IdP.class), idpTO);
     }
 
-    private void populateItems(
-            final SAML2IdPTO idpTO,
-            final SAML2IdP idp,
-            final AnyTypeClassTO allowedSchemas) {
-
+    private void populateItems(final SAML2IdPTO idpTO, final SAML2IdP idp) {
         SyncopeClientCompositeException scce = SyncopeClientException.buildComposite();
         SyncopeClientException invalidMapping =
                 SyncopeClientException.build(ClientExceptionType.InvalidMapping);
         SyncopeClientException requiredValuesMissing =
                 SyncopeClientException.build(ClientExceptionType.RequiredValuesMissing);
 
-        for (ItemTO itemTO : idpTO.getItems()) {
+        idpTO.getItems().forEach(itemTO -> {
             if (itemTO == null) {
                 LOG.error("Null {}", ItemTO.class.getSimpleName());
                 invalidMapping.getElements().add("Null " + ItemTO.class.getSimpleName());
@@ -104,69 +99,43 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
                     LOG.error("'{}' not existing", itemTO.getIntAttrName());
                     invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not existing");
                 } else {
-                    boolean allowed = true;
-                    if (intAttrName.getSchemaType() != null
-                            && intAttrName.getEnclosingGroup() == null
-                            && intAttrName.getRelatedAnyObject() == null) {
-                        switch (intAttrName.getSchemaType()) {
-                            case PLAIN:
-                                allowed = allowedSchemas.getPlainSchemas().contains(intAttrName.getSchema().getKey());
-                                break;
-
-                            case DERIVED:
-                                allowed = allowedSchemas.getDerSchemas().contains(intAttrName.getSchema().getKey());
-                                break;
-
-                            case VIRTUAL:
-                                allowed = allowedSchemas.getVirSchemas().contains(intAttrName.getSchema().getKey());
-                                break;
-
-                            default:
-                        }
+                    // no mandatory condition implies mandatory condition false
+                    if (!JexlUtils.isExpressionValid(itemTO.getMandatoryCondition() == null
+                            ? "false" : itemTO.getMandatoryCondition())) {
+
+                        SyncopeClientException invalidMandatoryCondition = SyncopeClientException.build(
+                                ClientExceptionType.InvalidValues);
+                        invalidMandatoryCondition.getElements().add(itemTO.getMandatoryCondition());
+                        scce.addException(invalidMandatoryCondition);
                     }
 
-                    if (allowed) {
-                        // no mandatory condition implies mandatory condition false
-                        if (!JexlUtils.isExpressionValid(itemTO.getMandatoryCondition() == null
-                                ? "false" : itemTO.getMandatoryCondition())) {
-
-                            SyncopeClientException invalidMandatoryCondition = SyncopeClientException.build(
-                                    ClientExceptionType.InvalidValues);
-                            invalidMandatoryCondition.getElements().add(itemTO.getMandatoryCondition());
-                            scce.addException(invalidMandatoryCondition);
+                    SAML2IdPItem item = entityFactory.newEntity(SAML2IdPItem.class);
+                    item.setIntAttrName(itemTO.getIntAttrName());
+                    item.setExtAttrName(itemTO.getExtAttrName());
+                    item.setMandatoryCondition(itemTO.getMandatoryCondition());
+                    item.setConnObjectKey(itemTO.isConnObjectKey());
+                    item.setPassword(itemTO.isPassword());
+                    item.setPropagationJEXLTransformer(itemTO.getPropagationJEXLTransformer());
+                    item.setPullJEXLTransformer(itemTO.getPullJEXLTransformer());
+                    item.setIdP(idp);
+                    item.setPurpose(MappingPurpose.NONE);
+                    if (item.isConnObjectKey()) {
+                        if (intAttrName.getSchemaType() == SchemaType.VIRTUAL) {
+                            invalidMapping.getElements().
+                                    add("Virtual attributes cannot be set as ConnObjectKey");
                         }
-
-                        SAML2IdPItem item = entityFactory.newEntity(SAML2IdPItem.class);
-                        item.setIntAttrName(itemTO.getIntAttrName());
-                        item.setExtAttrName(itemTO.getExtAttrName());
-                        item.setMandatoryCondition(itemTO.getMandatoryCondition());
-                        item.setConnObjectKey(itemTO.isConnObjectKey());
-                        item.setPassword(itemTO.isPassword());
-                        item.setPropagationJEXLTransformer(itemTO.getPropagationJEXLTransformer());
-                        item.setPullJEXLTransformer(itemTO.getPullJEXLTransformer());
-                        item.setIdP(idp);
-                        item.setPurpose(MappingPurpose.NONE);
-                        if (item.isConnObjectKey()) {
-                            if (intAttrName.getSchemaType() == SchemaType.VIRTUAL) {
-                                invalidMapping.getElements().
-                                        add("Virtual attributes cannot be set as ConnObjectKey");
-                            }
-                            if ("password".equals(intAttrName.getField())) {
-                                invalidMapping.getElements().add(
-                                        "Password attributes cannot be set as ConnObjectKey");
-                            }
-
-                            idp.setConnObjectKeyItem(item);
-                        } else {
-                            idp.add(item);
+                        if ("password".equals(intAttrName.getField())) {
+                            invalidMapping.getElements().add(
+                                    "Password attributes cannot be set as ConnObjectKey");
                         }
+
+                        idp.setConnObjectKeyItem(item);
                     } else {
-                        LOG.error("'{}' not allowed", itemTO.getIntAttrName());
-                        invalidMapping.getElements().add('\'' + itemTO.getIntAttrName() + "' not allowed");
+                        idp.add(item);
                     }
                 }
             }
-        }
+        });
 
         if (!invalidMapping.getElements().isEmpty()) {
             scce.addException(invalidMapping);
@@ -202,16 +171,7 @@ public class SAML2IdPDataBinderImpl implements SAML2IdPDataBinder {
         }
 
         idp.getItems().clear();
-        AnyTypeClassTO allowedSchemas = new AnyTypeClassTO();
-        anyTypeDAO.findUser().getClasses().forEach(anyTypeClass -> {
-            allowedSchemas.getPlainSchemas().addAll(anyTypeClass.getPlainSchemas().stream().
-                    map(Entity::getKey).collect(Collectors.toList()));
-            allowedSchemas.getDerSchemas().addAll(anyTypeClass.getDerSchemas().stream().
-                    map(Entity::getKey).collect(Collectors.toList()));
-            allowedSchemas.getVirSchemas().addAll(anyTypeClass.getVirSchemas().stream().
-                    map(Entity::getKey).collect(Collectors.toList()));
-        });
-        populateItems(idpTO, idp, allowedSchemas);
+        populateItems(idpTO, idp);
 
         idpTO.getActions().forEach(action -> {
             Implementation implementation = implementationDAO.find(action);
diff --git a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
index 40834af..dcebb97 100644
--- a/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
+++ b/fit/core-reference/src/main/java/org/apache/syncope/fit/core/reference/ITImplementationLookup.java
@@ -184,7 +184,7 @@ public class ITImplementationLookup implements ImplementationLookup {
             classNames = new HashSet<>();
             classNames.add(DateToDateItemTransformer.class.getName());
             classNames.add(DateToLongItemTransformer.class.getName());
-            put(IdMImplementationType.ITEM_TRANSFORMER, classNames);
+            put(IdRepoImplementationType.ITEM_TRANSFORMER, classNames);
 
             classNames = new HashSet<>();
             classNames.add(TestSampleJobDelegate.class.getName());
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
index a13c4fa..ab51c1f 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
@@ -45,6 +45,7 @@ import org.apache.syncope.common.lib.auth.StaticAuthModuleConf;
 import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
 import org.apache.syncope.common.lib.auth.U2FAuthModuleConf;
 import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.lib.to.ItemTO;
 
 public class AuthModuleITCase extends AbstractITCase {
 
@@ -66,8 +67,8 @@ public class AuthModuleITCase extends AbstractITCase {
         AuthModuleTO authModuleTO = new AuthModuleTO();
         authModuleTO.setName("Test" + type + "AuthenticationModule" + getUUIDString());
         authModuleTO.setDescription("A test " + type + " Authentication Module");
-        AuthModuleConf conf;
 
+        AuthModuleConf conf;
         switch (type) {
             case LDAP:
                 conf = new LDAPAuthModuleConf();
@@ -161,19 +162,21 @@ public class AuthModuleITCase extends AbstractITCase {
         }
         authModuleTO.setConf(conf);
 
-        return authModuleTO;
-    }
+        ItemTO keyMapping = new ItemTO();
+        keyMapping.setIntAttrName("uid");
+        keyMapping.setExtAttrName("username");
+        authModuleTO.add(keyMapping);
 
-    @Test
-    public void findAll() {
-        List<AuthModuleTO> authModuleTOs = authModuleService.list();
-        assertNotNull(authModuleTOs);
-        assertFalse(authModuleTOs.isEmpty());
-        assertTrue(authModuleTOs.size() >= 10);
+        ItemTO fullnameMapping = new ItemTO();
+        fullnameMapping.setIntAttrName("cn");
+        fullnameMapping.setExtAttrName("fullname");
+        authModuleTO.add(fullnameMapping);
+
+        return authModuleTO;
     }
 
     @Test
-    public void listByType() {
+    public void list() {
         List<AuthModuleTO> authModuleTOs = authModuleService.list();
         assertNotNull(authModuleTOs);
         assertFalse(authModuleTOs.isEmpty());
@@ -564,17 +567,17 @@ public class AuthModuleITCase extends AbstractITCase {
     private void testCreate(final AuthModuleSupportedType type) {
         AuthModuleTO authModuleTO = createAuthModule(buildAuthModuleTO(type));
         assertNotNull(authModuleTO);
-        assertTrue(authModuleTO.getName().contains(
-                "Test" + type + "AuthenticationModule"));
-        assertTrue(authModuleTO.getDescription().contains(
-                "A test " + type + " Authentication Module"));
+        assertTrue(authModuleTO.getName().contains("Test" + type + "AuthenticationModule"));
+        assertTrue(authModuleTO.getDescription().contains("A test " + type + " Authentication Module"));
+        assertEquals(2, authModuleTO.getItems().size());
     }
 
     private void testDelete(final AuthModuleSupportedType type) {
-        AuthModuleTO authModuleTO = buildAuthModuleTO(type);
-        AuthModuleTO read = createAuthModule(authModuleTO);
+        AuthModuleTO read = createAuthModule(buildAuthModuleTO(type));
         assertNotNull(read);
+
         authModuleService.delete(read.getKey());
+
         try {
             authModuleService.read(read.getKey());
             fail("This should not happen");
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
index 4b65c75..817e121 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PolicyITCase.java
@@ -66,14 +66,13 @@ public class PolicyITCase extends AbstractITCase {
         String authPolicyName = "TestAuthPolicy" + getUUIDString();
         ImplementationTO implementationTO = null;
         try {
-            implementationTO = implementationService.read(
-                    AMImplementationType.AUTH_POLICY_CONFIGURATIONS, authPolicyName);
+            implementationTO = implementationService.read(AMImplementationType.AUTH_POLICY_CONF, authPolicyName);
         } catch (SyncopeClientException e) {
             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
                 implementationTO = new ImplementationTO();
                 implementationTO.setKey(authPolicyName);
                 implementationTO.setEngine(ImplementationEngine.JAVA);
-                implementationTO.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+                implementationTO.setType(AMImplementationType.AUTH_POLICY_CONF);
 
                 DefaultAuthPolicyConf conf = new DefaultAuthPolicyConf();
                 conf.getAuthModules().addAll(List.of("LdapAuthentication1"));
@@ -97,14 +96,13 @@ public class PolicyITCase extends AbstractITCase {
     private static AttrReleasePolicyTO buildAttributeReleasePolicyTO(final String policyName) {
         ImplementationTO implementationTO = null;
         try {
-            implementationTO = implementationService.read(
-                    AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS, policyName);
+            implementationTO = implementationService.read(AMImplementationType.ATTR_RELEASE_POLICY_CONF, policyName);
         } catch (SyncopeClientException e) {
             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
                 implementationTO = new ImplementationTO();
                 implementationTO.setKey(policyName);
                 implementationTO.setEngine(ImplementationEngine.JAVA);
-                implementationTO.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+                implementationTO.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONF);
 
                 AllowedAttrReleasePolicyConf conf = new AllowedAttrReleasePolicyConf();
                 conf.setName("MyDefaultAttrReleasePolicyConf");
@@ -131,14 +129,13 @@ public class PolicyITCase extends AbstractITCase {
 
         ImplementationTO implementationTO = null;
         try {
-            implementationTO = implementationService.read(
-                    AMImplementationType.ACCESS_POLICY_CONFIGURATIONS, accessPolicyName);
+            implementationTO = implementationService.read(AMImplementationType.ACCESS_POLICY_CONF, accessPolicyName);
         } catch (SyncopeClientException e) {
             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
                 implementationTO = new ImplementationTO();
                 implementationTO.setKey(accessPolicyName);
                 implementationTO.setEngine(ImplementationEngine.JAVA);
-                implementationTO.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+                implementationTO.setType(AMImplementationType.ACCESS_POLICY_CONF);
 
                 DefaultAccessPolicyConf conf = new DefaultAccessPolicyConf();
                 conf.setEnabled(true);
@@ -332,8 +329,7 @@ public class PolicyITCase extends AbstractITCase {
         assertNotNull(newAuthPolicyTO);
         newAuthPolicyTO = createPolicy(PolicyType.AUTH, newAuthPolicyTO);
 
-        ImplementationTO authPolicyImplementationTO = implementationService.read(
-                AMImplementationType.AUTH_POLICY_CONFIGURATIONS, "MyDefaultAuthPolicyConf");
+        ImplementationTO authPolicyImplementationTO = implementationService.read(AMImplementationType.AUTH_POLICY_CONF, "MyDefaultAuthPolicyConf");
         assertNotNull(authPolicyImplementationTO);
         assertFalse(StringUtils.isBlank(authPolicyImplementationTO.getBody()));
 
@@ -365,8 +361,7 @@ public class PolicyITCase extends AbstractITCase {
         newAccessPolicyTO = createPolicy(PolicyType.ACCESS, newAccessPolicyTO);
         assertNotNull(newAccessPolicyTO);
 
-        ImplementationTO accessPolicyImplementationTO = implementationService.read(
-                AMImplementationType.ACCESS_POLICY_CONFIGURATIONS, "MyDefaultAccessPolicyConf");
+        ImplementationTO accessPolicyImplementationTO = implementationService.read(AMImplementationType.ACCESS_POLICY_CONF, "MyDefaultAccessPolicyConf");
         assertNotNull(accessPolicyImplementationTO);
         assertFalse(StringUtils.isBlank(accessPolicyImplementationTO.getBody()));
 
@@ -400,8 +395,7 @@ public class PolicyITCase extends AbstractITCase {
         newPolicyTO = createPolicy(PolicyType.ATTR_RELEASE, newPolicyTO);
         assertNotNull(newPolicyTO);
 
-        ImplementationTO implementationTO = implementationService.read(
-                AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS, policyName);
+        ImplementationTO implementationTO = implementationService.read(AMImplementationType.ATTR_RELEASE_POLICY_CONF, policyName);
         assertNotNull(implementationTO);
         assertFalse(StringUtils.isBlank(implementationTO.getBody()));
 
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
index 95475fd..0a109ac 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PropagationTaskITCase.java
@@ -62,9 +62,9 @@ import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.AttrSchemaType;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.MappingPurpose;
 import org.apache.syncope.common.lib.types.PatchOperation;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
@@ -91,15 +91,15 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
         ImplementationTO dateToDate = null;
         try {
             dateToLong = implementationService.read(
-                    IdMImplementationType.ITEM_TRANSFORMER, DateToLongItemTransformer.class.getSimpleName());
+                    IdRepoImplementationType.ITEM_TRANSFORMER, DateToLongItemTransformer.class.getSimpleName());
             dateToDate = implementationService.read(
-                    IdMImplementationType.ITEM_TRANSFORMER, DateToDateItemTransformer.class.getSimpleName());
+                    IdRepoImplementationType.ITEM_TRANSFORMER, DateToDateItemTransformer.class.getSimpleName());
         } catch (SyncopeClientException e) {
             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
                 dateToLong = new ImplementationTO();
                 dateToLong.setKey(DateToLongItemTransformer.class.getSimpleName());
                 dateToLong.setEngine(ImplementationEngine.JAVA);
-                dateToLong.setType(IdMImplementationType.ITEM_TRANSFORMER);
+                dateToLong.setType(IdRepoImplementationType.ITEM_TRANSFORMER);
                 dateToLong.setBody(DateToLongItemTransformer.class.getName());
                 Response response = implementationService.create(dateToLong);
                 dateToLong = implementationService.read(
@@ -109,7 +109,7 @@ public class PropagationTaskITCase extends AbstractTaskITCase {
                 dateToDate = new ImplementationTO();
                 dateToDate.setKey(DateToDateItemTransformer.class.getSimpleName());
                 dateToDate.setEngine(ImplementationEngine.JAVA);
-                dateToDate.setType(IdMImplementationType.ITEM_TRANSFORMER);
+                dateToDate.setType(IdRepoImplementationType.ITEM_TRANSFORMER);
                 dateToDate.setBody(DateToDateItemTransformer.class.getName());
                 response = implementationService.create(dateToDate);
                 dateToDate = implementationService.read(
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
index 5be88ed..d1146f4 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PullTaskITCase.java
@@ -83,6 +83,7 @@ import org.apache.syncope.common.lib.types.ImplementationEngine;
 import org.apache.syncope.common.lib.types.PolicyType;
 import org.apache.syncope.common.lib.types.ExecStatus;
 import org.apache.syncope.common.lib.types.IdMImplementationType;
+import org.apache.syncope.common.lib.types.IdRepoImplementationType;
 import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.lib.types.PullMode;
 import org.apache.syncope.common.lib.types.ResourceOperation;
@@ -497,13 +498,13 @@ public class PullTaskITCase extends AbstractTaskITCase {
         ImplementationTO transformer = null;
         try {
             transformer = implementationService.read(
-                    IdMImplementationType.ITEM_TRANSFORMER, "PrefixItemTransformer");
+                    IdRepoImplementationType.ITEM_TRANSFORMER, "PrefixItemTransformer");
         } catch (SyncopeClientException e) {
             if (e.getType().getResponseStatus() == Response.Status.NOT_FOUND) {
                 transformer = new ImplementationTO();
                 transformer.setKey("PrefixItemTransformer");
                 transformer.setEngine(ImplementationEngine.GROOVY);
-                transformer.setType(IdMImplementationType.ITEM_TRANSFORMER);
+                transformer.setType(IdRepoImplementationType.ITEM_TRANSFORMER);
                 transformer.setBody(IOUtils.toString(
                         getClass().getResourceAsStream("/PrefixItemTransformer.groovy"), StandardCharsets.UTF_8));
                 Response response = implementationService.create(transformer);
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
index 8a0d9b2..884eaee 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RealmITCase.java
@@ -220,7 +220,7 @@ public class RealmITCase extends AbstractITCase {
         ImplementationTO rule = new ImplementationTO();
         rule.setKey("TestAuthPolicy" + getUUIDString());
         rule.setEngine(ImplementationEngine.JAVA);
-        rule.setType(AMImplementationType.AUTH_POLICY_CONFIGURATIONS);
+        rule.setType(AMImplementationType.AUTH_POLICY_CONF);
         rule.setBody(POJOHelper.serialize(ruleConf));
         Response response = implementationService.create(rule);
         rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
@@ -271,7 +271,7 @@ public class RealmITCase extends AbstractITCase {
         ImplementationTO rule = new ImplementationTO();
         rule.setKey("TestAccessPolicy" + getUUIDString());
         rule.setEngine(ImplementationEngine.JAVA);
-        rule.setType(AMImplementationType.ACCESS_POLICY_CONFIGURATIONS);
+        rule.setType(AMImplementationType.ACCESS_POLICY_CONF);
         rule.setBody(POJOHelper.serialize(ruleConf));
         Response response = implementationService.create(rule);
         rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
@@ -321,7 +321,7 @@ public class RealmITCase extends AbstractITCase {
         ImplementationTO rule = new ImplementationTO();
         rule.setKey("TestAttrReleasePolicy" + getUUIDString());
         rule.setEngine(ImplementationEngine.JAVA);
-        rule.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONFIGURATIONS);
+        rule.setType(AMImplementationType.ATTR_RELEASE_POLICY_CONF);
         rule.setBody(POJOHelper.serialize(ruleConf));
         Response response = implementationService.create(rule);
         rule.setKey(response.getHeaderString(RESTHeaders.RESOURCE_KEY));
diff --git a/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java b/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java
index 9e4096d..dc9c7cf 100644
--- a/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java
+++ b/ide/netbeans/src/main/java/org/apache/syncope/ide/netbeans/view/ResourceExplorerTopComponent.java
@@ -402,7 +402,7 @@ public final class ResourceExplorerTopComponent extends TopComponent {
                                 templateClassName = "MyPasswordRule";
                                 break;
 
-                            case IdMImplementationType.ITEM_TRANSFORMER:
+                            case IdRepoImplementationType.ITEM_TRANSFORMER:
                                 templateClassName = "MyItemTransformer";
                                 break;
 


[syncope] 02/08: [SYNCOPE-160] AuthModule service

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 426b2f2a332f2246b2888aabd3537bfeb903704d
Author: Matteo Alessandroni <sk...@apache.org>
AuthorDate: Thu Apr 9 12:58:10 2020 +0200

    [SYNCOPE-160] AuthModule service
---
 .../common/lib/auth/AbstractAuthModuleConf.java    |  48 ++
 .../syncope/common/lib/auth/AuthModuleConf.java    |  35 ++
 .../common/lib/auth/GoogleMfaAuthModuleConf.java   |  99 ++++
 .../common/lib/auth/JDBCAuthModuleConf.java        |  99 ++++
 .../common/lib/auth/JaasAuthModuleConf.java        |  88 +++
 .../common/lib/auth/LDAPAuthModuleConf.java        | 126 +++++
 .../common/lib/auth/OIDCAuthModuleConf.java        | 182 +++++++
 .../common/lib/auth/RadiusAuthModuleConf.java      | 172 ++++++
 .../common/lib/auth/SAML2IdPAuthModuleConf.java    | 430 +++++++++++++++
 .../common/lib/auth/StaticAuthModuleConf.java      |  47 ++
 .../common/lib/auth/SyncopeAuthModuleConf.java     |  56 ++
 .../syncope/common/lib/auth/U2FAuthModuleConf.java |  69 +++
 .../syncope/common/lib/auth/package-info.java      |  30 ++
 .../apache/syncope/common/lib/to/AuthModuleTO.java | 132 +++++
 .../common/rest/api/service/AuthModuleService.java | 126 +++++
 .../apache/syncope/core/logic/AuthModuleLogic.java | 131 +++++
 .../rest/cxf/service/AuthModuleServiceImpl.java    |  65 +++
 .../persistence/api/dao/auth/AuthModuleDAO.java    |  37 ++
 .../persistence/api/entity/auth/AuthModule.java    |  48 ++
 .../api/entity/auth/AuthModuleItem.java            |  25 +
 .../persistence/jpa/dao/auth/JPAAuthModuleDAO.java |  68 +++
 .../persistence/jpa/entity/auth/JPAAuthModule.java | 104 ++++
 .../jpa/entity/auth/JPAAuthModuleItem.java         |  69 +++
 .../core/persistence/jpa/inner/AuthModuleTest.java | 502 ++++++++++++++++++
 .../api/data/AuthModuleDataBinder.java             |  32 ++
 .../java/data/AuthModuleDataBinderImpl.java        |  79 +++
 .../apache/syncope/fit/core/AuthModuleITCase.java  | 589 +++++++++++++++++++++
 27 files changed, 3488 insertions(+)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java
new file mode 100644
index 0000000..a4399d6
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractAuthModuleConf.java
@@ -0,0 +1,48 @@
+/*
+ * 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.auth;
+
+import java.io.Serializable;
+import javax.xml.bind.annotation.XmlSeeAlso;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType
+@XmlSeeAlso({ JaasAuthModuleConf.class, StaticAuthModuleConf.class, LDAPAuthModuleConf.class,
+    OIDCAuthModuleConf.class, GoogleMfaAuthModuleConf.class, SAML2IdPAuthModuleConf.class, U2FAuthModuleConf.class,
+    JDBCAuthModuleConf.class, SyncopeAuthModuleConf.class, RadiusAuthModuleConf.class })
+public abstract class AbstractAuthModuleConf implements Serializable, AuthModuleConf {
+
+    private static final long serialVersionUID = 4153200197344709778L;
+
+    private String name;
+
+    public AbstractAuthModuleConf() {
+        setName(getClass().getName());
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    public final void setName(final String name) {
+        this.name = name;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java
new file mode 100644
index 0000000..28c1357
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AuthModuleConf.java
@@ -0,0 +1,35 @@
+/*
+ * 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.auth;
+
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.io.Serializable;
+import javax.xml.bind.annotation.XmlTransient;
+
+@XmlTransient
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
+public interface AuthModuleConf extends Serializable {
+
+    /**
+     * Given name of related authentication module instance.
+     *
+     * @return name of this authentication module instance
+     */
+    String getName();
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java
new file mode 100644
index 0000000..a2e4168
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/GoogleMfaAuthModuleConf.java
@@ -0,0 +1,99 @@
+/*
+ * 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.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "googleMfaAuthModuleConf")
+@XmlType
+public class GoogleMfaAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -7883257599139312426L;
+
+    /**
+     * Issuer used in the barcode when dealing with device registration events.
+     * Used in the registration URL to identify CAS.
+     */
+    private String issuer = "Syncope";
+
+    /**
+     * Label used in the barcode when dealing with device registration events.
+     * Used in the registration URL to identify CAS.
+     */
+    private String label = "Syncope";
+
+    /**
+     * Length of the generated code.
+     */
+    private int codeDigits = 6;
+
+    /**
+     * The expiration time of the generated code in seconds.
+     */
+    private long timeStepSize = 30;
+
+    /**
+     * Since TOTP passwords are time-based, it is essential that
+     * the clock of both the server and
+     * the client are synchronised within
+     * the tolerance defined here as the window size.
+     */
+    private int windowSize = 3;
+
+    public String getIssuer() {
+        return issuer;
+    }
+
+    public void setIssuer(final String issuer) {
+        this.issuer = issuer;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(final String label) {
+        this.label = label;
+    }
+
+    public int getCodeDigits() {
+        return codeDigits;
+    }
+
+    public void setCodeDigits(final int codeDigits) {
+        this.codeDigits = codeDigits;
+    }
+
+    public long getTimeStepSize() {
+        return timeStepSize;
+    }
+
+    public void setTimeStepSize(final long timeStepSize) {
+        this.timeStepSize = timeStepSize;
+    }
+
+    public int getWindowSize() {
+        return windowSize;
+    }
+
+    public void setWindowSize(final int windowSize) {
+        this.windowSize = windowSize;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java
new file mode 100644
index 0000000..0141078
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JDBCAuthModuleConf.java
@@ -0,0 +1,99 @@
+/*
+ * 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.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlType;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name = "jdbcAuthModuleConf")
+@XmlType
+public class JDBCAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = 8383233437907219385L;
+
+    /**
+     * SQL query to execute. Example: {@code SELECT * FROM table WHERE name=?}.
+     */
+    private String sql;
+
+    /**
+     * Password field/column name to retrieve.
+     */
+    private String fieldPassword;
+
+    /**
+     * Boolean field that should indicate whether the account is expired.
+     */
+    private String fieldExpired;
+
+    /**
+     * Boolean field that should indicate whether the account is disabled.
+     */
+    private String fieldDisabled;
+
+    /**
+     * List of column names to fetch as user attributes.
+     */
+    private final List<String> principalAttributeList = new ArrayList<>();
+
+    public String getSql() {
+        return sql;
+    }
+
+    public void setSql(final String sql) {
+        this.sql = sql;
+    }
+
+    public String getFieldPassword() {
+        return fieldPassword;
+    }
+
+    public void setFieldPassword(final String fieldPassword) {
+        this.fieldPassword = fieldPassword;
+    }
+
+    public String getFieldExpired() {
+        return fieldExpired;
+    }
+
+    public void setFieldExpired(final String fieldExpired) {
+        this.fieldExpired = fieldExpired;
+    }
+
+    public String getFieldDisabled() {
+        return fieldDisabled;
+    }
+
+    public void setFieldDisabled(final String fieldDisabled) {
+        this.fieldDisabled = fieldDisabled;
+    }
+
+    @XmlElementWrapper(name = "principalAttributeList")
+    @XmlElement(name = "principalAttributeList")
+    @JsonProperty("principalAttributeList")
+    public List<String> getPrincipalAttributeList() {
+        return principalAttributeList;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java
new file mode 100644
index 0000000..b8627ab
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/JaasAuthModuleConf.java
@@ -0,0 +1,88 @@
+/*
+ * 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.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "jaasAuthModuleConf")
+@XmlType
+public class JaasAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -7775771400318503131L;
+
+    /**
+     * The realm that contains the login module information.
+     */
+    private String realm;
+
+    /**
+     * System property value to overwrite the realm in krb5 config.
+     */
+    private String kerberosRealmSystemProperty;
+
+    /**
+     * System property value to overwrite the kdc in krb5 config.
+     */
+    private String kerberosKdcSystemProperty;
+
+    private String loginConfigType;
+
+    private String loginConfigurationFile;
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(final String realm) {
+        this.realm = realm;
+    }
+
+    public String getKerberosRealmSystemProperty() {
+        return kerberosRealmSystemProperty;
+    }
+
+    public void setKerberosRealmSystemProperty(final String kerberosRealmSystemProperty) {
+        this.kerberosRealmSystemProperty = kerberosRealmSystemProperty;
+    }
+
+    public String getKerberosKdcSystemProperty() {
+        return kerberosKdcSystemProperty;
+    }
+
+    public void setKerberosKdcSystemProperty(final String kerberosKdcSystemProperty) {
+        this.kerberosKdcSystemProperty = kerberosKdcSystemProperty;
+    }
+
+    public String getLoginConfigType() {
+        return loginConfigType;
+    }
+
+    public void setLoginConfigType(final String loginConfigType) {
+        this.loginConfigType = loginConfigType;
+    }
+
+    public String getLoginConfigurationFile() {
+        return loginConfigurationFile;
+    }
+
+    public void setLoginConfigurationFile(final String loginConfigurationFile) {
+        this.loginConfigurationFile = loginConfigurationFile;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java
new file mode 100644
index 0000000..afd7f90
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/LDAPAuthModuleConf.java
@@ -0,0 +1,126 @@
+/*
+ * 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.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "ldapAuthModuleConf")
+@XmlType
+public class LDAPAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -471527731042579422L;
+
+    protected String searchFilter;
+
+    /**
+     * The attribute value that should be used
+     * for the authenticated username, upon a successful authentication
+     * attempt.
+     */
+    private String userIdAttribute;
+
+    /**
+     * Whether subtree searching is allowed.
+     */
+    private boolean subtreeSearch = true;
+
+    private String ldapUrl;
+
+    /**
+     * The bind DN to use when connecting to LDAP.
+     * LDAP connection configuration injected into the LDAP connection pool
+     * can be initialized with the following parameters:
+     * <ul>
+     * <li>{@code bindDn/bindCredential} provided - Use the provided credentials
+     * to bind when initializing connections.</li>
+     * <li>{@code bindDn/bindCredential} set to {@code *} - Use a fast-bind
+     * strategy to initialize the pool.</li>
+     * <li>{@code bindDn/bindCredential} set to blank - Skip connection
+     * initializing; perform operations anonymously.</li>
+     * <li>SASL mechanism provided - Use the given SASL mechanism
+     * to bind when initializing connections. </li>
+     * </ul>
+     */
+    private String bindDn;
+
+    /**
+     * The bind credential to use when connecting to LDAP.
+     */
+    private String bindCredential;
+
+    private String baseDn;
+
+    public String getSearchFilter() {
+        return searchFilter;
+    }
+
+    public void setSearchFilter(final String searchFilter) {
+        this.searchFilter = searchFilter;
+    }
+
+    public String getUserIdAttribute() {
+        return userIdAttribute;
+    }
+
+    public void setUserIdAttribute(final String userIdAttribute) {
+        this.userIdAttribute = userIdAttribute;
+    }
+
+    public boolean isSubtreeSearch() {
+        return subtreeSearch;
+    }
+
+    public void setSubtreeSearch(final boolean subtreeSearch) {
+        this.subtreeSearch = subtreeSearch;
+    }
+
+    public String getLdapUrl() {
+        return ldapUrl;
+    }
+
+    public void setLdapUrl(final String ldapUrl) {
+        this.ldapUrl = ldapUrl;
+    }
+
+    public String getBindDn() {
+        return bindDn;
+    }
+
+    public void setBindDn(final String bindDn) {
+        this.bindDn = bindDn;
+    }
+
+    public String getBindCredential() {
+        return bindCredential;
+    }
+
+    public void setBindCredential(final String bindCredential) {
+        this.bindCredential = bindCredential;
+    }
+
+    public String getBaseDn() {
+        return baseDn;
+    }
+
+    public void setBaseDn(final String baseDn) {
+        this.baseDn = baseDn;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java
new file mode 100644
index 0000000..d59b34d
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OIDCAuthModuleConf.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlType;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.syncope.common.lib.jaxb.XmlGenericMapAdapter;
+
+@XmlRootElement(name = "oidcAuthModuleConf")
+@XmlType
+public class OIDCAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -471527731042579422L;
+
+    /**
+     * The client id.
+     */
+    private String id;
+
+    /**
+     * The client secret.
+     */
+    private String secret;
+
+    /**
+     * The attribute value that should be used
+     * for the authenticated username, upon a successful authentication
+     * attempt.
+     */
+    private String userIdAttribute;
+
+    private String discoveryUri;
+
+    /**
+     * Whether an initial nonce should be to used
+     * initially for replay attack mitigation.
+     */
+    private boolean useNonce;
+
+    /**
+     * Requested scope(s).
+     */
+    private String scope;
+
+    /**
+     * The JWS algorithm to use forcefully when validating ID tokens.
+     * If none is defined, the first algorithm from metadata will be used.
+     */
+    private String preferredJwsAlgorithm;
+
+    /**
+     * Clock skew in order to account for drift, when validating id tokens.
+     */
+    private int maxClockSkew;
+
+    /**
+     * Custom parameters to send along in authZ requests, etc.
+     */
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, String> customParams = new HashMap<>(0);
+
+    /**
+     * The response mode specifies how the result of the authorization request is formatted.
+     * Possible values includes "query", "fragment", "form_post", or "web_message"
+     */
+    private String responseMode;
+
+    /**
+     * The response type tells the authorization server which grant to execute.
+     * Possibles values includes "code", "token" or "id_token".
+     */
+    private String responseType;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(final String id) {
+        this.id = id;
+    }
+
+    public String getSecret() {
+        return secret;
+    }
+
+    public void setSecret(final String secret) {
+        this.secret = secret;
+    }
+
+    public String getUserIdAttribute() {
+        return userIdAttribute;
+    }
+
+    public void setUserIdAttribute(final String userIdAttribute) {
+        this.userIdAttribute = userIdAttribute;
+    }
+
+    public String getDiscoveryUri() {
+        return discoveryUri;
+    }
+
+    public void setDiscoveryUri(final String discoveryUri) {
+        this.discoveryUri = discoveryUri;
+    }
+
+    public boolean isUseNonce() {
+        return useNonce;
+    }
+
+    public void setUseNonce(final boolean useNonce) {
+        this.useNonce = useNonce;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(final String scope) {
+        this.scope = scope;
+    }
+
+    public String getPreferredJwsAlgorithm() {
+        return preferredJwsAlgorithm;
+    }
+
+    public void setPreferredJwsAlgorithm(final String preferredJwsAlgorithm) {
+        this.preferredJwsAlgorithm = preferredJwsAlgorithm;
+    }
+
+    public int getMaxClockSkew() {
+        return maxClockSkew;
+    }
+
+    public void setMaxClockSkew(final int maxClockSkew) {
+        this.maxClockSkew = maxClockSkew;
+    }
+
+    @XmlElementWrapper(name = "customParams")
+    @XmlElement(name = "customParam")
+    @JsonProperty("customParams")
+    public Map<String, String> getCustomParams() {
+        return customParams;
+    }
+
+    public String getResponseMode() {
+        return responseMode;
+    }
+
+    public void setResponseMode(final String responseMode) {
+        this.responseMode = responseMode;
+    }
+
+    public String getResponseType() {
+        return responseType;
+    }
+
+    public void setResponseType(final String responseType) {
+        this.responseType = responseType;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java
new file mode 100644
index 0000000..7cd543c
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/RadiusAuthModuleConf.java
@@ -0,0 +1,172 @@
+/*
+ * 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.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "radiusAuthModuleConf")
+@XmlType
+public class RadiusAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -2235771400318503131L;
+
+    /**
+     * Radius protocol to use when communicating with the server.
+     */
+    private String protocol = "EAP_MSCHAPv2";
+
+    private String inetAddress;
+
+    private String sharedSecret;
+
+    private int socketTimeout;
+
+    private int authenticationPort = 1812;
+
+    private int accountingPort = 1813;
+
+    private int retries = 3;
+
+    private String nasIdentifier;
+
+    private long nasPort = -1;
+
+    private long nasPortId = -1;
+
+    private long nasRealPort = -1;
+
+    private int nasPortType = -1;
+
+    private String nasIpAddress;
+
+    private String nasIpv6Address;
+
+    public String getProtocol() {
+        return protocol;
+    }
+
+    public void setProtocol(final String protocol) {
+        this.protocol = protocol;
+    }
+
+    public int getRetries() {
+        return retries;
+    }
+
+    public void setRetries(final int retries) {
+        this.retries = retries;
+    }
+
+    public String getNasIdentifier() {
+        return nasIdentifier;
+    }
+
+    public void setNasIdentifier(final String nasIdentifier) {
+        this.nasIdentifier = nasIdentifier;
+    }
+
+    public long getNasPort() {
+        return nasPort;
+    }
+
+    public void setNasPort(final long nasPort) {
+        this.nasPort = nasPort;
+    }
+
+    public long getNasPortId() {
+        return nasPortId;
+    }
+
+    public void setNasPortId(final long nasPortId) {
+        this.nasPortId = nasPortId;
+    }
+
+    public long getNasRealPort() {
+        return nasRealPort;
+    }
+
+    public void setNasRealPort(final long nasRealPort) {
+        this.nasRealPort = nasRealPort;
+    }
+
+    public int getNasPortType() {
+        return nasPortType;
+    }
+
+    public void setNasPortType(final int nasPortType) {
+        this.nasPortType = nasPortType;
+    }
+
+    public String getNasIpAddress() {
+        return nasIpAddress;
+    }
+
+    public void setNasIpAddress(final String nasIpAddress) {
+        this.nasIpAddress = nasIpAddress;
+    }
+
+    public String getNasIpv6Address() {
+        return nasIpv6Address;
+    }
+
+    public void setNasIpv6Address(final String nasIpv6Address) {
+        this.nasIpv6Address = nasIpv6Address;
+    }
+
+    public String getInetAddress() {
+        return inetAddress;
+    }
+
+    public void setInetAddress(final String inetAddress) {
+        this.inetAddress = inetAddress;
+    }
+
+    public String getSharedSecret() {
+        return sharedSecret;
+    }
+
+    public void setSharedSecret(final String sharedSecret) {
+        this.sharedSecret = sharedSecret;
+    }
+
+    public int getSocketTimeout() {
+        return socketTimeout;
+    }
+
+    public void setSocketTimeout(final int socketTimeout) {
+        this.socketTimeout = socketTimeout;
+    }
+
+    public int getAuthenticationPort() {
+        return authenticationPort;
+    }
+
+    public void setAuthenticationPort(final int authenticationPort) {
+        this.authenticationPort = authenticationPort;
+    }
+
+    public int getAccountingPort() {
+        return accountingPort;
+    }
+
+    public void setAccountingPort(final int accountingPort) {
+        this.accountingPort = accountingPort;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java
new file mode 100644
index 0000000..03b6aed
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SAML2IdPAuthModuleConf.java
@@ -0,0 +1,430 @@
+/*
+ * 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.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "saml2IdPAuthModuleConf")
+@XmlType
+public class SAML2IdPAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -471527731042579422L;
+
+    /**
+     * The attribute value that should be used
+     * for the authenticated username, upon a successful authentication
+     * attempt.
+     */
+    private String userIdAttribute;
+
+    /**
+     * The destination binding to use
+     * when creating authentication requests.
+     */
+    private String destinationBinding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
+
+    /**
+     * The password to use when generating the SP keystore.
+     */
+    private String keystorePassword;
+
+    /**
+     * The password to use when generating the private key for the SP keystore.
+     */
+    private String privateKeyPassword;
+
+    /**
+     * Location of the keystore to use and generate the SP keystore.
+     */
+    private String keystorePath;
+
+    /**
+     * The metadata location of the identity provider that is to handle authentications.
+     */
+    private String identityProviderMetadataPath;
+
+    /**
+     * Flag to indicate whether the allow-create flags
+     * for nameid policies should be set to true, false or ignored/defined.
+     * Accepted values are true, false or undefined.
+     */
+    private String nameIdPolicyAllowCreate = "undefined";
+
+    /**
+     * Once you have an authenticated session on the identity provider, usually it won't prompt you again to enter your
+     * credentials and it will automatically generate a new assertion for you. By default, the SAML client
+     * will accept assertions based on a previous authentication for one hour.
+     * You can adjust this behavior by modifying this setting. The unit of time here is seconds.
+     */
+    private int maximumAuthenticationLifetime = 3600;
+
+    /**
+     * Maximum skew in seconds between SP and IDP clocks.
+     * This skew is added onto the {@code NotOnOrAfter} field in seconds
+     * for the SAML response validation.
+     */
+    private int acceptedSkew = 300;
+
+    /**
+     * The entity id of the SP that is used in the SP metadata generation process.
+     */
+    private String serviceProviderEntityId;
+
+    /**
+     * Location of the SP metadata to use and generate.
+     */
+    private String serviceProviderMetadataPath;
+
+    /**
+     * Whether authentication requests should be tagged as forced auth.
+     */
+    private boolean forceAuth;
+
+    /**
+     * Whether authentication requests should be tagged as passive.
+     */
+    private boolean passive;
+
+    /**
+     * Requested authentication context class in authn requests.
+     */
+    private final List<String> authnContextClassRefs = new ArrayList<>(0);
+
+    /**
+     * Specifies the comparison rule that should be used to evaluate the specified authentication methods.
+     * For example, if exact is specified, the authentication method used must match one of the authentication
+     * methods specified by the AuthnContextClassRef elements.
+     * AuthContextClassRef element require comparison rule to be used to evaluate the specified
+     * authentication methods. If not explicitly specified "exact" rule will be used by default.
+     * Other acceptable values are minimum, maximum, better.
+     */
+    private String authnContextComparisonType = "exact";
+
+    /**
+     * The key alias used in the keystore.
+     */
+    private String keystoreAlias;
+
+    /**
+     * NameID policy to request in the authentication requests.
+     */
+    private String nameIdPolicyFormat;
+
+    /**
+     * Whether metadata should be marked to request sign assertions.
+     */
+    private boolean wantsAssertionsSigned;
+
+    /**
+     * AttributeConsumingServiceIndex attribute of AuthnRequest element.
+     * The given index points out a specific AttributeConsumingService structure, declared into the
+     * Service Provider (SP)'s metadata, to be used to specify all the attributes that the Service Provider
+     * is asking to be released within the authentication assertion returned by the Identity Provider (IdP).
+     * This attribute won't be sent with the request unless a positive value (including 0) is defined.
+     */
+    private int attributeConsumingServiceIndex;
+
+    /**
+     * Allows the SAML client to select a specific ACS url from the metadata, if defined.
+     * A negative value de-activates the selection process and is the default.
+     */
+    private int assertionConsumerServiceIndex = -1;
+
+    /**
+     * Whether name qualifiers should be produced
+     * in the final saml response.
+     */
+    private boolean useNameQualifier = true;
+
+    /**
+     * Whether or not SAML SP metadata should be signed when generated.
+     */
+    private boolean signServiceProviderMetadata;
+
+    /**
+     * Whether or not the authnRequest should be signed.
+     */
+    private boolean signAuthnRequest;
+
+    /**
+     * Whether or not the Logout Request sent from the SP should be signed.
+     */
+    private boolean signServiceProviderLogoutRequest;
+
+    /**
+     * Collection of signing signature blacklisted algorithms, if any, to override the global defaults.
+     */
+    private final List<String> blackListedSignatureSigningAlgorithms = new ArrayList<>(0);
+
+    /**
+     * Collection of signing signature algorithms, if any, to override the global defaults.
+     */
+    private final List<String> signatureAlgorithms = new ArrayList<>(0);
+
+    /**
+     * Collection of signing signature reference digest methods, if any, to override the global defaults.
+     */
+    private final List<String> signatureReferenceDigestMethods = new ArrayList<>(0);
+
+    /**
+     * The signing signature canonicalization algorithm, if any, to override the global defaults.
+     */
+    private String signatureCanonicalizationAlgorithm;
+
+    /**
+     * Provider name set for the saml authentication request.
+     * Sets the human-readable name of the requester for use by
+     * the presenter's user agent or the identity provider.
+     */
+    private String providerName;
+
+    public String getUserIdAttribute() {
+        return userIdAttribute;
+    }
+
+    public void setUserIdAttribute(final String userIdAttribute) {
+        this.userIdAttribute = userIdAttribute;
+    }
+
+    public String getDestinationBinding() {
+        return destinationBinding;
+    }
+
+    public void setDestinationBinding(final String destinationBinding) {
+        this.destinationBinding = destinationBinding;
+    }
+
+    public String getKeystorePassword() {
+        return keystorePassword;
+    }
+
+    public void setKeystorePassword(final String keystorePassword) {
+        this.keystorePassword = keystorePassword;
+    }
+
+    public String getPrivateKeyPassword() {
+        return privateKeyPassword;
+    }
+
+    public void setPrivateKeyPassword(final String privateKeyPassword) {
+        this.privateKeyPassword = privateKeyPassword;
+    }
+
+    public String getKeystorePath() {
+        return keystorePath;
+    }
+
+    public void setKeystorePath(final String keystorePath) {
+        this.keystorePath = keystorePath;
+    }
+
+    public String getIdentityProviderMetadataPath() {
+        return identityProviderMetadataPath;
+    }
+
+    public void setIdentityProviderMetadataPath(final String identityProviderMetadataPath) {
+        this.identityProviderMetadataPath = identityProviderMetadataPath;
+    }
+
+    public int getMaximumAuthenticationLifetime() {
+        return maximumAuthenticationLifetime;
+    }
+
+    public void setMaximumAuthenticationLifetime(final int maximumAuthenticationLifetime) {
+        this.maximumAuthenticationLifetime = maximumAuthenticationLifetime;
+    }
+
+    public int getAcceptedSkew() {
+        return acceptedSkew;
+    }
+
+    public void setAcceptedSkew(final int acceptedSkew) {
+        this.acceptedSkew = acceptedSkew;
+    }
+
+    public String getServiceProviderEntityId() {
+        return serviceProviderEntityId;
+    }
+
+    public void setServiceProviderEntityId(final String serviceProviderEntityId) {
+        this.serviceProviderEntityId = serviceProviderEntityId;
+    }
+
+    public String getServiceProviderMetadataPath() {
+        return serviceProviderMetadataPath;
+    }
+
+    public void setServiceProviderMetadataPath(final String serviceProviderMetadataPath) {
+        this.serviceProviderMetadataPath = serviceProviderMetadataPath;
+    }
+
+    public boolean isForceAuth() {
+        return forceAuth;
+    }
+
+    public void setForceAuth(final boolean forceAuth) {
+        this.forceAuth = forceAuth;
+    }
+
+    public boolean isPassive() {
+        return passive;
+    }
+
+    public void setPassive(final boolean passive) {
+        this.passive = passive;
+    }
+
+    public String getNameIdPolicyAllowCreate() {
+        return nameIdPolicyAllowCreate;
+    }
+
+    public void setNameIdPolicyAllowCreate(final String nameIdPolicyAllowCreate) {
+        this.nameIdPolicyAllowCreate = nameIdPolicyAllowCreate;
+    }
+
+    @XmlElementWrapper(name = "authnContextClassRefs")
+    @XmlElement(name = "authnContextClassRef")
+    @JsonProperty("authnContextClassRefs")
+    public List<String> getAuthnContextClassRefs() {
+        return authnContextClassRefs;
+    }
+
+    public String getAuthnContextComparisonType() {
+        return authnContextComparisonType;
+    }
+
+    public void setAuthnContextComparisonType(final String authnContextComparisonType) {
+        this.authnContextComparisonType = authnContextComparisonType;
+    }
+
+    public String getKeystoreAlias() {
+        return keystoreAlias;
+    }
+
+    public void setKeystoreAlias(final String keystoreAlias) {
+        this.keystoreAlias = keystoreAlias;
+    }
+
+    public String getNameIdPolicyFormat() {
+        return nameIdPolicyFormat;
+    }
+
+    public void setNameIdPolicyFormat(final String nameIdPolicyFormat) {
+        this.nameIdPolicyFormat = nameIdPolicyFormat;
+    }
+
+    public boolean isWantsAssertionsSigned() {
+        return wantsAssertionsSigned;
+    }
+
+    public void setWantsAssertionsSigned(final boolean wantsAssertionsSigned) {
+        this.wantsAssertionsSigned = wantsAssertionsSigned;
+    }
+
+    public int getAttributeConsumingServiceIndex() {
+        return attributeConsumingServiceIndex;
+    }
+
+    public void setAttributeConsumingServiceIndex(final int attributeConsumingServiceIndex) {
+        this.attributeConsumingServiceIndex = attributeConsumingServiceIndex;
+    }
+
+    public int getAssertionConsumerServiceIndex() {
+        return assertionConsumerServiceIndex;
+    }
+
+    public void setAssertionConsumerServiceIndex(final int assertionConsumerServiceIndex) {
+        this.assertionConsumerServiceIndex = assertionConsumerServiceIndex;
+    }
+
+    public boolean isUseNameQualifier() {
+        return useNameQualifier;
+    }
+
+    public void setUseNameQualifier(final boolean useNameQualifier) {
+        this.useNameQualifier = useNameQualifier;
+    }
+
+    public boolean isSignServiceProviderMetadata() {
+        return signServiceProviderMetadata;
+    }
+
+    public void setSignServiceProviderMetadata(final boolean signServiceProviderMetadata) {
+        this.signServiceProviderMetadata = signServiceProviderMetadata;
+    }
+
+    public boolean isSignAuthnRequest() {
+        return signAuthnRequest;
+    }
+
+    public void setSignAuthnRequest(final boolean signAuthnRequest) {
+        this.signAuthnRequest = signAuthnRequest;
+    }
+
+    public boolean isSignServiceProviderLogoutRequest() {
+        return signServiceProviderLogoutRequest;
+    }
+
+    public void setSignServiceProviderLogoutRequest(final boolean signServiceProviderLogoutRequest) {
+        this.signServiceProviderLogoutRequest = signServiceProviderLogoutRequest;
+    }
+
+    @XmlElementWrapper(name = "blackListedSignatureSigningAlgorithms")
+    @XmlElement(name = "blackListedSignatureSigningAlgorithm")
+    @JsonProperty("blackListedSignatureSigningAlgorithms")
+    public List<String> getBlackListedSignatureSigningAlgorithms() {
+        return blackListedSignatureSigningAlgorithms;
+    }
+
+    @XmlElementWrapper(name = "signatureAlgorithms")
+    @XmlElement(name = "signatureAlgorithm")
+    @JsonProperty("signatureAlgorithms")
+    public List<String> getSignatureAlgorithms() {
+        return signatureAlgorithms;
+    }
+
+    @XmlElementWrapper(name = "signatureReferenceDigestMethods")
+    @XmlElement(name = "signatureReferenceDigestMethod")
+    @JsonProperty("signatureReferenceDigestMethods")
+    public List<String> getSignatureReferenceDigestMethods() {
+        return signatureReferenceDigestMethods;
+    }
+
+    public String getSignatureCanonicalizationAlgorithm() {
+        return signatureCanonicalizationAlgorithm;
+    }
+
+    public void setSignatureCanonicalizationAlgorithm(final String signatureCanonicalizationAlgorithm) {
+        this.signatureCanonicalizationAlgorithm = signatureCanonicalizationAlgorithm;
+    }
+
+    public String getProviderName() {
+        return providerName;
+    }
+
+    public void setProviderName(final String providerName) {
+        this.providerName = providerName;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java
new file mode 100644
index 0000000..60d2751
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/StaticAuthModuleConf.java
@@ -0,0 +1,47 @@
+/*
+ * 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.auth;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import javax.xml.bind.annotation.XmlType;
+import java.util.HashMap;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import org.apache.syncope.common.lib.jaxb.XmlGenericMapAdapter;
+
+@XmlRootElement(name = "staticAuthModuleConf")
+@XmlType
+public class StaticAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -7775771400318503131L;
+
+    @XmlJavaTypeAdapter(XmlGenericMapAdapter.class)
+    private final Map<String, String> users = new HashMap<>();
+
+    @XmlElementWrapper(name = "users")
+    @XmlElement(name = "user")
+    @JsonProperty("users")
+    public Map<String, String> getUsers() {
+        return users;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java
new file mode 100644
index 0000000..2ac2192
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/SyncopeAuthModuleConf.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.lib.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "syncopeAuthModuleConf")
+@XmlType
+public class SyncopeAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -3334329948161152222L;
+
+    /**
+     * Syncope domain used for authentication, etc.
+     */
+    private String domain = "Master";
+
+    /**
+     * Syncope instance URL primary used for REST.
+     */
+    private String url;
+
+    public String getDomain() {
+        return domain;
+    }
+
+    public void setDomain(final String domain) {
+        this.domain = domain;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    public void setUrl(final String url) {
+        this.url = url;
+    }
+
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java
new file mode 100644
index 0000000..cc127da
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/U2FAuthModuleConf.java
@@ -0,0 +1,69 @@
+/*
+ * 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.auth;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+@XmlRootElement(name = "u2fAuthModuleConf")
+@XmlType
+public class U2FAuthModuleConf extends AbstractAuthModuleConf {
+
+    private static final long serialVersionUID = -1235771400318503131L;
+
+    private long expireRegistrations = 30;
+
+    private String expireRegistrationsTimeUnit = "SECONDS";
+
+    private long expireDevices = 30;
+
+    private String expireDevicesTimeUnit = "DAYS";
+
+    public long getExpireRegistrations() {
+        return expireRegistrations;
+    }
+
+    public void setExpireRegistrations(final long expireRegistrations) {
+        this.expireRegistrations = expireRegistrations;
+    }
+
+    public String getExpireRegistrationsTimeUnit() {
+        return expireRegistrationsTimeUnit;
+    }
+
+    public void setExpireRegistrationsTimeUnit(final String expireRegistrationsTimeUnit) {
+        this.expireRegistrationsTimeUnit = expireRegistrationsTimeUnit;
+    }
+
+    public long getExpireDevices() {
+        return expireDevices;
+    }
+
+    public void setExpireDevices(final long expireDevices) {
+        this.expireDevices = expireDevices;
+    }
+
+    public String getExpireDevicesTimeUnit() {
+        return expireDevicesTimeUnit;
+    }
+
+    public void setExpireDevicesTimeUnit(final String expireDevicesTimeUnit) {
+        this.expireDevicesTimeUnit = expireDevicesTimeUnit;
+    }
+}
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java
new file mode 100644
index 0000000..e3765a0
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+@XmlSchema(namespace = SyncopeConstants.NS)
+@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(type = Date.class, value = DateAdapter.class), })
+package org.apache.syncope.common.lib.auth;
+
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.jaxb.DateAdapter;
+
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
+
+import java.util.Date;
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
new file mode 100644
index 0000000..492227f
--- /dev/null
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/to/AuthModuleTO.java
@@ -0,0 +1,132 @@
+/*
+ * 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 com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.syncope.common.lib.BaseBean;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+
+@XmlRootElement(name = "authModule")
+@XmlType
+public class AuthModuleTO extends BaseBean implements EntityTO {
+
+    private static final long serialVersionUID = -7490425997956703057L;
+
+    private String key;
+
+    private String name;
+
+    private String description;
+
+    private final List<ItemTO> profileItems = new ArrayList<>();
+
+    private AuthModuleConf conf;
+
+    @Override
+    public String getKey() {
+        return key;
+    }
+
+    @Override
+    public void setKey(final String key) {
+        this.key = key;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    public AuthModuleConf getConf() {
+        return conf;
+    }
+
+    public void setConf(final AuthModuleConf conf) {
+        this.conf = conf;
+    }
+
+    @XmlElementWrapper(name = "profileItems")
+    @XmlElement(name = "profileItem")
+    @JsonProperty("profileItems")
+    public List<ItemTO> getProfileItems() {
+        return profileItems;
+    }
+
+    public boolean add(final ItemTO item) {
+        return Optional.ofNullable(item)
+                .filter(itemTO -> this.profileItems.contains(itemTO) || this.profileItems.add(itemTO)).isPresent();
+    }
+
+    public boolean remove(final ItemTO item) {
+        return this.profileItems.remove(item);
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        AuthModuleTO other = (AuthModuleTO) obj;
+        return new EqualsBuilder().
+                append(key, other.key).
+                append(name, other.name).
+                append(description, other.description).
+                append(profileItems, other.profileItems).
+                append(conf, other.conf).
+                build();
+    }
+
+    @Override
+    public int hashCode() {
+        return new HashCodeBuilder().
+                append(key).
+                append(name).
+                append(description).
+                append(profileItems).
+                append(conf).
+                build();
+    }
+
+}
diff --git a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
new file mode 100644
index 0000000..83e6015
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/AuthModuleService.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.common.rest.api.service;
+
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.annotations.headers.Header;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+
+/**
+ * REST operations for authentication modules.
+ */
+@Tag(name = "AuthModules")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("authModules")
+public interface AuthModuleService extends JAXRSService {
+
+    /**
+     * Returns the authentication module matching the given key.
+     *
+     * @param key key of requested authentication module
+     * @return authentication module with matching id
+     */
+    @GET
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    AuthModuleTO read(
+            @NotNull @PathParam("key") String key);
+
+    /**
+     * Returns a list of authentication modules of the matching type.
+     *
+     * @return list of authentication modules with matching type
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    List<AuthModuleTO> list();
+
+    /**
+     * Create a new authentication module.
+     *
+     * @param authModuleTO AuthModule to be created (needs to match type)
+     * @return Response object featuring Location header of created authentication module
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "201",
+                    description = "AuthModule 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 AuthModuleTO authModuleTO);
+
+    /**
+     * Updates authentication module matching the given key.
+     *
+     * @param authModuleTO AuthModule to replace existing authentication module
+     */
+    @Parameter(name = "key", description = "AuthModule's key", in = ParameterIn.PATH, schema =
+            @Schema(type = "string"))
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @PUT
+    @Path("{key}")
+    @Consumes({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void update(
+            @NotNull AuthModuleTO authModuleTO);
+
+    /**
+     * Delete authentication module matching the given key.
+     *
+     * @param key key of authentication module to be deleted
+     */
+    @ApiResponses(
+            @ApiResponse(responseCode = "204", description = "Operation was successful"))
+    @DELETE
+    @Path("{key}")
+    @Produces({ MediaType.APPLICATION_JSON, RESTHeaders.APPLICATION_YAML, MediaType.APPLICATION_XML })
+    void delete(
+            @NotNull @PathParam("key") String key);
+}
diff --git a/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java
new file mode 100644
index 0000000..538dc82
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/AuthModuleLogic.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic;
+
+import static org.apache.syncope.core.logic.AbstractLogic.LOG;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.lib.types.AMEntitlement;
+import org.apache.syncope.core.persistence.api.dao.NotFoundException;
+import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.provisioning.api.data.AuthModuleDataBinder;
+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 AuthModuleLogic extends AbstractTransactionalLogic<AuthModuleTO> {
+
+    @Autowired
+    private AuthModuleDataBinder binder;
+
+    @Autowired
+    private AuthModuleDAO authModuleDAO;
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_CREATE + "')")
+    public AuthModuleTO create(final AuthModuleTO authModuleTO) {
+        return binder.getAuthModuleTO(authModuleDAO.save(binder.create(authModuleTO)));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_UPDATE + "')")
+    public AuthModuleTO update(final AuthModuleTO authModuleTO) {
+        AuthModule authModule = authModuleDAO.find(authModuleTO.getKey());
+        if (authModule == null) {
+            throw new NotFoundException("AuthModule " + authModuleTO.getKey() + " not found");
+        }
+
+        return binder.getAuthModuleTO(authModuleDAO.save(binder.update(authModule, authModuleTO)));
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_LIST + "')")
+    @Transactional(readOnly = true)
+    public List<AuthModuleTO> list() {
+        return authModuleDAO.findAll().stream().
+                filter(Objects::nonNull).
+                map(authModule -> {
+                    AuthModuleTO result = null;
+                    try {
+                        result = binder.getAuthModuleTO(authModule);
+                    } catch (NotFoundException e) {
+                        LOG.error("Authentication module '{}' not found", authModule.getName());
+                    }
+
+                    return result;
+                }).collect(Collectors.toList());
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_READ + "')")
+    @Transactional(readOnly = true)
+    public AuthModuleTO read(final String key) {
+        AuthModule authModule = authModuleDAO.find(key);
+        if (authModule == null) {
+            throw new NotFoundException("AuthModule " + key + " not found");
+        }
+
+        return binder.getAuthModuleTO(authModule);
+    }
+
+    @PreAuthorize("hasRole('" + AMEntitlement.AUTH_MODULE_DELETE + "')")
+    public AuthModuleTO delete(final String key) {
+        AuthModule authModule = authModuleDAO.find(key);
+        if (authModule == null) {
+            throw new NotFoundException("AuthModule " + key + " not found");
+        }
+
+        AuthModuleTO deleted = binder.getAuthModuleTO(authModule);
+        authModuleDAO.delete(authModule);
+
+        return deleted;
+    }
+
+    @Override
+    protected AuthModuleTO resolveReference(final Method method, final Object... args)
+            throws UnresolvedReferenceException {
+
+        String key = null;
+
+        if (ArrayUtils.isNotEmpty(args)) {
+            for (int i = 0; key == null && i < args.length; i++) {
+                if (args[i] instanceof String) {
+                    key = (String) args[i];
+                } else if (args[i] instanceof AuthModuleTO) {
+                    key = ((AuthModuleTO) args[i]).getKey();
+                }
+            }
+        }
+
+        if (key != null) {
+            try {
+                return binder.getAuthModuleTO(authModuleDAO.find(key));
+            } catch (Throwable ignore) {
+                LOG.debug("Unresolved reference", ignore);
+                throw new UnresolvedReferenceException(ignore);
+            }
+        }
+
+        throw new UnresolvedReferenceException();
+    }
+}
diff --git a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.java
new file mode 100644
index 0000000..68bdd90
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AuthModuleServiceImpl.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.core.rest.cxf.service;
+
+import java.net.URI;
+import java.util.List;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.service.AuthModuleService;
+import org.apache.syncope.core.logic.AuthModuleLogic;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class AuthModuleServiceImpl extends AbstractServiceImpl implements AuthModuleService {
+
+    @Autowired
+    private AuthModuleLogic logic;
+
+    @Override
+    public Response create(final AuthModuleTO authModuleTO) {
+        AuthModuleTO authModule = logic.create(authModuleTO);
+        URI location = uriInfo.getAbsolutePathBuilder().path(authModule.getKey()).build();
+        return Response.created(location).
+                header(RESTHeaders.RESOURCE_KEY, authModule.getKey()).
+                build();
+    }
+
+    @Override
+    public void delete(final String key) {
+        logic.delete(key);
+    }
+
+    @Override
+    public List<AuthModuleTO> list() {
+        return logic.list();
+    }
+
+    @Override
+    public AuthModuleTO read(final String key) {
+        return logic.read(key);
+    }
+
+    @Override
+    public void update(final AuthModuleTO authModuleTO) {
+        logic.update(authModuleTO);
+    }
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java
new file mode 100644
index 0000000..fc5e18c
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/dao/auth/AuthModuleDAO.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.dao.auth;
+
+import org.apache.syncope.core.persistence.api.dao.DAO;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+
+public interface AuthModuleDAO extends DAO<AuthModule> {
+
+    AuthModule find(String key);
+
+    List<AuthModule> findAll();
+
+    AuthModule save(AuthModule authModule);
+
+    void delete(String key);
+
+    void delete(AuthModule authModule);
+
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
new file mode 100644
index 0000000..5d2393e
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModule.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity.auth;
+
+import java.util.List;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.core.persistence.api.entity.Entity;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+
+public interface AuthModule extends Entity {
+
+    String getName();
+
+    void setName(String name);
+
+    String getDescription();
+
+    void setDescription(String description);
+
+    /**
+     * Specify the mapping items for the attributes fetched from the source.
+     *
+     * @return list of mapping items
+     */
+    List<? extends Item> getProfileItems();
+
+    boolean add(Item profileItem);
+
+    AuthModuleConf getConf();
+
+    void setConf(AuthModuleConf description);
+}
diff --git a/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
new file mode 100644
index 0000000..9f249e4
--- /dev/null
+++ b/core/persistence-api/src/main/java/org/apache/syncope/core/persistence/api/entity/auth/AuthModuleItem.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.api.entity.auth;
+
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+
+public interface AuthModuleItem extends Item {
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.java
new file mode 100644
index 0000000..dd765ad
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/dao/auth/JPAAuthModuleDAO.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.core.persistence.jpa.dao.auth;
+
+import org.apache.syncope.core.persistence.jpa.dao.AbstractDAO;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+import javax.persistence.TypedQuery;
+import java.util.List;
+import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.jpa.entity.auth.JPAAuthModule;
+
+@Repository
+public class JPAAuthModuleDAO extends AbstractDAO<AuthModule> implements AuthModuleDAO {
+
+    @Override
+    public AuthModule find(final String key) {
+        return entityManager().find(JPAAuthModule.class, key);
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public List<AuthModule> findAll() {
+        TypedQuery<AuthModule> query = entityManager().createQuery("SELECT e FROM " + JPAAuthModule.class.
+                getSimpleName() + " e", AuthModule.class);
+
+        return query.getResultList();
+    }
+
+    @Transactional(readOnly = true)
+    @Override
+    public AuthModule save(final AuthModule authModule) {
+        return entityManager().merge(authModule);
+    }
+
+    @Override
+    public void delete(final String key) {
+        AuthModule authModule = find(key);
+        if (authModule == null) {
+            return;
+        }
+
+        delete(authModule);
+    }
+
+    @Override
+    public void delete(final AuthModule authModule) {
+        entityManager().remove(authModule);
+    }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
new file mode 100644
index 0000000..e2b6586
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModule.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Lob;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.api.entity.resource.Item;
+import org.apache.syncope.core.persistence.jpa.entity.AbstractGeneratedKeyEntity;
+import org.apache.syncope.core.provisioning.api.serialization.POJOHelper;
+
+@Entity
+@Table(name = JPAAuthModule.TABLE)
+public class JPAAuthModule extends AbstractGeneratedKeyEntity implements AuthModule {
+
+    public static final String TABLE = "AuthModule";
+
+    private static final long serialVersionUID = 5681033638234853077L;
+
+    @Column(unique = true, nullable = false)
+    private String name;
+
+    @Column(nullable = false)
+    private String description;
+
+    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "mapping")
+    private final List<JPAAuthModuleItem> profileItems = new ArrayList<>();
+
+    @Lob
+    private String jsonConf;
+
+    @Override
+    public String getDescription() {
+        return description;
+    }
+
+    @Override
+    public void setDescription(final String description) {
+        this.description = description;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public void setName(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public List<? extends Item> getProfileItems() {
+        return profileItems;
+    }
+
+    @Override
+    public boolean add(final Item profileItem) {
+        checkType(profileItem, JPAAuthModuleItem.class);
+        return profileItems.contains((JPAAuthModuleItem) profileItem)
+                || profileItems.add((JPAAuthModuleItem) profileItem);
+    }
+
+    @Override
+    public AuthModuleConf getConf() {
+        AuthModuleConf conf = null;
+        if (!StringUtils.isBlank(jsonConf)) {
+            conf = POJOHelper.deserialize(jsonConf, AuthModuleConf.class);
+        }
+
+        return conf;
+    }
+
+    @Override
+    public void setConf(final AuthModuleConf conf) {
+        jsonConf = POJOHelper.serialize(conf);
+    }
+
+}
diff --git a/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
new file mode 100644
index 0000000..bc92d49
--- /dev/null
+++ b/core/persistence-jpa/src/main/java/org/apache/syncope/core/persistence/jpa/entity/auth/JPAAuthModuleItem.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.entity.auth;
+
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.Cacheable;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import org.apache.syncope.common.lib.types.IdMImplementationType;
+import org.apache.syncope.core.persistence.api.entity.Implementation;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
+import org.apache.syncope.core.persistence.jpa.entity.JPAImplementation;
+import org.apache.syncope.core.persistence.jpa.entity.resource.AbstractItem;
+
+@Entity
+@Table(name = JPAAuthModuleItem.TABLE)
+@Cacheable
+public class JPAAuthModuleItem extends AbstractItem implements AuthModuleItem {
+
+    public static final String TABLE = "AuthModuleItem";
+
+    private static final long serialVersionUID = 3165440920144995781L;
+
+    @ManyToMany(fetch = FetchType.EAGER)
+    @JoinTable(name = TABLE + "Transformer",
+            joinColumns =
+            @JoinColumn(name = "item_id"),
+            inverseJoinColumns =
+            @JoinColumn(name = "implementation_id"),
+            uniqueConstraints =
+            @UniqueConstraint(columnNames = { "item_id", "implementation_id" }))
+    private final List<JPAImplementation> transformers = new ArrayList<>();
+
+    @Override
+    public boolean add(final Implementation transformer) {
+        checkType(transformer, JPAImplementation.class);
+        checkImplementationType(transformer, IdMImplementationType.ITEM_TRANSFORMER);
+        return transformers.contains((JPAImplementation) transformer)
+                || this.transformers.add((JPAImplementation) transformer);
+    }
+
+    @Override
+    public List<? extends Implementation> getTransformers() {
+        return transformers;
+    }
+
+}
diff --git a/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
new file mode 100644
index 0000000..27c0d22
--- /dev/null
+++ b/core/persistence-jpa/src/test/java/org/apache/syncope/core/persistence/jpa/inner/AuthModuleTest.java
@@ -0,0 +1,502 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.inner;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.UUID;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JaasAuthModuleConf;
+import org.apache.syncope.common.lib.auth.LDAPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.OIDCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.RadiusAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SAML2IdPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.StaticAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
+import org.apache.syncope.common.lib.auth.U2FAuthModuleConf;
+import org.apache.syncope.core.persistence.api.dao.auth.AuthModuleDAO;
+import org.apache.syncope.core.persistence.jpa.AbstractTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModuleItem;
+
+@Transactional("Master")
+public class AuthModuleTest extends AbstractTest {
+
+    @Autowired
+    private AuthModuleDAO authModuleDAO;
+
+    @Test
+    public void find() {
+        AuthModule authModule = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(authModule);
+
+        authModule = authModuleDAO.find(UUID.randomUUID().toString());
+        assertNull(authModule);
+    }
+
+    @Test
+    public void findAll() {
+        List<AuthModule> modules = authModuleDAO.findAll();
+        assertNotNull(modules);
+        assertFalse(modules.isEmpty());
+        assertTrue(modules.size() >= 10);
+    }
+
+    @Test
+    public void findByAuthModuleImpl() {
+        AuthModule ldapAuthModule = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(ldapAuthModule);
+        AuthModule jdbcAuthModule = authModuleDAO.find("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jdbcAuthModule);
+        AuthModule googleMfaAuthModule = authModuleDAO.find("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(googleMfaAuthModule);
+        AuthModule oidcAuthModule = authModuleDAO.find("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(oidcAuthModule);
+        AuthModule saml2IdPAuthModule = authModuleDAO.find("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(saml2IdPAuthModule);
+        AuthModule jaasAuthModule = authModuleDAO.find("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jaasAuthModule);
+        AuthModule staticAuthModule = authModuleDAO.find("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(staticAuthModule);
+        AuthModule syncopeAuthModule = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(syncopeAuthModule);
+        AuthModule radiusAuthModule = authModuleDAO.find("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(radiusAuthModule);
+        AuthModule u2fAuthModule = authModuleDAO.find("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(u2fAuthModule);
+
+        assertTrue(isSpecificConf(ldapAuthModule.getConf(), LDAPAuthModuleConf.class));
+        assertFalse(isSpecificConf(ldapAuthModule.getConf(), JDBCAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(jdbcAuthModule.getConf(), JDBCAuthModuleConf.class));
+        assertFalse(isSpecificConf(jdbcAuthModule.getConf(), GoogleMfaAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(googleMfaAuthModule.getConf(), GoogleMfaAuthModuleConf.class));
+        assertFalse(isSpecificConf(googleMfaAuthModule.getConf(), OIDCAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(oidcAuthModule.getConf(), OIDCAuthModuleConf.class));
+        assertFalse(isSpecificConf(oidcAuthModule.getConf(), SAML2IdPAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(saml2IdPAuthModule.getConf(), SAML2IdPAuthModuleConf.class));
+        assertFalse(isSpecificConf(saml2IdPAuthModule.getConf(), JaasAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(jaasAuthModule.getConf(), JaasAuthModuleConf.class));
+        assertFalse(isSpecificConf(jaasAuthModule.getConf(), StaticAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(staticAuthModule.getConf(), StaticAuthModuleConf.class));
+        assertFalse(isSpecificConf(staticAuthModule.getConf(), SyncopeAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(syncopeAuthModule.getConf(), SyncopeAuthModuleConf.class));
+        assertFalse(isSpecificConf(syncopeAuthModule.getConf(), RadiusAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(radiusAuthModule.getConf(), RadiusAuthModuleConf.class));
+        assertFalse(isSpecificConf(radiusAuthModule.getConf(), U2FAuthModuleConf.class));
+
+        assertTrue(isSpecificConf(u2fAuthModule.getConf(), U2FAuthModuleConf.class));
+        assertFalse(isSpecificConf(u2fAuthModule.getConf(), LDAPAuthModuleConf.class));
+    }
+
+    @Test
+    public void findByType() {
+        List<AuthModule> authModules = authModuleDAO.findAll();
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), LDAPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultLDAPAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JDBCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJDBCAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), GoogleMfaAuthModuleConf.class)
+                && authModule.getName().equals("DefaultGoogleMfaAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), OIDCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultOIDCAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SAML2IdPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSAML2IdPAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JaasAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJaasAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), StaticAuthModuleConf.class)
+                && authModule.getName().equals("DefaultStaticAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SyncopeAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSyncopeAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), RadiusAuthModuleConf.class)
+                && authModule.getName().equals("DefaultRadiusAuthModule")));
+        assertTrue(authModules.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), U2FAuthModuleConf.class)
+                && authModule.getName().equals("DefaultU2FAuthModule")));
+    }
+
+    @Test
+    public void saveWithStaticModule() {
+        StaticAuthModuleConf conf = new StaticAuthModuleConf();
+        conf.getUsers().put("user1", UUID.randomUUID().toString());
+        conf.getUsers().put("user2", "user2Password123");
+
+        saveAuthModule("StaticAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithJaasModule() {
+        JaasAuthModuleConf conf = new JaasAuthModuleConf();
+        conf.setKerberosKdcSystemProperty("sample-value");
+        conf.setKerberosRealmSystemProperty("sample-value");
+        conf.setLoginConfigType("JavaLoginConfig");
+        conf.setRealm("SYNCOPE");
+        conf.setLoginConfigurationFile("/opt/jaas/login.conf");
+
+        saveAuthModule("JaasAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithLdapModule() {
+        LDAPAuthModuleConf conf = new LDAPAuthModuleConf();
+        conf.setBaseDn("dc=example,dc=org");
+        conf.setSearchFilter("cn={user}");
+        conf.setSubtreeSearch(true);
+        conf.setLdapUrl("ldap://localhost:1389");
+        conf.setUserIdAttribute("uid");
+        conf.setBindCredential("Password");
+
+        saveAuthModule("LDAPAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithGoogleAuthenticatorModule() {
+        GoogleMfaAuthModuleConf conf = new GoogleMfaAuthModuleConf();
+        conf.setCodeDigits(6);
+        conf.setIssuer("SyncopeTest");
+        conf.setLabel("Syncope");
+        conf.setTimeStepSize(30);
+        conf.setWindowSize(3);
+
+        saveAuthModule("GoogleMfaAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithOIDCAuthModule() {
+        OIDCAuthModuleConf conf = new OIDCAuthModuleConf();
+        conf.setId("OIDCTestId");
+        conf.setDiscoveryUri("www.testurl.com");
+        conf.setUserIdAttribute("username");
+        conf.setResponseType("code");
+        conf.setScope("openid email profile");
+
+        saveAuthModule("OIDCAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithJDBCModule() {
+        JDBCAuthModuleConf conf = new JDBCAuthModuleConf();
+        conf.setSql("SELECT * FROM table WHERE name=?");
+        conf.setFieldPassword("password");
+        conf.getPrincipalAttributeList().addAll(List.of("sn", "cn:commonName", "givenName"));
+
+        saveAuthModule("JDBCAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithSyncopeModule() {
+        SyncopeAuthModuleConf conf = new SyncopeAuthModuleConf();
+        conf.setDomain("Master");
+        conf.setUrl("http://mydomain.com/syncope/rest");
+
+        saveAuthModule("SyncopeAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithSAML2IdPModule() {
+        SAML2IdPAuthModuleConf conf = new SAML2IdPAuthModuleConf();
+        conf.setServiceProviderEntityId("testEntityId");
+        conf.setProviderName("testProviderName");
+        conf.setServiceProviderMetadataPath("file:/etc/metadata");
+
+        saveAuthModule("SAML2IdPAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithRadiusModule() {
+        RadiusAuthModuleConf conf = new RadiusAuthModuleConf();
+        conf.setProtocol("MSCHAPv2");
+        conf.setInetAddress("1.2.3.4");
+        conf.setSharedSecret("xyz");
+        conf.setSocketTimeout(40);
+
+        saveAuthModule("RadiusAuthModuleTest", conf);
+    }
+
+    @Test
+    public void saveWithU2FModule() {
+        U2FAuthModuleConf conf = new U2FAuthModuleConf();
+        conf.setExpireDevices(50);
+
+        saveAuthModule("U2FAuthModuleTest", conf);
+    }
+
+    @Test
+    public void updateWithLDAPModule() {
+        AuthModule module = authModuleDAO.find("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        LDAPAuthModuleConf.class.cast(conf).setBaseDn("dc=example2,dc=org");
+        LDAPAuthModuleConf.class.cast(conf).setSearchFilter("cn={user2}");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("dc=example2,dc=org", LDAPAuthModuleConf.class.cast(found.getConf()).getBaseDn());
+        assertEquals("cn={user2}", LDAPAuthModuleConf.class.cast(found.getConf()).getSearchFilter());
+    }
+
+    @Test
+    public void updateWithJDBCModule() {
+        AuthModule module = authModuleDAO.find("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        JDBCAuthModuleConf.class.cast(conf).setSql("SELECT * FROM otherTable WHERE name=?");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("SELECT * FROM otherTable WHERE name=?",
+                JDBCAuthModuleConf.class.cast(found.getConf()).getSql());
+    }
+
+    @Test
+    public void updateWithGoogleMfaModule() {
+        AuthModule module = authModuleDAO.find("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        GoogleMfaAuthModuleConf.class.cast(conf).setLabel("newLabel");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("newLabel", GoogleMfaAuthModuleConf.class.cast(found.getConf()).getLabel());
+    }
+
+    @Test
+    public void updateWithSAML2IdPModule() {
+        AuthModule module = authModuleDAO.find("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderEntityId("newEntityId");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("newEntityId", SAML2IdPAuthModuleConf.class.cast(found.getConf()).getServiceProviderEntityId());
+    }
+
+    @Test
+    public void updateWithOIDCModule() {
+        AuthModule module = authModuleDAO.find("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        OIDCAuthModuleConf.class.cast(conf).setResponseType("newCode");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("newCode", OIDCAuthModuleConf.class.cast(found.getConf()).getResponseType());
+    }
+
+    @Test
+    public void updateWithJaasModule() {
+        AuthModule module = authModuleDAO.find("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        JaasAuthModuleConf.class.cast(conf).setRealm("SYNCOPE_NEW");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("SYNCOPE_NEW", JaasAuthModuleConf.class.cast(found.getConf()).getRealm());
+    }
+
+    @Test
+    public void updateWithStaticModule() {
+        AuthModule module = authModuleDAO.find("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        assertEquals(1, StaticAuthModuleConf.class.cast(module.getConf()).getUsers().size());
+        AuthModuleConf conf = module.getConf();
+        StaticAuthModuleConf.class.cast(conf).getUsers().put("user3", "user3Password123");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals(2, StaticAuthModuleConf.class.cast(found.getConf()).getUsers().size());
+    }
+
+    @Test
+    public void updateWithRadiusModule() {
+        AuthModule module = authModuleDAO.find("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        RadiusAuthModuleConf.class.cast(conf).setSocketTimeout(45);
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals(45, RadiusAuthModuleConf.class.cast(found.getConf()).getSocketTimeout());
+    }
+
+    @Test
+    public void updateWithU2fModule() {
+        AuthModule module = authModuleDAO.find("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        U2FAuthModuleConf.class.cast(conf).setExpireDevices(24);
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals(24, U2FAuthModuleConf.class.cast(found.getConf()).getExpireDevices());
+    }
+
+    @Test
+    public void updateWithSyncopeModule() {
+        AuthModule module = authModuleDAO.find("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(module);
+        AuthModuleConf conf = module.getConf();
+        SyncopeAuthModuleConf.class.cast(conf).setDomain("Two");
+        module.setConf(conf);
+
+        module = authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        AuthModule found = authModuleDAO.find(module.getKey());
+        assertNotNull(found);
+        assertEquals("Two", SyncopeAuthModuleConf.class.cast(found.getConf()).getDomain());
+    }
+
+    @Test
+    public void delete() {
+        testDelete("be456831-593d-4003-b273-4c3fb61700df");
+        testDelete("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3edc98-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        testDelete("4c3edd60-7008-11ea-bc55-0242ac130003");
+        testDelete("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        testDelete("f6e1288d-50d9-45fe-82ee-597c42242205");
+    }
+
+    private void saveAuthModule(final String name, final AuthModuleConf conf) {
+        AuthModule module = entityFactory.newEntity(AuthModule.class);
+        module.setName(name);
+        module.setDescription("An authentication module");
+        module.setConf(conf);
+        AuthModuleItem keyMapping = entityFactory.newEntity(AuthModuleItem.class);
+        keyMapping.setIntAttrName("uid");
+        keyMapping.setExtAttrName("username");
+        AuthModuleItem fullnameMapping = entityFactory.newEntity(AuthModuleItem.class);
+        fullnameMapping.setIntAttrName("cn");
+        fullnameMapping.setExtAttrName("fullname");
+        module.add(keyMapping);
+        module.add(fullnameMapping);
+        authModuleDAO.save(module);
+        assertNotNull(module);
+        assertNotNull(module.getKey());
+        assertNotNull(authModuleDAO.find(module.getKey()));
+    }
+
+    private void testDelete(final String key) {
+        AuthModule authModule = authModuleDAO.find(key);
+        assertNotNull(authModule);
+        authModuleDAO.delete(key);
+        authModule = authModuleDAO.find(key);
+        assertNull(authModule);
+    }
+
+    private boolean isSpecificConf(final AuthModuleConf conf, final Class<? extends AuthModuleConf> clazz) {
+        return ClassUtils.isAssignable(clazz, conf.getClass());
+    }
+}
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java
new file mode 100644
index 0000000..1eab9f4
--- /dev/null
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/AuthModuleDataBinder.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.provisioning.api.data;
+
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+
+public interface AuthModuleDataBinder {
+
+    AuthModule create(AuthModuleTO authModuleTO);
+
+    AuthModule update(AuthModule authModule, AuthModuleTO authModuleTO);
+
+    AuthModuleTO getAuthModuleTO(AuthModule authModule);
+
+}
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.java
new file mode 100644
index 0000000..0b0e38b
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/AuthModuleDataBinderImpl.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.core.provisioning.java.data;
+
+import org.apache.syncope.core.persistence.api.entity.EntityFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+import org.apache.syncope.core.persistence.api.entity.auth.AuthModule;
+import org.apache.syncope.core.provisioning.api.data.AuthModuleDataBinder;
+
+@Component
+public class AuthModuleDataBinderImpl implements AuthModuleDataBinder {
+
+    @Autowired
+    private EntityFactory entityFactory;
+
+    private AuthModule getAuthModule(final AuthModule authModule, final AuthModuleTO authModuleTO) {
+        AuthModule result = authModule;
+
+        if (result == null) {
+            result = entityFactory.newEntity(AuthModule.class);
+        }
+
+        AuthModule authenticationModule = AuthModule.class.cast(result);
+        AuthModuleTO authenticationModuleTO = AuthModuleTO.class.cast(authModuleTO);
+
+        authenticationModule.setName(authenticationModuleTO.getName());
+        authenticationModule.setConf(authenticationModuleTO.getConf());
+        authenticationModule.setDescription(authenticationModuleTO.getDescription());
+        // remove all profile items not contained in the TO
+        authenticationModule.getProfileItems().
+                removeIf(item -> !authenticationModuleTO.getProfileItems().stream().
+                anyMatch(otherItem -> item.getKey().equals(otherItem.getKey())));
+
+        return result;
+    }
+
+    @Override
+    public AuthModule create(final AuthModuleTO authModuleTO) {
+        return getAuthModule(null, authModuleTO);
+    }
+
+    @Override
+    public AuthModule update(final AuthModule authModule, final AuthModuleTO authModuleTO) {
+        return getAuthModule(authModule, authModuleTO);
+    }
+
+    @Override
+    public AuthModuleTO getAuthModuleTO(final AuthModule authModule) {
+        AuthModuleTO authModuleTO = new AuthModuleTO();
+
+        authModuleTO.setName(authModule.getName());
+        authModuleTO.setKey(authModule.getKey());
+        authModuleTO.setDescription(authModule.getDescription());
+        authModuleTO.setConf(authModule.getConf());
+        authModuleTO.getProfileItems().forEach(item -> {
+            authModuleTO.add(item);
+        });
+
+        return authModuleTO;
+    }
+}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
new file mode 100644
index 0000000..a13c4fa
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuthModuleITCase.java
@@ -0,0 +1,589 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.fit.core;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.fit.AbstractITCase;
+import org.junit.jupiter.api.Test;
+import java.io.IOException;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.UUID;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.JaasAuthModuleConf;
+import org.apache.syncope.common.lib.auth.LDAPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.OIDCAuthModuleConf;
+import org.apache.syncope.common.lib.auth.RadiusAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SAML2IdPAuthModuleConf;
+import org.apache.syncope.common.lib.auth.StaticAuthModuleConf;
+import org.apache.syncope.common.lib.auth.SyncopeAuthModuleConf;
+import org.apache.syncope.common.lib.auth.U2FAuthModuleConf;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+
+public class AuthModuleITCase extends AbstractITCase {
+
+    private enum AuthModuleSupportedType {
+        GOOGLE_MFA,
+        SAML2_IDP,
+        STATIC,
+        SYNCOPE,
+        LDAP,
+        JAAS,
+        JDBC,
+        U2F,
+        RADIUS,
+        OIDC;
+
+    };
+
+    private static AuthModuleTO buildAuthModuleTO(final AuthModuleSupportedType type) {
+        AuthModuleTO authModuleTO = new AuthModuleTO();
+        authModuleTO.setName("Test" + type + "AuthenticationModule" + getUUIDString());
+        authModuleTO.setDescription("A test " + type + " Authentication Module");
+        AuthModuleConf conf;
+
+        switch (type) {
+            case LDAP:
+                conf = new LDAPAuthModuleConf();
+                LDAPAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                LDAPAuthModuleConf.class.cast(conf).setBaseDn("dc=example,dc=org");
+                LDAPAuthModuleConf.class.cast(conf).setSearchFilter("cn={user}");
+                LDAPAuthModuleConf.class.cast(conf).setSubtreeSearch(true);
+                LDAPAuthModuleConf.class.cast(conf).setLdapUrl("ldap://localhost:1389");
+                LDAPAuthModuleConf.class.cast(conf).setUserIdAttribute("uid");
+                LDAPAuthModuleConf.class.cast(conf).setBaseDn("cn=Directory Manager,dc=example,dc=org");
+                LDAPAuthModuleConf.class.cast(conf).setBindCredential("Password");
+                break;
+
+            case GOOGLE_MFA:
+                conf = new GoogleMfaAuthModuleConf();
+                GoogleMfaAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                GoogleMfaAuthModuleConf.class.cast(conf).setCodeDigits(6);
+                GoogleMfaAuthModuleConf.class.cast(conf).setIssuer("SyncopeTest");
+                GoogleMfaAuthModuleConf.class.cast(conf).setLabel("Syncope");
+                GoogleMfaAuthModuleConf.class.cast(conf).setTimeStepSize(30);
+                GoogleMfaAuthModuleConf.class.cast(conf).setWindowSize(3);
+                break;
+
+            case JAAS:
+                conf = new JaasAuthModuleConf();
+                JaasAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                JaasAuthModuleConf.class.cast(conf).setKerberosKdcSystemProperty("sample-value");
+                JaasAuthModuleConf.class.cast(conf).setKerberosRealmSystemProperty("sample-value");
+                JaasAuthModuleConf.class.cast(conf).setLoginConfigType("JavaLoginConfig");
+                JaasAuthModuleConf.class.cast(conf).setRealm("SYNCOPE");
+                JaasAuthModuleConf.class.cast(conf).setLoginConfigurationFile("/opt/jaas/login.conf");
+                break;
+
+            case JDBC:
+                conf = new JDBCAuthModuleConf();
+                JDBCAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                JDBCAuthModuleConf.class.cast(conf).setSql("SELECT * FROM table WHERE name=?");
+                JDBCAuthModuleConf.class.cast(conf).setFieldPassword("password");
+                JDBCAuthModuleConf.class.cast(conf).getPrincipalAttributeList().addAll(
+                        List.of("sn", "cn:commonName", "givenName"));
+                break;
+
+            case OIDC:
+                conf = new OIDCAuthModuleConf();
+                OIDCAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                OIDCAuthModuleConf.class.cast(conf).setId("OIDCTestId");
+                OIDCAuthModuleConf.class.cast(conf).setDiscoveryUri("www.testurl.com");
+                OIDCAuthModuleConf.class.cast(conf).setUserIdAttribute("username");
+                OIDCAuthModuleConf.class.cast(conf).setResponseType("code");
+                OIDCAuthModuleConf.class.cast(conf).setScope("openid email profile");
+                break;
+
+            case SAML2_IDP:
+                conf = new SAML2IdPAuthModuleConf();
+                SAML2IdPAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderEntityId("testEntityId");
+                SAML2IdPAuthModuleConf.class.cast(conf).setProviderName("testProviderName");
+                SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderMetadataPath(
+                        "file:/etc/metadata");
+                break;
+
+            case SYNCOPE:
+                conf = new SyncopeAuthModuleConf();
+                SyncopeAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                SyncopeAuthModuleConf.class.cast(conf).setDomain("Master");
+                SyncopeAuthModuleConf.class.cast(conf).setUrl("http://mydomain.com/syncope/rest");
+                break;
+
+            case U2F:
+                conf = new U2FAuthModuleConf();
+                U2FAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                U2FAuthModuleConf.class.cast(conf).setExpireDevices(50);
+                break;
+
+            case RADIUS:
+                conf = new RadiusAuthModuleConf();
+                RadiusAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                RadiusAuthModuleConf.class.cast(conf).setProtocol("MSCHAPv2");
+                RadiusAuthModuleConf.class.cast(conf).setInetAddress("1.2.3.4");
+                RadiusAuthModuleConf.class.cast(conf).setSharedSecret("xyz");
+                RadiusAuthModuleConf.class.cast(conf).setSocketTimeout(40);
+                break;
+
+            case STATIC:
+            default:
+                conf = new StaticAuthModuleConf();
+                StaticAuthModuleConf.class.cast(conf).setName("TestConf" + getUUIDString());
+                StaticAuthModuleConf.class.cast(conf).getUsers().put("user1", UUID.randomUUID().toString());
+                StaticAuthModuleConf.class.cast(conf).getUsers().put("user2", "user2Password123");
+                break;
+        }
+        authModuleTO.setConf(conf);
+
+        return authModuleTO;
+    }
+
+    @Test
+    public void findAll() {
+        List<AuthModuleTO> authModuleTOs = authModuleService.list();
+        assertNotNull(authModuleTOs);
+        assertFalse(authModuleTOs.isEmpty());
+        assertTrue(authModuleTOs.size() >= 10);
+    }
+
+    @Test
+    public void listByType() {
+        List<AuthModuleTO> authModuleTOs = authModuleService.list();
+        assertNotNull(authModuleTOs);
+        assertFalse(authModuleTOs.isEmpty());
+
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), LDAPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultLDAPAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JDBCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJDBCAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), GoogleMfaAuthModuleConf.class)
+                && authModule.getName().equals("DefaultGoogleMfaAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), OIDCAuthModuleConf.class)
+                && authModule.getName().equals("DefaultOIDCAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SAML2IdPAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSAML2IdPAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), JaasAuthModuleConf.class)
+                && authModule.getName().equals("DefaultJaasAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), StaticAuthModuleConf.class)
+                && authModule.getName().equals("DefaultStaticAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), SyncopeAuthModuleConf.class)
+                && authModule.getName().equals("DefaultSyncopeAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), U2FAuthModuleConf.class)
+                && authModule.getName().equals("DefaultU2FAuthModule")));
+        assertTrue(authModuleTOs.stream().anyMatch(
+                authModule -> isSpecificConf(authModule.getConf(), RadiusAuthModuleConf.class)
+                && authModule.getName().equals("DefaultRadiusAuthModule")));
+    }
+
+    @Test
+    public void getLDAPAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("be456831-593d-4003-b273-4c3fb61700df");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), LDAPAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), JDBCAuthModuleConf.class));
+    }
+
+    @Test
+    public void getJDBCAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), JDBCAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), GoogleMfaAuthModuleConf.class));
+    }
+
+    @Test
+    public void getGoogleMfaAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), GoogleMfaAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), OIDCAuthModuleConf.class));
+    }
+
+    @Test
+    public void getOIDCAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), OIDCAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), SAML2IdPAuthModuleConf.class));
+    }
+
+    @Test
+    public void getSAML2IdPAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), SAML2IdPAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), JaasAuthModuleConf.class));
+    }
+
+    @Test
+    public void getJaasAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3edbbc-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), JaasAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), StaticAuthModuleConf.class));
+    }
+
+    @Test
+    public void getStaticAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3edc98-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), StaticAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), SyncopeAuthModuleConf.class));
+    }
+
+    @Test
+    public void getSyncopeAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("4c3edd60-7008-11ea-bc55-0242ac130003");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), SyncopeAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), RadiusAuthModuleConf.class));
+    }
+
+    @Test
+    public void getRadiusAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), RadiusAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), U2FAuthModuleConf.class));
+    }
+
+    @Test
+    public void getU2FAuthModule() {
+        AuthModuleTO authModuleTO = authModuleService.read("f6e1288d-50d9-45fe-82ee-597c42242205");
+
+        assertNotNull(authModuleTO);
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getName()));
+        assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+        assertTrue(isSpecificConf(authModuleTO.getConf(), U2FAuthModuleConf.class));
+        assertFalse(isSpecificConf(authModuleTO.getConf(), LDAPAuthModuleConf.class));
+    }
+
+    @Test
+    public void create() throws IOException {
+        EnumSet.allOf(AuthModuleSupportedType.class).forEach(type -> testCreate(type));
+    }
+
+    @Test
+    public void updateGoogleMfaAuthModule() {
+        AuthModuleTO googleMfaAuthModuleTO = authModuleService.read("4c3ed4e6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(googleMfaAuthModuleTO);
+
+        AuthModuleTO newGoogleMfaAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.GOOGLE_MFA);
+        newGoogleMfaAuthModuleTO = createAuthModule(newGoogleMfaAuthModuleTO);
+        assertNotNull(newGoogleMfaAuthModuleTO);
+
+        AuthModuleConf conf = googleMfaAuthModuleTO.getConf();
+        assertNotNull(conf);
+        GoogleMfaAuthModuleConf.class.cast(conf).setLabel("newLabel");
+        newGoogleMfaAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newGoogleMfaAuthModuleTO);
+        newGoogleMfaAuthModuleTO = authModuleService.read(newGoogleMfaAuthModuleTO.getKey());
+        assertNotNull(newGoogleMfaAuthModuleTO);
+
+        conf = newGoogleMfaAuthModuleTO.getConf();
+        assertEquals("newLabel", GoogleMfaAuthModuleConf.class.cast(conf).getLabel());
+    }
+
+    @Test
+    public void updateLDAPAuthModule() {
+        AuthModuleTO ldapAuthModuleTO = authModuleService.read("be456831-593d-4003-b273-4c3fb61700df");
+        assertNotNull(ldapAuthModuleTO);
+
+        AuthModuleTO newLdapAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.LDAP);
+        newLdapAuthModuleTO = createAuthModule(newLdapAuthModuleTO);
+        assertNotNull(newLdapAuthModuleTO);
+
+        AuthModuleConf conf = ldapAuthModuleTO.getConf();
+        assertNotNull(conf);
+        LDAPAuthModuleConf.class.cast(conf).setSubtreeSearch(false);
+        newLdapAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newLdapAuthModuleTO);
+        newLdapAuthModuleTO = authModuleService.read(newLdapAuthModuleTO.getKey());
+        assertNotNull(newLdapAuthModuleTO);
+
+        conf = newLdapAuthModuleTO.getConf();
+        assertFalse(LDAPAuthModuleConf.class.cast(conf).isSubtreeSearch());
+    }
+
+    @Test
+    public void updateSAML2IdPAuthModule() {
+        AuthModuleTO saml2IdpAuthModuleTO = authModuleService.read("4c3ed9d2-7008-11ea-bc55-0242ac130003");
+        assertNotNull(saml2IdpAuthModuleTO);
+
+        AuthModuleTO newsaml2IdpAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.SAML2_IDP);
+        newsaml2IdpAuthModuleTO = createAuthModule(newsaml2IdpAuthModuleTO);
+        assertNotNull(newsaml2IdpAuthModuleTO);
+
+        AuthModuleConf conf = saml2IdpAuthModuleTO.getConf();
+        assertNotNull(conf);
+        SAML2IdPAuthModuleConf.class.cast(conf).setServiceProviderEntityId("newEntityId");
+        newsaml2IdpAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newsaml2IdpAuthModuleTO);
+        newsaml2IdpAuthModuleTO = authModuleService.read(newsaml2IdpAuthModuleTO.getKey());
+        assertNotNull(newsaml2IdpAuthModuleTO);
+
+        conf = newsaml2IdpAuthModuleTO.getConf();
+        assertEquals("newEntityId", SAML2IdPAuthModuleConf.class.cast(conf).getServiceProviderEntityId());
+    }
+
+    @Test
+    public void updateOIDCAuthModule() {
+        AuthModuleTO oidcAuthModuleTO = authModuleService.read("4c3ed8f6-7008-11ea-bc55-0242ac130003");
+        assertNotNull(oidcAuthModuleTO);
+
+        AuthModuleTO newOIDCAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.OIDC);
+        newOIDCAuthModuleTO = createAuthModule(newOIDCAuthModuleTO);
+        assertNotNull(newOIDCAuthModuleTO);
+
+        AuthModuleConf conf = oidcAuthModuleTO.getConf();
+        assertNotNull(conf);
+        OIDCAuthModuleConf.class.cast(conf).setResponseType("newCode");
+        newOIDCAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newOIDCAuthModuleTO);
+        newOIDCAuthModuleTO = authModuleService.read(newOIDCAuthModuleTO.getKey());
+        assertNotNull(newOIDCAuthModuleTO);
+
+        conf = newOIDCAuthModuleTO.getConf();
+        assertEquals("newCode", OIDCAuthModuleConf.class.cast(conf).getResponseType());
+    }
+
+    @Test
+    public void updateJDBCAuthModule() {
+        AuthModuleTO jdbcAuthModuleTO = authModuleService.read("4c3ed7e8-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jdbcAuthModuleTO);
+
+        AuthModuleTO newJDBCAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.JDBC);
+        newJDBCAuthModuleTO = createAuthModule(newJDBCAuthModuleTO);
+        assertNotNull(newJDBCAuthModuleTO);
+
+        AuthModuleConf conf = jdbcAuthModuleTO.getConf();
+        assertNotNull(conf);
+        JDBCAuthModuleConf.class.cast(conf).setFieldPassword("uPassword");
+        newJDBCAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newJDBCAuthModuleTO);
+        newJDBCAuthModuleTO = authModuleService.read(newJDBCAuthModuleTO.getKey());
+        assertNotNull(newJDBCAuthModuleTO);
+
+        conf = newJDBCAuthModuleTO.getConf();
+        assertEquals("uPassword", JDBCAuthModuleConf.class.cast(conf).getFieldPassword());
+    }
+
+    @Test
+    public void updateJaasAuthModule() {
+        AuthModuleTO jaasAuthModuleTO = authModuleService.read("4c3edbbc-7008-11ea-bc55-0242ac130003");
+        assertNotNull(jaasAuthModuleTO);
+
+        AuthModuleTO newJaasAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.JAAS);
+        newJaasAuthModuleTO = createAuthModule(newJaasAuthModuleTO);
+        assertNotNull(newJaasAuthModuleTO);
+
+        AuthModuleConf conf = jaasAuthModuleTO.getConf();
+        assertNotNull(conf);
+        JaasAuthModuleConf.class.cast(conf).setRealm("SYNCOPE_NEW");
+        newJaasAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newJaasAuthModuleTO);
+        newJaasAuthModuleTO = authModuleService.read(newJaasAuthModuleTO.getKey());
+        assertNotNull(newJaasAuthModuleTO);
+
+        conf = newJaasAuthModuleTO.getConf();
+        assertEquals("SYNCOPE_NEW", JaasAuthModuleConf.class.cast(conf).getRealm());
+    }
+
+    @Test
+    public void updateStaticAuthModule() {
+        AuthModuleTO staticAuthModuleTO = authModuleService.read("4c3edc98-7008-11ea-bc55-0242ac130003");
+        assertNotNull(staticAuthModuleTO);
+
+        AuthModuleTO newStaticAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.STATIC);
+        newStaticAuthModuleTO = createAuthModule(newStaticAuthModuleTO);
+        assertNotNull(newStaticAuthModuleTO);
+
+        AuthModuleConf conf = staticAuthModuleTO.getConf();
+        assertNotNull(conf);
+        assertEquals(1, StaticAuthModuleConf.class.cast(conf).getUsers().size());
+        StaticAuthModuleConf.class.cast(conf).getUsers().put("user3", "user3Password123");
+        newStaticAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newStaticAuthModuleTO);
+        newStaticAuthModuleTO = authModuleService.read(newStaticAuthModuleTO.getKey());
+        assertNotNull(newStaticAuthModuleTO);
+
+        conf = newStaticAuthModuleTO.getConf();
+        assertEquals(2, StaticAuthModuleConf.class.cast(conf).getUsers().size());
+    }
+
+    @Test
+    public void updateRadiusAuthModule() {
+        AuthModuleTO radiusAuthModuleTO = authModuleService.read("07c528f3-63b4-4dc1-a4da-87f35b8bdec8");
+        assertNotNull(radiusAuthModuleTO);
+
+        AuthModuleTO newRadiusAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.RADIUS);
+        newRadiusAuthModuleTO = createAuthModule(newRadiusAuthModuleTO);
+        assertNotNull(newRadiusAuthModuleTO);
+
+        AuthModuleConf conf = radiusAuthModuleTO.getConf();
+        assertNotNull(conf);
+        RadiusAuthModuleConf.class.cast(conf).setSocketTimeout(45);
+        newRadiusAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newRadiusAuthModuleTO);
+        newRadiusAuthModuleTO = authModuleService.read(newRadiusAuthModuleTO.getKey());
+        assertNotNull(newRadiusAuthModuleTO);
+
+        conf = newRadiusAuthModuleTO.getConf();
+        assertEquals(45, RadiusAuthModuleConf.class.cast(conf).getSocketTimeout());
+    }
+
+    @Test
+    public void updateU2fAuthModule() {
+        AuthModuleTO u2fAuthModuleTO = authModuleService.read("f6e1288d-50d9-45fe-82ee-597c42242205");
+        assertNotNull(u2fAuthModuleTO);
+
+        AuthModuleTO newU2fAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.U2F);
+        newU2fAuthModuleTO = createAuthModule(newU2fAuthModuleTO);
+        assertNotNull(newU2fAuthModuleTO);
+
+        AuthModuleConf conf = u2fAuthModuleTO.getConf();
+        assertNotNull(conf);
+        U2FAuthModuleConf.class.cast(conf).setExpireDevices(24);
+        newU2fAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newU2fAuthModuleTO);
+        newU2fAuthModuleTO = authModuleService.read(newU2fAuthModuleTO.getKey());
+        assertNotNull(newU2fAuthModuleTO);
+
+        conf = newU2fAuthModuleTO.getConf();
+        assertEquals(24, U2FAuthModuleConf.class.cast(conf).getExpireDevices());
+    }
+
+    @Test
+    public void updateSyncopeAuthModule() {
+        AuthModuleTO syncopeAuthModuleTO = authModuleService.read("4c3edd60-7008-11ea-bc55-0242ac130003");
+        assertNotNull(syncopeAuthModuleTO);
+
+        AuthModuleTO newSyncopeAuthModuleTO = buildAuthModuleTO(AuthModuleSupportedType.SYNCOPE);
+        newSyncopeAuthModuleTO = createAuthModule(newSyncopeAuthModuleTO);
+        assertNotNull(newSyncopeAuthModuleTO);
+
+        AuthModuleConf conf = syncopeAuthModuleTO.getConf();
+        assertNotNull(conf);
+        SyncopeAuthModuleConf.class.cast(conf).setDomain("Two");
+        newSyncopeAuthModuleTO.setConf(conf);
+
+        // update new auth module
+        authModuleService.update(newSyncopeAuthModuleTO);
+        newSyncopeAuthModuleTO = authModuleService.read(newSyncopeAuthModuleTO.getKey());
+        assertNotNull(newSyncopeAuthModuleTO);
+
+        conf = newSyncopeAuthModuleTO.getConf();
+        assertEquals("Two", SyncopeAuthModuleConf.class.cast(conf).getDomain());
+    }
+
+    @Test
+    public void delete() throws IOException {
+        EnumSet.allOf(AuthModuleSupportedType.class).forEach(type -> testDelete(type));
+    }
+
+    private void testCreate(final AuthModuleSupportedType type) {
+        AuthModuleTO authModuleTO = createAuthModule(buildAuthModuleTO(type));
+        assertNotNull(authModuleTO);
+        assertTrue(authModuleTO.getName().contains(
+                "Test" + type + "AuthenticationModule"));
+        assertTrue(authModuleTO.getDescription().contains(
+                "A test " + type + " Authentication Module"));
+    }
+
+    private void testDelete(final AuthModuleSupportedType type) {
+        AuthModuleTO authModuleTO = buildAuthModuleTO(type);
+        AuthModuleTO read = createAuthModule(authModuleTO);
+        assertNotNull(read);
+        authModuleService.delete(read.getKey());
+        try {
+            authModuleService.read(read.getKey());
+            fail("This should not happen");
+        } catch (SyncopeClientException e) {
+            assertNotNull(e);
+        }
+    }
+
+    private boolean isSpecificConf(final AuthModuleConf conf, final Class<? extends AuthModuleConf> clazz) {
+        return ClassUtils.isAssignable(clazz, conf.getClass());
+    }
+}


[syncope] 04/08: [SYNCOPE-1545] allow wa to lazy-load application context by scheduling a quartz job to retry/refresh the context after startup

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 9a4a1783e9ddae070ed55ef42eafe6be83ee0056
Author: Misagh Moayyed <mm...@apache.org>
AuthorDate: Thu Apr 9 13:38:20 2020 +0200

    [SYNCOPE-1545] allow wa to lazy-load application context by scheduling a quartz job to retry/refresh the context after startup
---
 docker/wa/src/main/resources/wa.properties         |  27 +++
 fit/wa-reference/pom.xml                           |  24 +--
 fit/wa-reference/src/main/resources/wa.properties  |  27 +++
 wa/bootstrap/pom.xml                               |  10 +-
 .../java/org/apache/syncope/wa/WARestClient.java   | 101 ++++++++++
 .../bootstrap/SyncopeWABootstrapConfiguration.java |  52 ++++++
 ...on.java => SyncopeWAPropertySourceLocator.java} |  44 +++--
 .../src/main/resources/META-INF/spring.factories   |   2 +-
 wa/starter/pom.xml                                 |  49 ++++-
 .../syncope/wa/mapper/RegisteredServiceMapper.java | 205 +++++++++++++++++++++
 .../syncope/wa/starter/SyncopeWAApplication.java   |  70 ++++++-
 .../syncope/wa/starter/SyncopeWAConfiguration.java |  30 ++-
 .../wa/starter/rest/SyncopeServiceRegistry.java    | 148 +++++++++++++++
 .../src/main/resources/application.properties      |   6 +-
 wa/starter/src/main/resources/wa.properties        |  19 ++
 15 files changed, 764 insertions(+), 50 deletions(-)

diff --git a/docker/wa/src/main/resources/wa.properties b/docker/wa/src/main/resources/wa.properties
index c44b04e..c98a6e7 100644
--- a/docker/wa/src/main/resources/wa.properties
+++ b/docker/wa/src/main/resources/wa.properties
@@ -23,3 +23,30 @@ useGZIPCompression=true
 conf.directory=${conf.directory}
 cas.standalone.configurationDirectory=${conf.directory}
 cas.authn.oidc.jwks.jwksFile=file:${conf.directory}/oidc.keystore
+
+cas.server.name=http://localhost:8080
+cas.server.prefix=${cas.server.name}/syncope-wa
+cas.server.scope=syncope.org
+
+cas.authn.samlIdp.entityId=https://syncope.apache.org/saml
+cas.authn.samlIdp.metadata.location=file:${conf.directory}
+
+# Disable access to the login endpoint
+# if no target application is specified.
+cas.sso.allow-missing-service-parameter=true
+
+# Disable the acceptable usage policy
+# by default for now.
+cas.acceptableUsagePolicy.enabled=false
+
+management.endpoints.web.exposure.include=health,loggers,refresh
+management.endpoint.health.show-details=always
+
+management.endpoint.health.enabled=true
+management.endpoint.loggers.enabled=true
+management.endpoint.refresh.enabled=true
+
+cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED
+
+spring.security.user.name=${anonymousUser}
+spring.security.user.password=${anonymousKey}
diff --git a/fit/wa-reference/pom.xml b/fit/wa-reference/pom.xml
index fa05bad..e3b4dcb 100644
--- a/fit/wa-reference/pom.xml
+++ b/fit/wa-reference/pom.xml
@@ -143,18 +143,18 @@ under the License.
                 <context>syncope</context>
               </properties>
             </deployable>
-            <deployable>
-              <location>${basedir}/../console-reference/target/syncope-fit-console-reference-${project.version}</location>
-              <properties>
-                <context>syncope-console</context>
-              </properties>
-            </deployable>
-            <deployable>
-              <location>${basedir}/../enduser-reference/target/syncope-fit-enduser-reference-${project.version}</location>
-              <properties>
-                <context>syncope-enduser</context>
-              </properties>
-            </deployable>
+<!--            <deployable>-->
+<!--              <location>${basedir}/../console-reference/target/syncope-fit-console-reference-${project.version}</location>-->
+<!--              <properties>-->
+<!--                <context>syncope-console</context>-->
+<!--              </properties>-->
+<!--            </deployable>-->
+<!--            <deployable>-->
+<!--              <location>${basedir}/../enduser-reference/target/syncope-fit-enduser-reference-${project.version}</location>-->
+<!--              <properties>-->
+<!--                <context>syncope-enduser</context>-->
+<!--              </properties>-->
+<!--            </deployable>-->
             <deployable>
               <location>${project.build.directory}/${project.build.finalName}</location>
               <properties>
diff --git a/fit/wa-reference/src/main/resources/wa.properties b/fit/wa-reference/src/main/resources/wa.properties
index c44b04e..c98a6e7 100644
--- a/fit/wa-reference/src/main/resources/wa.properties
+++ b/fit/wa-reference/src/main/resources/wa.properties
@@ -23,3 +23,30 @@ useGZIPCompression=true
 conf.directory=${conf.directory}
 cas.standalone.configurationDirectory=${conf.directory}
 cas.authn.oidc.jwks.jwksFile=file:${conf.directory}/oidc.keystore
+
+cas.server.name=http://localhost:8080
+cas.server.prefix=${cas.server.name}/syncope-wa
+cas.server.scope=syncope.org
+
+cas.authn.samlIdp.entityId=https://syncope.apache.org/saml
+cas.authn.samlIdp.metadata.location=file:${conf.directory}
+
+# Disable access to the login endpoint
+# if no target application is specified.
+cas.sso.allow-missing-service-parameter=true
+
+# Disable the acceptable usage policy
+# by default for now.
+cas.acceptableUsagePolicy.enabled=false
+
+management.endpoints.web.exposure.include=health,loggers,refresh
+management.endpoint.health.show-details=always
+
+management.endpoint.health.enabled=true
+management.endpoint.loggers.enabled=true
+management.endpoint.refresh.enabled=true
+
+cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED
+
+spring.security.user.name=${anonymousUser}
+spring.security.user.password=${anonymousKey}
diff --git a/wa/bootstrap/pom.xml b/wa/bootstrap/pom.xml
index 72b7482..9ea1e97 100644
--- a/wa/bootstrap/pom.xml
+++ b/wa/bootstrap/pom.xml
@@ -43,11 +43,19 @@ under the License.
       <artifactId>syncope-client-am-lib</artifactId>
       <version>${project.version}</version>
     </dependency>
-
+    <dependency>
+      <groupId>org.apache.syncope.common.keymaster</groupId>
+      <artifactId>syncope-common-keymaster-client-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.apereo.cas</groupId>
       <artifactId>cas-server-core-configuration-api</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-core-util-api</artifactId>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java
new file mode 100644
index 0000000..ca2d048
--- /dev/null
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/WARestClient.java
@@ -0,0 +1,101 @@
+/*
+ * 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;
+
+import org.apereo.cas.util.spring.ApplicationContextProvider;
+
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
+import org.apache.syncope.common.keymaster.client.api.KeymasterException;
+import org.apache.syncope.common.keymaster.client.api.ServiceOps;
+import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ApplicationContext;
+
+import java.util.Collection;
+
+public class WARestClient {
+
+    private static final Logger LOG = LoggerFactory.getLogger(WARestClient.class);
+
+    private final String anonymousUser;
+
+    private final String anonymousKey;
+
+    private final boolean useGZIPCompression;
+
+    private SyncopeClient client;
+
+    public WARestClient(
+        final String anonymousUser,
+        final String anonymousKey,
+        final boolean useGZIPCompression) {
+
+        this.anonymousUser = anonymousUser;
+        this.anonymousKey = anonymousKey;
+        this.useGZIPCompression = useGZIPCompression;
+    }
+
+    public SyncopeClient getSyncopeClient() {
+        synchronized (this) {
+            if (client == null && isReady()) {
+                try {
+                    client = new SyncopeClientFactoryBean().
+                        setAddress(getCore().getAddress()).
+                        setUseCompression(useGZIPCompression).
+                        create(new AnonymousAuthenticationHandler(anonymousUser, anonymousKey));
+                } catch (Exception e) {
+                    LOG.error("Could not init SyncopeClient", e);
+                }
+            }
+        }
+
+        return client;
+    }
+
+    private static NetworkService getCore() {
+        try {
+            final ApplicationContext context = ApplicationContextProvider.getApplicationContext();
+            if (context == null) {
+                return null;
+            }
+
+            Collection<ServiceOps> serviceOpsList = context.getBeansOfType(ServiceOps.class).values();
+            if (serviceOpsList.isEmpty()) {
+                return null;
+            }
+            ServiceOps serviceOps = serviceOpsList.iterator().next();
+            return serviceOps.get(NetworkService.Type.CORE);
+        } catch (KeymasterException e) {
+            LOG.trace(e.getMessage());
+        }
+        return null;
+    }
+
+    public static boolean isReady() {
+        try {
+            return getCore() != null;
+        } catch (Exception e) {
+            LOG.trace(e.getMessage());
+        }
+        return false;
+    }
+}
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java
new file mode 100644
index 0000000..2330c3f
--- /dev/null
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWABootstrapConfiguration.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.wa.bootstrap;
+
+import org.apache.syncope.wa.WARestClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+@Configuration(proxyBeanMethods = false)
+@PropertySource("classpath:wa.properties")
+@PropertySource(value = "file:${conf.directory}/wa.properties", ignoreResourceNotFound = true)
+public class SyncopeWABootstrapConfiguration {
+    @Value("${anonymousUser}")
+    private String anonymousUser;
+
+    @Value("${anonymousKey}")
+    private String anonymousKey;
+
+    @Value("${useGZIPCompression}")
+    private boolean useGZIPCompression;
+
+    @Bean
+    public WARestClient waRestClient() {
+        return new WARestClient(anonymousUser, anonymousKey, useGZIPCompression);
+    }
+
+    @Autowired
+    @Bean
+    public PropertySourceLocator configPropertySourceLocator(final WARestClient waRestClient) {
+        return new SyncopeWAPropertySourceLocator(waRestClient);
+    }
+}
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/RestfulCloudConfigBootstrapConfiguration.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
similarity index 55%
rename from wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/RestfulCloudConfigBootstrapConfiguration.java
rename to wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
index 262931f..22f3669 100644
--- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/RestfulCloudConfigBootstrapConfiguration.java
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
@@ -7,7 +7,7 @@
  * "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
+ *    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
@@ -16,36 +16,42 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package org.apache.syncope.wa.bootstrap;
 
-import com.fasterxml.jackson.core.type.TypeReference;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import java.net.URI;
-import java.util.Map;
-import javax.ws.rs.core.MediaType;
-import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.wa.WARestClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
-import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
 import org.springframework.core.env.Environment;
 import org.springframework.core.env.MapPropertySource;
 import org.springframework.core.env.PropertySource;
 
-@Configuration(value = "restfulCloudConfigBootstrapConfiguration", proxyBeanMethods = false)
-public class RestfulCloudConfigBootstrapConfiguration implements PropertySourceLocator {
+import java.util.HashMap;
+import java.util.Map;
+
+@Order
+public class SyncopeWAPropertySourceLocator implements PropertySourceLocator {
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeWABootstrapConfiguration.class);
 
-    private static final ObjectMapper MAPPER = new ObjectMapper().findAndRegisterModules();
+    private final WARestClient waRestClient;
+
+    public SyncopeWAPropertySourceLocator(final WARestClient waRestClient) {
+        this.waRestClient = waRestClient;
+    }
 
     @Override
     public PropertySource<?> locate(final Environment environment) {
         try {
-            String content = WebClient.create(URI.create("https://demo5926981.mockable.io/casproperties")).
-                    accept(MediaType.APPLICATION_JSON_TYPE).
-                    get().
-                    readEntity(String.class);
-
-            Map<String, Object> payload = MAPPER.readValue(content, new TypeReference<Map<String, Object>>() {
-            });
-            return new MapPropertySource(getClass().getName(), payload);
+            Map<String, Object> properties = new HashMap<>();
+            if (WARestClient.isReady()) {
+                LOG.info("Bootstrapping WA configuration");
+                return new MapPropertySource(getClass().getName(), properties);
+            }
+
+            LOG.warn("Application context is not ready to bootstrap WA configuration");
+            return null;
         } catch (Exception e) {
             throw new IllegalArgumentException("Unable to fetch settings", e);
         }
diff --git a/wa/bootstrap/src/main/resources/META-INF/spring.factories b/wa/bootstrap/src/main/resources/META-INF/spring.factories
index 4cd20b8..0729129 100644
--- a/wa/bootstrap/src/main/resources/META-INF/spring.factories
+++ b/wa/bootstrap/src/main/resources/META-INF/spring.factories
@@ -16,4 +16,4 @@
 # under the License.
 
 org.springframework.cloud.bootstrap.BootstrapConfiguration=\
- org.apache.syncope.wa.bootstrap.RestfulCloudConfigBootstrapConfiguration
+ org.apache.syncope.wa.bootstrap.SyncopeWABootstrapConfiguration
diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml
index 3fa2b65..afd48ac 100644
--- a/wa/starter/pom.xml
+++ b/wa/starter/pom.xml
@@ -90,6 +90,14 @@ under the License.
     </dependency>
     <dependency>
       <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-core-services-registry</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-core-services-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
       <artifactId>cas-server-core-tickets</artifactId>
     </dependency>
     <dependency>
@@ -146,10 +154,30 @@ under the License.
     </dependency>
     <dependency>
       <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-support-radius</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-support-radius-mfa</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-support-saml-idp</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-support-saml-idp-core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
       <artifactId>cas-server-support-oidc</artifactId>
     </dependency>
     <dependency>
       <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-support-oauth-services</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
       <artifactId>cas-server-support-validation</artifactId>
     </dependency>
     <dependency>
@@ -176,6 +204,21 @@ under the License.
       <groupId>org.apereo.cas</groupId>
       <artifactId>cas-server-webapp-config</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-support-oidc-services</artifactId>
+      <version>6.2.0-SNAPSHOT</version>
+    </dependency>   
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-core-authentication-attributes</artifactId>
+      <version>6.2.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apereo.cas</groupId>
+      <artifactId>cas-server-core-services-authentication</artifactId>
+      <version>6.2.0-SNAPSHOT</version>
+    </dependency>
 
     <dependency>
       <groupId>org.springframework.boot</groupId>
@@ -197,6 +240,11 @@ under the License.
     </dependency>
 
     <dependency>
+      <groupId>org.bouncycastle</groupId>
+      <artifactId>bcpkix-jdk15on</artifactId>
+    </dependency>
+    
+    <dependency>
       <groupId>org.apache.syncope.common.keymaster</groupId>
       <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
       <version>${project.version}</version>
@@ -273,7 +321,6 @@ under the License.
           <groupId>org.apache.syncope.common.keymaster</groupId>
           <artifactId>syncope-common-keymaster-client-zookeeper</artifactId>
           <version>${project.version}</version>
-          <scope>compile</scope>
         </dependency>
         <dependency>
           <groupId>org.apache.curator</groupId>
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/mapper/RegisteredServiceMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/mapper/RegisteredServiceMapper.java
new file mode 100644
index 0000000..c81d1a7
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/mapper/RegisteredServiceMapper.java
@@ -0,0 +1,205 @@
+/*
+ * 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.mapper;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyCriteriaConf;
+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.OIDCSubjectType;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
+import org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy;
+import org.apereo.cas.services.DefaultRegisteredServiceAuthenticationPolicy;
+import org.apereo.cas.services.DenyAllAttributeReleasePolicy;
+import org.apereo.cas.services.OidcRegisteredService;
+import org.apereo.cas.services.RegisteredService;
+import org.apereo.cas.services.RegisteredServiceAccessStrategy;
+import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
+import org.apereo.cas.services.AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria;
+import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
+import org.apereo.cas.support.saml.services.SamlRegisteredService;
+
+public class RegisteredServiceMapper {
+
+    public RegisteredService toRegisteredService(final RegisteredClientAppTO clientApp) {
+
+        DefaultRegisteredServiceAuthenticationPolicy authenticationPolicy =
+                new DefaultRegisteredServiceAuthenticationPolicy();
+        AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria criteria =
+                new AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria();
+        criteria.setTryAll(((DefaultAuthPolicyCriteriaConf) clientApp.getAuthPolicyConf().getCriteria()).isAll());
+        authenticationPolicy.setCriteria(criteria);
+
+        RegisteredServiceAccessStrategy accessStrategy =
+                new DefaultRegisteredServiceAccessStrategy(
+                        clientApp.getAccessPolicyConf().isEnabled(), clientApp.getAccessPolicyConf().isSsoEnabled());
+        accessStrategy.getRequiredAttributes().putAll(clientApp.getAccessPolicyConf().getRequiredAttributes());
+
+        RegisteredServiceAttributeReleasePolicy attributeReleasePolicy;
+        if (clientApp.getAttrReleasePolicyConf() != null
+                && clientApp.getAttrReleasePolicyConf() instanceof AllowedAttrReleasePolicyConf
+                && !((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).
+                        getAllowedAttributes().isEmpty()) {
+            attributeReleasePolicy = new ReturnAllowedAttributeReleasePolicy();
+            ((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).getAllowedAttributes();
+            ((ReturnAllowedAttributeReleasePolicy) attributeReleasePolicy).getAllowedAttributes().addAll(
+                    ((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).getAllowedAttributes());
+        } else {
+            attributeReleasePolicy = new DenyAllAttributeReleasePolicy();
+        }
+
+        if (clientApp.getClientAppTO() instanceof OIDCRPTO) {
+            OIDCRPTO rp = OIDCRPTO.class.cast(clientApp.getClientAppTO());
+            OidcRegisteredService registeredService = new OidcRegisteredService();
+
+            String redirectURIs = String.join("|", rp.getRedirectUris());
+            registeredService.setServiceId(redirectURIs);
+            registeredService.setName(rp.getName());
+            registeredService.setDescription(rp.getDescription());
+            registeredService.setAccessStrategy(accessStrategy);
+            registeredService.setAuthenticationPolicy(authenticationPolicy);
+            registeredService.setAttributeReleasePolicy(attributeReleasePolicy);
+
+            registeredService.setClientId(rp.getClientId());
+            registeredService.setClientSecret(rp.getClientSecret());
+            registeredService.setSignIdToken(rp.isSignIdToken());
+            registeredService.setJwks(rp.getJwks());
+            registeredService.setSubjectType(rp.getSubjectType().name());
+            registeredService.setRedirectUrl(redirectURIs);
+            registeredService.setSupportedGrantTypes((HashSet<String>) rp.getSupportedGrantTypes());
+            registeredService.setSupportedResponseTypes((HashSet<String>) rp.getSupportedResponseTypes());
+
+            return registeredService;
+        } else if (clientApp.getClientAppTO() instanceof SAML2SPTO) {
+            SAML2SPTO sp = SAML2SPTO.class.cast(clientApp.getClientAppTO());
+            SamlRegisteredService registeredService = new SamlRegisteredService();
+
+            registeredService.setServiceId(sp.getEntityId());
+            registeredService.setName(sp.getName());
+            registeredService.setDescription(sp.getDescription());
+            registeredService.setAccessStrategy(accessStrategy);
+            registeredService.setAuthenticationPolicy(authenticationPolicy);
+            registeredService.setAttributeReleasePolicy(attributeReleasePolicy);
+
+            registeredService.setMetadataLocation(sp.getMetadataLocation());
+            registeredService.setMetadataSignatureLocation(sp.getMetadataSignatureLocation());
+            registeredService.setSignAssertions(sp.isSignAssertions());
+            registeredService.setSignResponses(sp.isSignResponses());
+            registeredService.setEncryptionOptional(sp.isEncryptionOptional());
+            registeredService.setEncryptAssertions(sp.isEncryptAssertions());
+            registeredService.setRequiredAuthenticationContextClass(sp.getRequiredAuthenticationContextClass());
+            registeredService.setRequiredNameIdFormat(sp.getRequiredNameIdFormat().getNameId());
+            registeredService.setSkewAllowance(sp.getSkewAllowance());
+            registeredService.setNameIdQualifier(sp.getNameIdQualifier());
+            registeredService.setAssertionAudiences(sp.getAssertionAudiences());
+            registeredService.setServiceProviderNameIdQualifier(sp.getServiceProviderNameIdQualifier());
+            return registeredService;
+        }
+        return null;
+    }
+
+    public RegisteredClientAppTO fromRegisteredService(final RegisteredService registeredService) {
+        RegisteredClientAppTO clientApp = new RegisteredClientAppTO();
+
+        if (registeredService.getAuthenticationPolicy() != null) {
+            DefaultAuthPolicyConf authPolicyConf = new DefaultAuthPolicyConf();
+            DefaultAuthPolicyCriteriaConf criteria = new DefaultAuthPolicyCriteriaConf();
+            criteria.setAll(((DefaultAuthPolicyCriteriaConf) registeredService.
+                    getAuthenticationPolicy().getCriteria()).isAll());
+            authPolicyConf.setCriteria(criteria);
+
+            clientApp.setAuthPolicyConf(authPolicyConf);
+        }
+
+        if (registeredService.getAccessStrategy() != null) {
+            DefaultAccessPolicyConf accessPolicyConf = new DefaultAccessPolicyConf();
+            accessPolicyConf.setEnabled(
+                    ((DefaultRegisteredServiceAccessStrategy) registeredService.getAccessStrategy()).
+                            isEnabled());
+            accessPolicyConf.setSsoEnabled(((DefaultRegisteredServiceAccessStrategy) registeredService.
+                    getAccessStrategy()).
+                    isSsoEnabled());
+            accessPolicyConf.getRequiredAttributes().putAll(((DefaultRegisteredServiceAccessStrategy) registeredService.
+                    getAccessStrategy()).getRejectedAttributes());
+
+            clientApp.setAccessPolicyConf(accessPolicyConf);
+        }
+
+        if (registeredService.getAttributeReleasePolicy() != null) {
+
+            if (registeredService.getAttributeReleasePolicy() instanceof ReturnAllowedAttributeReleasePolicy) {
+                ReturnAllowedAttributeReleasePolicy returnAllowedAttributeReleasePolicy =
+                        ReturnAllowedAttributeReleasePolicy.class.cast(registeredService.getAttributeReleasePolicy());
+                AllowedAttrReleasePolicyConf allowedAttrReleasePolicyConf = new AllowedAttrReleasePolicyConf();
+                allowedAttrReleasePolicyConf.getAllowedAttributes().addAll(returnAllowedAttributeReleasePolicy.
+                        getAllowedAttributes());
+
+                clientApp.setAttrReleasePolicyConf(allowedAttrReleasePolicyConf);
+            }
+        }
+
+        if (registeredService instanceof OidcRegisteredService) {
+            OidcRegisteredService oidcRegisteredService = OidcRegisteredService.class.cast(registeredService);
+            OIDCRPTO oidcrpto = new OIDCRPTO();
+
+            Arrays.asList(registeredService.getServiceId().split("|")).forEach(redirectURI
+                    -> oidcrpto.getRedirectUris().add(redirectURI));
+            oidcrpto.setName(oidcRegisteredService.getName());
+            oidcrpto.setDescription(oidcRegisteredService.getDescription());
+            oidcrpto.setClientId(oidcRegisteredService.getClientId());
+            oidcrpto.setClientSecret(oidcRegisteredService.getClientSecret());
+            oidcrpto.setSignIdToken(oidcRegisteredService.isSignIdToken());
+            oidcrpto.setJwks(oidcRegisteredService.getJwks());
+            oidcrpto.setSubjectType(OIDCSubjectType.valueOf(oidcRegisteredService.getSubjectType()));
+            oidcrpto.getSupportedGrantTypes().addAll(oidcRegisteredService.getSupportedGrantTypes());
+            oidcrpto.getSupportedResponseTypes().addAll(oidcRegisteredService.getSupportedResponseTypes());
+
+            clientApp.setClientAppTO(oidcrpto);
+        } else if (registeredService instanceof SamlRegisteredService) {
+            SamlRegisteredService samlRegisteredService = SamlRegisteredService.class.cast(registeredService);
+            SAML2SPTO saml2spto = new SAML2SPTO();
+
+            saml2spto.setEntityId(samlRegisteredService.getServiceId());
+            saml2spto.setName(samlRegisteredService.getName());
+            saml2spto.setDescription(samlRegisteredService.getDescription());
+
+            saml2spto.setMetadataLocation(samlRegisteredService.getMetadataLocation());
+            saml2spto.setMetadataSignatureLocation(samlRegisteredService.getMetadataSignatureLocation());
+            saml2spto.setSignAssertions(samlRegisteredService.isSignAssertions());
+            saml2spto.setSignResponses(samlRegisteredService.isSignResponses());
+            saml2spto.setEncryptionOptional(samlRegisteredService.isEncryptionOptional());
+            saml2spto.setEncryptAssertions(samlRegisteredService.isEncryptAssertions());
+            saml2spto.setRequiredAuthenticationContextClass(samlRegisteredService.
+                    getRequiredAuthenticationContextClass());
+            saml2spto.setRequiredNameIdFormat(SAML2SPNameId.valueOf(samlRegisteredService.getRequiredNameIdFormat()));
+            saml2spto.setSkewAllowance(samlRegisteredService.getSkewAllowance());
+            saml2spto.setNameIdQualifier(samlRegisteredService.getNameIdQualifier());
+            saml2spto.setAssertionAudiences(samlRegisteredService.getAssertionAudiences());
+            saml2spto.setServiceProviderNameIdQualifier(samlRegisteredService.getServiceProviderNameIdQualifier());
+
+            clientApp.setClientAppTO(saml2spto);
+        }
+        return clientApp;
+    }
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
index 4d5d43a..887ad97 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
@@ -24,8 +24,18 @@ import org.apereo.cas.util.AsciiArtUtils;
 import org.apereo.cas.util.DateTimeUtils;
 
 import org.apache.commons.lang.StringUtils;
+import org.quartz.Job;
+import org.quartz.JobBuilder;
+import org.quartz.JobDetail;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobKey;
+import org.quartz.SchedulerException;
+import org.quartz.Trigger;
+import org.quartz.TriggerBuilder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
 import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
@@ -43,13 +53,19 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.boot.context.event.ApplicationReadyEvent;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+import org.springframework.cloud.context.refresh.ContextRefresher;
 import org.springframework.context.annotation.EnableAspectJAutoProxy;
 import org.springframework.context.annotation.PropertySource;
 import org.springframework.context.event.EventListener;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+
 @PropertySource("classpath:wa.properties")
 @PropertySource(value = "file:${conf.directory}/wa.properties", ignoreResourceNotFound = true)
 @SpringBootApplication(exclude = {
@@ -75,10 +91,32 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
 
     private static final Logger LOG = LoggerFactory.getLogger(SyncopeWAApplication.class);
 
+    @Autowired
+    private ContextRefresher contextRefresher;
+
+    @Autowired
+    private SchedulerFactoryBean scheduler;
+
+    @Value("${contextRefreshDelay:15}")
+    private long contextRefreshDelay;
+
     public static void main(final String[] args) {
         new SpringApplicationBuilder(SyncopeWAApplication.class).run(args);
     }
 
+    private static void advertiseReady(final ApplicationReadyEvent event) {
+        AsciiArtUtils.printAsciiArtReady(LOG, StringUtils.EMPTY);
+        LOG.info("Ready to process requests @ [{}]", DateTimeUtils.zonedDateTimeOf(event.getTimestamp()));
+    }
+
+    private static void validateConfiguration(final ApplicationReadyEvent event) {
+        if (!Boolean.getBoolean("SKIP_CONFIG_VALIDATION")) {
+            CasConfigurationPropertiesValidator validator =
+                new CasConfigurationPropertiesValidator(event.getApplicationContext());
+            validator.validate();
+        }
+    }
+
     /**
      * Handle application ready event.
      *
@@ -86,13 +124,33 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
      */
     @EventListener
     public void handleApplicationReadyEvent(final ApplicationReadyEvent event) {
-        if (!Boolean.getBoolean("SKIP_CONFIG_VALIDATION")) {
-            CasConfigurationPropertiesValidator validator =
-                new CasConfigurationPropertiesValidator(event.getApplicationContext());
-            validator.validate();
+        validateConfiguration(event);
+        scheduleJobToRefreshContext();
+        advertiseReady(event);
+    }
+
+    private void scheduleJobToRefreshContext() {
+        try {
+            Date date = Date.from(LocalDateTime.now().plusSeconds(this.contextRefreshDelay).
+                atZone(ZoneId.systemDefault()).toInstant());
+            Trigger trigger = TriggerBuilder.newTrigger().startAt(date).build();
+            JobKey jobKey = new JobKey(getClass().getSimpleName());
+
+            JobDetail job = JobBuilder.newJob(RefreshApplicationContextJob.class).withIdentity(jobKey).build();
+            scheduler.getScheduler().scheduleJob(job, trigger);
+        } catch (SchedulerException e) {
+            throw new RuntimeException("Could not schedule refresh job", e);
         }
+    }
 
-        AsciiArtUtils.printAsciiArtReady(LOG, StringUtils.EMPTY);
-        LOG.info("Ready to process requests @ [{}]", DateTimeUtils.zonedDateTimeOf(event.getTimestamp()));
+    private class RefreshApplicationContextJob implements Job {
+        @Override
+        public void execute(final JobExecutionContext jobExecutionContext) {
+            try {
+                LOG.debug("Refreshed context: {}", contextRefresher.refresh());
+            } catch (final Exception e) {
+                LOG.error(e.getMessage(), e);
+            }
+        }
     }
 }
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
index 16375c3..07661e0 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
@@ -18,24 +18,36 @@
  */
 package org.apache.syncope.wa.starter;
 
-import java.io.Serializable;
-import org.apereo.cas.services.DefaultRegisteredServiceEntityMapper;
-import org.apereo.cas.services.RegisteredService;
-import org.apereo.cas.services.RegisteredServiceEntityMapper;
+import java.util.Collection;
 import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.apache.syncope.wa.WARestClient;
+import org.apache.syncope.wa.starter.rest.SyncopeServiceRegistry;
+import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer;
+import org.apereo.cas.services.ServiceRegistryListener;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 
-@Configuration(proxyBeanMethods = false)
+@Configuration
 public class SyncopeWAConfiguration {
 
+    @Autowired
+    private ConfigurableApplicationContext applicationContext;
+
+    @Autowired
+    @Qualifier("serviceRegistryListeners")
+    private Collection<ServiceRegistryListener> serviceRegistryListeners;
+
+    @Autowired
     @Bean
-    @ConditionalOnProperty(name = "cas.serviceRegistry.rest.url")
-    public RegisteredServiceEntityMapper<RegisteredService, Serializable> registeredServiceEntityMapper() {
-        return new DefaultRegisteredServiceEntityMapper();
+    public ServiceRegistryExecutionPlanConfigurer syncopeServiceRegistryConfigurer(final WARestClient restClient) {
+        SyncopeServiceRegistry registry =
+                new SyncopeServiceRegistry(restClient, applicationContext, serviceRegistryListeners);
+        return plan -> plan.registerServiceRegistry(registry);
     }
 
     @Bean
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/rest/SyncopeServiceRegistry.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/rest/SyncopeServiceRegistry.java
new file mode 100644
index 0000000..b042262
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/rest/SyncopeServiceRegistry.java
@@ -0,0 +1,148 @@
+/*
+ * 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.rest;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.ws.rs.core.Response;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.rest.api.service.RegisteredClientAppService;
+import org.apache.syncope.wa.WARestClient;
+import org.apache.syncope.wa.mapper.RegisteredServiceMapper;
+import org.apereo.cas.services.AbstractServiceRegistry;
+import org.apereo.cas.services.OidcRegisteredService;
+import org.apereo.cas.services.RegisteredService;
+import org.apereo.cas.services.ServiceRegistryListener;
+import org.apereo.cas.support.saml.services.SamlRegisteredService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.ConfigurableApplicationContext;
+
+public class SyncopeServiceRegistry extends AbstractServiceRegistry {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SyncopeServiceRegistry.class);
+
+    private final WARestClient restClient;
+
+    private final RegisteredServiceMapper mapper;
+
+    public SyncopeServiceRegistry(final WARestClient restClient,
+            final ConfigurableApplicationContext applicationContext,
+            final Collection<ServiceRegistryListener> serviceRegistryListeners) {
+
+        super(applicationContext, serviceRegistryListeners);
+        this.restClient = restClient;
+        this.mapper = new RegisteredServiceMapper();
+    }
+
+    @Override
+    public RegisteredService save(final RegisteredService registeredService) {
+        if (WARestClient.isReady()) {
+            LOG.info("Create application definitions");
+            Response response =
+                    restClient.getSyncopeClient().getService(RegisteredClientAppService.class).create(mapper.
+                            fromRegisteredService(registeredService));
+            if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
+                return registeredService;
+            }
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return null;
+    }
+
+    @Override
+    public boolean delete(final RegisteredService registeredService) {
+        if (WARestClient.isReady()) {
+            LOG.info("Delete application definitions");
+            return restClient.getSyncopeClient().getService(RegisteredClientAppService.class).
+                    delete(registeredService.getName());
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return false;
+    }
+
+    @Override
+    public Collection<RegisteredService> load() {
+        if (WARestClient.isReady()) {
+            LOG.info("Loading application definitions");
+            return restClient.getSyncopeClient().getService(RegisteredClientAppService.class).list().stream().
+                    map(clientApp -> mapper.toRegisteredService(clientApp)).collect(Collectors.toList());
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return List.of();
+    }
+
+    @Override
+    public RegisteredService findServiceById(final long id) {
+        if (WARestClient.isReady()) {
+            LOG.info("Searching for application definition by id {}", id);
+            return mapper.toRegisteredService(restClient.getSyncopeClient().
+                    getService(RegisteredClientAppService.class).read(id));
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends RegisteredService> T findServiceByExactServiceName(final String name, final Class<T> clazz) {
+        if (WARestClient.isReady()) {
+            LOG.info("Searching for application definition by name {} and type {}", name, clazz);
+            if (clazz.isInstance(OidcRegisteredService.class)) {
+                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(RegisteredClientAppService.class).read(name, ClientAppType.OIDCRP));
+            } else if (clazz.isInstance(SamlRegisteredService.class)) {
+                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(RegisteredClientAppService.class).read(name, ClientAppType.SAML2SP));
+            }
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return null;
+    }
+
+    @Override
+    public RegisteredService findServiceByExactServiceName(final String name) {
+        if (WARestClient.isReady()) {
+            LOG.info("Searching for application definition by name {}", name);
+            return mapper.toRegisteredService(restClient.getSyncopeClient().
+                    getService(RegisteredClientAppService.class).read(name));
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return null;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends RegisteredService> T findServiceById(final long id, final Class<T> clazz) {
+        if (WARestClient.isReady()) {
+            LOG.info("Searching for application definition by id {} and type {}", id, clazz);
+            if (clazz.isInstance(OidcRegisteredService.class)) {
+                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(RegisteredClientAppService.class).read(id, ClientAppType.OIDCRP));
+            } else if (clazz.isInstance(SamlRegisteredService.class)) {
+                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(RegisteredClientAppService.class).read(id, ClientAppType.SAML2SP));
+            }
+        }
+        LOG.debug("Syncope client is not yet ready to fetch application definitions");
+        return null;
+    }
+
+}
diff --git a/wa/starter/src/main/resources/application.properties b/wa/starter/src/main/resources/application.properties
index 5ad1d51..1104f29 100644
--- a/wa/starter/src/main/resources/application.properties
+++ b/wa/starter/src/main/resources/application.properties
@@ -28,9 +28,13 @@ server.servlet.contextPath=/syncope-wa
 
 spring.resources.static-locations=classpath:/thymeleaf/static,classpath:/static
 
-management.endpoints.web.exposure.include=health,loggers
+management.endpoints.web.exposure.include=health,loggers,refresh
 management.endpoint.health.show-details=always
 
+management.endpoint.health.enabled=true
+management.endpoint.loggers.enabled=true
+management.endpoint.refresh.enabled=true
+
 ##
 # Allow configuration classes to override bean definitions from Spring Boot
 #
diff --git a/wa/starter/src/main/resources/wa.properties b/wa/starter/src/main/resources/wa.properties
index 5d1ef1c..c98a6e7 100644
--- a/wa/starter/src/main/resources/wa.properties
+++ b/wa/starter/src/main/resources/wa.properties
@@ -24,6 +24,13 @@ conf.directory=${conf.directory}
 cas.standalone.configurationDirectory=${conf.directory}
 cas.authn.oidc.jwks.jwksFile=file:${conf.directory}/oidc.keystore
 
+cas.server.name=http://localhost:8080
+cas.server.prefix=${cas.server.name}/syncope-wa
+cas.server.scope=syncope.org
+
+cas.authn.samlIdp.entityId=https://syncope.apache.org/saml
+cas.authn.samlIdp.metadata.location=file:${conf.directory}
+
 # Disable access to the login endpoint
 # if no target application is specified.
 cas.sso.allow-missing-service-parameter=true
@@ -31,3 +38,15 @@ cas.sso.allow-missing-service-parameter=true
 # Disable the acceptable usage policy
 # by default for now.
 cas.acceptableUsagePolicy.enabled=false
+
+management.endpoints.web.exposure.include=health,loggers,refresh
+management.endpoint.health.show-details=always
+
+management.endpoint.health.enabled=true
+management.endpoint.loggers.enabled=true
+management.endpoint.refresh.enabled=true
+
+cas.monitor.endpoints.endpoint.defaults.access=AUTHENTICATED
+
+spring.security.user.name=${anonymousUser}
+spring.security.user.password=${anonymousKey}


[syncope] 07/08: [SYNCOPE-160] WA service refactoring

Posted by il...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ilgrosso pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git

commit 2a1d9e8c131cea6c4276e49e951657326fb2c0eb
Author: Francesco Chicchiriccò <il...@apache.org>
AuthorDate: Thu Apr 9 16:24:19 2020 +0200

    [SYNCOPE-160] WA service refactoring
---
 .../WAClientApp.java}                              |   4 +-
 .../apache/syncope/common/lib/wa/package-info.java |  16 +-
 .../api/service/RegisteredClientAppService.java    | 133 ------------
 .../rest/api/service/wa/WAClientAppService.java    |  78 +++++++
 .../core/logic/RegisteredClientAppLogic.java       | 238 ---------------------
 .../syncope/core/logic/wa/WAClientAppLogic.java    | 160 ++++++++++++++
 .../service/RegisteredClientAppServiceImpl.java    |  96 ---------
 .../cxf/service/wa/WAClientAppServiceImpl.java     |  50 +++++
 .../core/rest/cxf/service/AbstractServiceImpl.java |   2 +-
 .../WAClientAppBinder.java}                        |   9 +-
 .../java/data/RegisteredClientAppBinderImpl.java   |  84 --------
 .../java/data/wa/WAClientAppBinderImpl.java        |  74 +++++++
 .../org/apache/syncope/fit/AbstractITCase.java     |   1 -
 .../fit/core/RegisteredClientAppITCase.java        | 114 ----------
 .../apache/syncope/fit/core/WAClientAppITCase.java |  83 +++++++
 .../syncope/wa/mapper/RegisteredServiceMapper.java | 205 ------------------
 .../wa/starter/RegisteredServiceMapper.java        | 205 ++++++++++++++++++
 .../starter/{rest => }/SyncopeServiceRegistry.java |  82 +++----
 .../syncope/wa/starter/SyncopeWAApplication.java   |  15 +-
 .../syncope/wa/starter/SyncopeWAConfiguration.java |   1 -
 20 files changed, 704 insertions(+), 946 deletions(-)

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/wa/WAClientApp.java
similarity index 95%
rename from common/am/lib/src/main/java/org/apache/syncope/common/lib/to/RegisteredClientAppTO.java
rename to common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/WAClientApp.java
index 6b56357..524ffc9 100644
--- 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/wa/WAClientApp.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.common.lib.to;
+package org.apache.syncope.common.lib.wa;
 
 import java.io.Serializable;
 import org.apache.syncope.common.lib.policy.AccessPolicyConf;
@@ -24,7 +24,7 @@ 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 {
+public class WAClientApp implements Serializable {
 
     private static final long serialVersionUID = 6633251825655119506L;
 
diff --git a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/package-info.java
similarity index 62%
copy from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/package-info.java
index d422534..988e450 100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/wa/package-info.java
@@ -16,13 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.api.data;
+@XmlSchema(namespace = SyncopeConstants.NS)
+@XmlJavaTypeAdapters({ @XmlJavaTypeAdapter(type = Date.class, value = DateAdapter.class), })
+package org.apache.syncope.common.lib.wa;
 
-import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
-import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.jaxb.DateAdapter;
 
-public interface RegisteredClientAppBinder {
+import javax.xml.bind.annotation.XmlSchema;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
 
-    RegisteredClientAppTO getRegisteredClientAppTO(ClientApp clientApp);
-
-}
+import java.util.Date;
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
deleted file mode 100644
index cd55423..0000000
--- a/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/RegisteredClientAppService.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * 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/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAClientAppService.java b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAClientAppService.java
new file mode 100644
index 0000000..16c0521
--- /dev/null
+++ b/common/am/rest-api/src/main/java/org/apache/syncope/common/rest/api/service/wa/WAClientAppService.java
@@ -0,0 +1,78 @@
+/*
+ * 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.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.security.SecurityRequirements;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import java.util.List;
+import javax.validation.constraints.NotNull;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.wa.WAClientApp;
+import org.apache.syncope.common.rest.api.service.JAXRSService;
+
+/**
+ * REST operations for WA.
+ */
+@Tag(name = "WA")
+@SecurityRequirements({
+    @SecurityRequirement(name = "BasicAuthentication"),
+    @SecurityRequirement(name = "Bearer") })
+@Path("wa/clientApps")
+public interface WAClientAppService extends JAXRSService {
+
+    /**
+     * Returns a list of all client applications available.
+     *
+     * @return list of all client applications.
+     */
+    @GET
+    @Produces({ MediaType.APPLICATION_JSON })
+    List<WAClientApp> list();
+
+    /**
+     * Returns client application with matching type and clientAppId, if found.
+     *
+     * @param clientAppId registered client application ID to be read
+     * @param type client application type
+     * @return client application with matching type and clientAppId
+     */
+    @GET
+    @Path("{clientAppId}")
+    @Produces({ MediaType.APPLICATION_JSON })
+    WAClientApp read(@NotNull @PathParam("clientAppId") Long clientAppId, @QueryParam("type") ClientAppType type);
+
+    /**
+     * Returns client application with matching type and name, if found.
+     *
+     * @param name registered client application name to be read
+     * @param type client application type
+     * @return client application with matching type and name
+     */
+    @GET
+    @Path("byName/{name}")
+    @Produces({ MediaType.APPLICATION_JSON })
+    WAClientApp read(@NotNull @PathParam("name") String name, @QueryParam("type") ClientAppType type);
+}
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
deleted file mode 100644
index f9b162a..0000000
--- a/core/am/logic/src/main/java/org/apache/syncope/core/logic/RegisteredClientAppLogic.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java b/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
new file mode 100644
index 0000000..1bb6cd5
--- /dev/null
+++ b/core/am/logic/src/main/java/org/apache/syncope/core/logic/wa/WAClientAppLogic.java
@@ -0,0 +1,160 @@
+/*
+ * 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.wa;
+
+import static org.apache.syncope.common.lib.types.ClientAppType.OIDCRP;
+import static org.apache.syncope.common.lib.types.ClientAppType.SAML2SP;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.syncope.common.lib.wa.WAClientApp;
+import org.apache.syncope.common.lib.types.ClientAppType;
+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.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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.syncope.core.provisioning.api.data.wa.WAClientAppBinder;
+
+@Component
+public class WAClientAppLogic {
+
+    @Autowired
+    private WAClientAppBinder binder;
+
+    @Autowired
+    private SAML2SPDAO saml2spDAO;
+
+    @Autowired
+    private OIDCRPDAO oidcrpDAO;
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public List<WAClientApp> list() {
+        List<WAClientApp> clientApps = new ArrayList<>();
+
+        Stream.of(ClientAppType.values()).forEach(type -> {
+            switch (type) {
+                case OIDCRP:
+                    clientApps.addAll(oidcrpDAO.findAll().stream().
+                            map(binder::getWAClientApp).collect(Collectors.toList()));
+                    break;
+
+                case SAML2SP:
+                default:
+                    clientApps.addAll(saml2spDAO.findAll().stream().
+                            map(binder::getWAClientApp).collect(Collectors.toList()));
+            }
+        });
+
+        return clientApps;
+    }
+
+    private WAClientApp doRead(final Long clientAppId, final ClientAppType type) {
+        WAClientApp clientApp = null;
+
+        switch (type) {
+            case OIDCRP:
+                OIDCRP oidcrp = oidcrpDAO.findByClientAppId(clientAppId);
+                if (oidcrp != null) {
+                    clientApp = binder.getWAClientApp(oidcrp);
+                }
+                break;
+
+            case SAML2SP:
+                SAML2SP saml2sp = saml2spDAO.findByClientAppId(clientAppId);
+                if (saml2sp != null) {
+                    clientApp = binder.getWAClientApp(saml2sp);
+                }
+                break;
+
+            default:
+        }
+
+        return clientApp;
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public WAClientApp read(final Long clientAppId, final ClientAppType type) {
+        WAClientApp clientApp = null;
+        if (type == null) {
+            for (int i = 0; i < ClientAppType.values().length && clientApp == null; i++) {
+                clientApp = doRead(clientAppId, ClientAppType.values()[i]);
+            }
+        } else {
+            clientApp = doRead(clientAppId, type);
+        }
+
+        if (clientApp == null) {
+            throw new NotFoundException(
+                    "Client app with clientApp ID " + clientAppId + " and type " + type + " not found");
+        }
+        return clientApp;
+    }
+
+    private WAClientApp doRead(final String name, final ClientAppType type) {
+        WAClientApp clientApp = null;
+
+        switch (type) {
+            case OIDCRP:
+                OIDCRP oidcrp = oidcrpDAO.findByName(name);
+                if (oidcrp != null) {
+                    clientApp = binder.getWAClientApp(oidcrp);
+                }
+                break;
+
+            case SAML2SP:
+                SAML2SP saml2sp = saml2spDAO.findByName(name);
+                if (saml2sp != null) {
+                    clientApp = binder.getWAClientApp(saml2sp);
+                }
+                break;
+
+            default:
+        }
+
+        return clientApp;
+    }
+
+    @PreAuthorize("hasRole('" + IdRepoEntitlement.ANONYMOUS + "')")
+    @Transactional(readOnly = true)
+    public WAClientApp read(final String name, final ClientAppType type) {
+        WAClientApp clientApp = null;
+        if (type == null) {
+            for (int i = 0; i < ClientAppType.values().length && clientApp == null; i++) {
+                clientApp = doRead(name, ClientAppType.values()[i]);
+            }
+        } else {
+            clientApp = doRead(name, type);
+        }
+
+        if (clientApp == null) {
+            throw new NotFoundException("Client app with name " + name + " with type " + type + " not found");
+        }
+        return clientApp;
+    }
+}
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
deleted file mode 100644
index 9a858fd..0000000
--- a/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/RegisteredClientAppServiceImpl.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAClientAppServiceImpl.java b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAClientAppServiceImpl.java
new file mode 100644
index 0000000..fcd9061
--- /dev/null
+++ b/core/am/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/wa/WAClientAppServiceImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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 java.util.List;
+import org.apache.syncope.common.lib.types.ClientAppType;
+import org.apache.syncope.common.lib.wa.WAClientApp;
+import org.apache.syncope.common.rest.api.service.wa.WAClientAppService;
+import org.apache.syncope.core.logic.wa.WAClientAppLogic;
+import org.apache.syncope.core.rest.cxf.service.AbstractServiceImpl;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Service
+public class WAClientAppServiceImpl extends AbstractServiceImpl implements WAClientAppService {
+
+    @Autowired
+    private WAClientAppLogic logic;
+
+    @Override
+    public List<WAClientApp> list() {
+        return logic.list();
+    }
+
+    @Override
+    public WAClientApp read(final Long clientAppId, final ClientAppType type) {
+        return logic.read(clientAppId, type);
+    }
+
+    @Override
+    public WAClientApp read(final String name, final ClientAppType type) {
+        return logic.read(name, type);
+    }
+}
diff --git a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
index 7fe01c3..f61260d 100644
--- a/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
+++ b/core/idrepo/rest-cxf/src/main/java/org/apache/syncope/core/rest/cxf/service/AbstractServiceImpl.java
@@ -50,7 +50,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 
-abstract class AbstractServiceImpl implements JAXRSService {
+public abstract class AbstractServiceImpl implements JAXRSService {
 
     protected static final Logger LOG = LoggerFactory.getLogger(AbstractServiceImpl.class);
 
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/wa/WAClientAppBinder.java
similarity index 78%
rename from core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/RegisteredClientAppBinder.java
rename to core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/data/wa/WAClientAppBinder.java
index d422534..c95f9a6 100644
--- 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/wa/WAClientAppBinder.java
@@ -16,13 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.core.provisioning.api.data;
+package org.apache.syncope.core.provisioning.api.data.wa;
 
-import org.apache.syncope.common.lib.to.RegisteredClientAppTO;
+import org.apache.syncope.common.lib.wa.WAClientApp;
 import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
 
-public interface RegisteredClientAppBinder {
-
-    RegisteredClientAppTO getRegisteredClientAppTO(ClientApp clientApp);
+public interface WAClientAppBinder {
 
+    WAClientApp getWAClientApp(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
deleted file mode 100644
index c8aae5b..0000000
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/RegisteredClientAppBinderImpl.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * 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/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/wa/WAClientAppBinderImpl.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/wa/WAClientAppBinderImpl.java
new file mode 100644
index 0000000..5636892
--- /dev/null
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/data/wa/WAClientAppBinderImpl.java
@@ -0,0 +1,74 @@
+/*
+ * 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.wa;
+
+import org.apache.syncope.common.lib.wa.WAClientApp;
+import org.apache.syncope.core.persistence.api.entity.auth.ClientApp;
+import org.apache.syncope.core.provisioning.api.data.ClientAppDataBinder;
+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;
+import org.apache.syncope.core.provisioning.api.data.wa.WAClientAppBinder;
+
+@Component
+public class WAClientAppBinderImpl implements WAClientAppBinder {
+
+    private static final Logger LOG = LoggerFactory.getLogger(WAClientAppBinder.class);
+
+    @Autowired
+    private ClientAppDataBinder clientAppDataBinder;
+
+    @Override
+    public WAClientApp getWAClientApp(final ClientApp clientApp) {
+        WAClientApp waClientApp = new WAClientApp();
+        waClientApp.setClientAppTO(clientAppDataBinder.getClientAppTO(clientApp));
+
+        try {
+            if (clientApp.getAuthPolicy() != null) {
+                waClientApp.setAuthPolicyConf(
+                        ImplementationManager.build((clientApp.getAuthPolicy()).getConfiguration()));
+            } else if (clientApp.getRealm().getAuthPolicy() != null) {
+                waClientApp.setAuthPolicyConf(
+                        ImplementationManager.build((clientApp.getRealm().getAuthPolicy()).getConfiguration()));
+            }
+
+            if (clientApp.getAccessPolicy() != null) {
+                waClientApp.setAccessPolicyConf(
+                        ImplementationManager.build((clientApp.getAccessPolicy()).getConfiguration()));
+            } else if (clientApp.getRealm().getAccessPolicy() != null) {
+                waClientApp.setAccessPolicyConf(
+                        ImplementationManager.build((clientApp.getRealm().getAccessPolicy()).getConfiguration()));
+            }
+
+            if (clientApp.getAttrReleasePolicy() != null) {
+                waClientApp.setAttrReleasePolicyConf(
+                        ImplementationManager.build((clientApp.getAttrReleasePolicy()).getConfiguration()));
+            } else if (clientApp.getRealm().getAttrReleasePolicy() != null) {
+                waClientApp.setAttrReleasePolicyConf(
+                        ImplementationManager.build((clientApp.getRealm().getAttrReleasePolicy()).getConfiguration()));
+            }
+        } catch (Exception e) {
+            LOG.error("While building the configuration from an application's policy ", e);
+        }
+
+        return waClientApp;
+    }
+}
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 c8f8129..ee19a6b 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
@@ -793,5 +793,4 @@ public abstract class AbstractITCase {
         }
         return (T) getObject(response.getLocation(), ClientAppService.class, clientAppTO.getClass());
     }
-
 }
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
deleted file mode 100644
index 42fe26f..0000000
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/RegisteredClientAppITCase.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * 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()));
-    }
-
-}
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAClientAppITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAClientAppITCase.java
new file mode 100644
index 0000000..99d4ded
--- /dev/null
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/WAClientAppITCase.java
@@ -0,0 +1,83 @@
+/*
+ * 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 java.util.List;
+import org.apache.syncope.client.lib.AnonymousAuthenticationHandler;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.wa.WAClientApp;
+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.fit.AbstractITCase;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.apache.syncope.common.rest.api.service.wa.WAClientAppService;
+
+public class WAClientAppITCase extends AbstractITCase {
+
+    private static WAClientAppService waClientAppService;
+
+    @BeforeAll
+    public static void setup() {
+        SyncopeClient anonymous = clientFactory.create(
+                new AnonymousAuthenticationHandler(ANONYMOUS_UNAME, ANONYMOUS_KEY));
+        waClientAppService = anonymous.getService(WAClientAppService.class);
+    }
+
+    @Test
+    public void list() {
+        createClientApp(ClientAppType.OIDCRP, buildOIDCRP());
+
+        List<WAClientApp> list = waClientAppService.list();
+        assertFalse(list.isEmpty());
+    }
+
+    @Test
+    public void read() {
+        OIDCRPTO oidcrpto = createClientApp(ClientAppType.OIDCRP, buildOIDCRP());
+        WAClientApp waClientApp = waClientAppService.read(oidcrpto.getClientAppId(), null);
+        assertNotNull(waClientApp);
+
+        waClientApp = waClientAppService.read(oidcrpto.getClientAppId(), ClientAppType.OIDCRP);
+        assertNotNull(waClientApp);
+
+        waClientApp = waClientAppService.read(oidcrpto.getName(), null);
+        assertNotNull(waClientApp);
+
+        waClientApp = waClientAppService.read(oidcrpto.getName(), ClientAppType.OIDCRP);
+        assertNotNull(waClientApp);
+
+        SAML2SPTO samlspto = createClientApp(ClientAppType.SAML2SP, buildSAML2SP());
+        WAClientApp registeredSamlClientApp = waClientAppService.read(samlspto.getClientAppId(), null);
+        assertNotNull(registeredSamlClientApp);
+
+        registeredSamlClientApp = waClientAppService.read(samlspto.getClientAppId(), ClientAppType.SAML2SP);
+        assertNotNull(registeredSamlClientApp);
+
+        registeredSamlClientApp = waClientAppService.read(samlspto.getName(), null);
+        assertNotNull(registeredSamlClientApp);
+
+        registeredSamlClientApp = waClientAppService.read(samlspto.getName(), ClientAppType.SAML2SP);
+        assertNotNull(registeredSamlClientApp);
+    }
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/mapper/RegisteredServiceMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/mapper/RegisteredServiceMapper.java
deleted file mode 100644
index c81d1a7..0000000
--- a/wa/starter/src/main/java/org/apache/syncope/wa/mapper/RegisteredServiceMapper.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.mapper;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
-import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
-import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
-import org.apache.syncope.common.lib.policy.DefaultAuthPolicyCriteriaConf;
-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.OIDCSubjectType;
-import org.apache.syncope.common.lib.types.SAML2SPNameId;
-import org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy;
-import org.apereo.cas.services.DefaultRegisteredServiceAuthenticationPolicy;
-import org.apereo.cas.services.DenyAllAttributeReleasePolicy;
-import org.apereo.cas.services.OidcRegisteredService;
-import org.apereo.cas.services.RegisteredService;
-import org.apereo.cas.services.RegisteredServiceAccessStrategy;
-import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
-import org.apereo.cas.services.AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria;
-import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
-import org.apereo.cas.support.saml.services.SamlRegisteredService;
-
-public class RegisteredServiceMapper {
-
-    public RegisteredService toRegisteredService(final RegisteredClientAppTO clientApp) {
-
-        DefaultRegisteredServiceAuthenticationPolicy authenticationPolicy =
-                new DefaultRegisteredServiceAuthenticationPolicy();
-        AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria criteria =
-                new AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria();
-        criteria.setTryAll(((DefaultAuthPolicyCriteriaConf) clientApp.getAuthPolicyConf().getCriteria()).isAll());
-        authenticationPolicy.setCriteria(criteria);
-
-        RegisteredServiceAccessStrategy accessStrategy =
-                new DefaultRegisteredServiceAccessStrategy(
-                        clientApp.getAccessPolicyConf().isEnabled(), clientApp.getAccessPolicyConf().isSsoEnabled());
-        accessStrategy.getRequiredAttributes().putAll(clientApp.getAccessPolicyConf().getRequiredAttributes());
-
-        RegisteredServiceAttributeReleasePolicy attributeReleasePolicy;
-        if (clientApp.getAttrReleasePolicyConf() != null
-                && clientApp.getAttrReleasePolicyConf() instanceof AllowedAttrReleasePolicyConf
-                && !((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).
-                        getAllowedAttributes().isEmpty()) {
-            attributeReleasePolicy = new ReturnAllowedAttributeReleasePolicy();
-            ((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).getAllowedAttributes();
-            ((ReturnAllowedAttributeReleasePolicy) attributeReleasePolicy).getAllowedAttributes().addAll(
-                    ((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).getAllowedAttributes());
-        } else {
-            attributeReleasePolicy = new DenyAllAttributeReleasePolicy();
-        }
-
-        if (clientApp.getClientAppTO() instanceof OIDCRPTO) {
-            OIDCRPTO rp = OIDCRPTO.class.cast(clientApp.getClientAppTO());
-            OidcRegisteredService registeredService = new OidcRegisteredService();
-
-            String redirectURIs = String.join("|", rp.getRedirectUris());
-            registeredService.setServiceId(redirectURIs);
-            registeredService.setName(rp.getName());
-            registeredService.setDescription(rp.getDescription());
-            registeredService.setAccessStrategy(accessStrategy);
-            registeredService.setAuthenticationPolicy(authenticationPolicy);
-            registeredService.setAttributeReleasePolicy(attributeReleasePolicy);
-
-            registeredService.setClientId(rp.getClientId());
-            registeredService.setClientSecret(rp.getClientSecret());
-            registeredService.setSignIdToken(rp.isSignIdToken());
-            registeredService.setJwks(rp.getJwks());
-            registeredService.setSubjectType(rp.getSubjectType().name());
-            registeredService.setRedirectUrl(redirectURIs);
-            registeredService.setSupportedGrantTypes((HashSet<String>) rp.getSupportedGrantTypes());
-            registeredService.setSupportedResponseTypes((HashSet<String>) rp.getSupportedResponseTypes());
-
-            return registeredService;
-        } else if (clientApp.getClientAppTO() instanceof SAML2SPTO) {
-            SAML2SPTO sp = SAML2SPTO.class.cast(clientApp.getClientAppTO());
-            SamlRegisteredService registeredService = new SamlRegisteredService();
-
-            registeredService.setServiceId(sp.getEntityId());
-            registeredService.setName(sp.getName());
-            registeredService.setDescription(sp.getDescription());
-            registeredService.setAccessStrategy(accessStrategy);
-            registeredService.setAuthenticationPolicy(authenticationPolicy);
-            registeredService.setAttributeReleasePolicy(attributeReleasePolicy);
-
-            registeredService.setMetadataLocation(sp.getMetadataLocation());
-            registeredService.setMetadataSignatureLocation(sp.getMetadataSignatureLocation());
-            registeredService.setSignAssertions(sp.isSignAssertions());
-            registeredService.setSignResponses(sp.isSignResponses());
-            registeredService.setEncryptionOptional(sp.isEncryptionOptional());
-            registeredService.setEncryptAssertions(sp.isEncryptAssertions());
-            registeredService.setRequiredAuthenticationContextClass(sp.getRequiredAuthenticationContextClass());
-            registeredService.setRequiredNameIdFormat(sp.getRequiredNameIdFormat().getNameId());
-            registeredService.setSkewAllowance(sp.getSkewAllowance());
-            registeredService.setNameIdQualifier(sp.getNameIdQualifier());
-            registeredService.setAssertionAudiences(sp.getAssertionAudiences());
-            registeredService.setServiceProviderNameIdQualifier(sp.getServiceProviderNameIdQualifier());
-            return registeredService;
-        }
-        return null;
-    }
-
-    public RegisteredClientAppTO fromRegisteredService(final RegisteredService registeredService) {
-        RegisteredClientAppTO clientApp = new RegisteredClientAppTO();
-
-        if (registeredService.getAuthenticationPolicy() != null) {
-            DefaultAuthPolicyConf authPolicyConf = new DefaultAuthPolicyConf();
-            DefaultAuthPolicyCriteriaConf criteria = new DefaultAuthPolicyCriteriaConf();
-            criteria.setAll(((DefaultAuthPolicyCriteriaConf) registeredService.
-                    getAuthenticationPolicy().getCriteria()).isAll());
-            authPolicyConf.setCriteria(criteria);
-
-            clientApp.setAuthPolicyConf(authPolicyConf);
-        }
-
-        if (registeredService.getAccessStrategy() != null) {
-            DefaultAccessPolicyConf accessPolicyConf = new DefaultAccessPolicyConf();
-            accessPolicyConf.setEnabled(
-                    ((DefaultRegisteredServiceAccessStrategy) registeredService.getAccessStrategy()).
-                            isEnabled());
-            accessPolicyConf.setSsoEnabled(((DefaultRegisteredServiceAccessStrategy) registeredService.
-                    getAccessStrategy()).
-                    isSsoEnabled());
-            accessPolicyConf.getRequiredAttributes().putAll(((DefaultRegisteredServiceAccessStrategy) registeredService.
-                    getAccessStrategy()).getRejectedAttributes());
-
-            clientApp.setAccessPolicyConf(accessPolicyConf);
-        }
-
-        if (registeredService.getAttributeReleasePolicy() != null) {
-
-            if (registeredService.getAttributeReleasePolicy() instanceof ReturnAllowedAttributeReleasePolicy) {
-                ReturnAllowedAttributeReleasePolicy returnAllowedAttributeReleasePolicy =
-                        ReturnAllowedAttributeReleasePolicy.class.cast(registeredService.getAttributeReleasePolicy());
-                AllowedAttrReleasePolicyConf allowedAttrReleasePolicyConf = new AllowedAttrReleasePolicyConf();
-                allowedAttrReleasePolicyConf.getAllowedAttributes().addAll(returnAllowedAttributeReleasePolicy.
-                        getAllowedAttributes());
-
-                clientApp.setAttrReleasePolicyConf(allowedAttrReleasePolicyConf);
-            }
-        }
-
-        if (registeredService instanceof OidcRegisteredService) {
-            OidcRegisteredService oidcRegisteredService = OidcRegisteredService.class.cast(registeredService);
-            OIDCRPTO oidcrpto = new OIDCRPTO();
-
-            Arrays.asList(registeredService.getServiceId().split("|")).forEach(redirectURI
-                    -> oidcrpto.getRedirectUris().add(redirectURI));
-            oidcrpto.setName(oidcRegisteredService.getName());
-            oidcrpto.setDescription(oidcRegisteredService.getDescription());
-            oidcrpto.setClientId(oidcRegisteredService.getClientId());
-            oidcrpto.setClientSecret(oidcRegisteredService.getClientSecret());
-            oidcrpto.setSignIdToken(oidcRegisteredService.isSignIdToken());
-            oidcrpto.setJwks(oidcRegisteredService.getJwks());
-            oidcrpto.setSubjectType(OIDCSubjectType.valueOf(oidcRegisteredService.getSubjectType()));
-            oidcrpto.getSupportedGrantTypes().addAll(oidcRegisteredService.getSupportedGrantTypes());
-            oidcrpto.getSupportedResponseTypes().addAll(oidcRegisteredService.getSupportedResponseTypes());
-
-            clientApp.setClientAppTO(oidcrpto);
-        } else if (registeredService instanceof SamlRegisteredService) {
-            SamlRegisteredService samlRegisteredService = SamlRegisteredService.class.cast(registeredService);
-            SAML2SPTO saml2spto = new SAML2SPTO();
-
-            saml2spto.setEntityId(samlRegisteredService.getServiceId());
-            saml2spto.setName(samlRegisteredService.getName());
-            saml2spto.setDescription(samlRegisteredService.getDescription());
-
-            saml2spto.setMetadataLocation(samlRegisteredService.getMetadataLocation());
-            saml2spto.setMetadataSignatureLocation(samlRegisteredService.getMetadataSignatureLocation());
-            saml2spto.setSignAssertions(samlRegisteredService.isSignAssertions());
-            saml2spto.setSignResponses(samlRegisteredService.isSignResponses());
-            saml2spto.setEncryptionOptional(samlRegisteredService.isEncryptionOptional());
-            saml2spto.setEncryptAssertions(samlRegisteredService.isEncryptAssertions());
-            saml2spto.setRequiredAuthenticationContextClass(samlRegisteredService.
-                    getRequiredAuthenticationContextClass());
-            saml2spto.setRequiredNameIdFormat(SAML2SPNameId.valueOf(samlRegisteredService.getRequiredNameIdFormat()));
-            saml2spto.setSkewAllowance(samlRegisteredService.getSkewAllowance());
-            saml2spto.setNameIdQualifier(samlRegisteredService.getNameIdQualifier());
-            saml2spto.setAssertionAudiences(samlRegisteredService.getAssertionAudiences());
-            saml2spto.setServiceProviderNameIdQualifier(samlRegisteredService.getServiceProviderNameIdQualifier());
-
-            clientApp.setClientAppTO(saml2spto);
-        }
-        return clientApp;
-    }
-}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/RegisteredServiceMapper.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/RegisteredServiceMapper.java
new file mode 100644
index 0000000..2746793
--- /dev/null
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/RegisteredServiceMapper.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.wa.starter;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import org.apache.syncope.common.lib.policy.AllowedAttrReleasePolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAccessPolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyConf;
+import org.apache.syncope.common.lib.policy.DefaultAuthPolicyCriteriaConf;
+import org.apache.syncope.common.lib.wa.WAClientApp;
+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.OIDCSubjectType;
+import org.apache.syncope.common.lib.types.SAML2SPNameId;
+import org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy;
+import org.apereo.cas.services.DefaultRegisteredServiceAuthenticationPolicy;
+import org.apereo.cas.services.DenyAllAttributeReleasePolicy;
+import org.apereo.cas.services.OidcRegisteredService;
+import org.apereo.cas.services.RegisteredService;
+import org.apereo.cas.services.RegisteredServiceAccessStrategy;
+import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicy;
+import org.apereo.cas.services.AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria;
+import org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy;
+import org.apereo.cas.support.saml.services.SamlRegisteredService;
+
+public final class RegisteredServiceMapper {
+
+    private RegisteredServiceMapper() {
+        // private constructor for static utility class
+    }
+
+    public static RegisteredService toRegisteredService(final WAClientApp clientApp) {
+        DefaultRegisteredServiceAuthenticationPolicy authPolicy = new DefaultRegisteredServiceAuthenticationPolicy();
+        AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria criteria =
+                new AnyAuthenticationHandlerRegisteredServiceAuthenticationPolicyCriteria();
+        criteria.setTryAll(((DefaultAuthPolicyCriteriaConf) clientApp.getAuthPolicyConf().getCriteria()).isAll());
+        authPolicy.setCriteria(criteria);
+
+        RegisteredServiceAccessStrategy accessStrategy = new DefaultRegisteredServiceAccessStrategy(
+                clientApp.getAccessPolicyConf().isEnabled(), clientApp.getAccessPolicyConf().isSsoEnabled());
+        accessStrategy.getRequiredAttributes().putAll(clientApp.getAccessPolicyConf().getRequiredAttributes());
+
+        RegisteredServiceAttributeReleasePolicy attributeReleasePolicy;
+        if (clientApp.getAttrReleasePolicyConf() != null
+                && clientApp.getAttrReleasePolicyConf() instanceof AllowedAttrReleasePolicyConf
+                && !((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).
+                        getAllowedAttributes().isEmpty()) {
+
+            attributeReleasePolicy = new ReturnAllowedAttributeReleasePolicy();
+            ((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).getAllowedAttributes();
+            ((ReturnAllowedAttributeReleasePolicy) attributeReleasePolicy).getAllowedAttributes().addAll(
+                    ((AllowedAttrReleasePolicyConf) clientApp.getAttrReleasePolicyConf()).getAllowedAttributes());
+        } else {
+            attributeReleasePolicy = new DenyAllAttributeReleasePolicy();
+        }
+
+        if (clientApp.getClientAppTO() instanceof OIDCRPTO) {
+            OIDCRPTO rp = OIDCRPTO.class.cast(clientApp.getClientAppTO());
+            OidcRegisteredService service = new OidcRegisteredService();
+
+            String redirectURIs = String.join("|", rp.getRedirectUris());
+            service.setServiceId(redirectURIs);
+            service.setName(rp.getName());
+            service.setDescription(rp.getDescription());
+            service.setAccessStrategy(accessStrategy);
+            service.setAuthenticationPolicy(authPolicy);
+            service.setAttributeReleasePolicy(attributeReleasePolicy);
+
+            service.setClientId(rp.getClientId());
+            service.setClientSecret(rp.getClientSecret());
+            service.setSignIdToken(rp.isSignIdToken());
+            service.setJwks(rp.getJwks());
+            service.setSubjectType(rp.getSubjectType().name());
+            service.setRedirectUrl(redirectURIs);
+            service.setSupportedGrantTypes((HashSet<String>) rp.getSupportedGrantTypes());
+            service.setSupportedResponseTypes((HashSet<String>) rp.getSupportedResponseTypes());
+
+            return service;
+        } else if (clientApp.getClientAppTO() instanceof SAML2SPTO) {
+            SAML2SPTO sp = SAML2SPTO.class.cast(clientApp.getClientAppTO());
+            SamlRegisteredService service = new SamlRegisteredService();
+
+            service.setServiceId(sp.getEntityId());
+            service.setName(sp.getName());
+            service.setDescription(sp.getDescription());
+            service.setAccessStrategy(accessStrategy);
+            service.setAuthenticationPolicy(authPolicy);
+            service.setAttributeReleasePolicy(attributeReleasePolicy);
+
+            service.setMetadataLocation(sp.getMetadataLocation());
+            service.setMetadataSignatureLocation(sp.getMetadataSignatureLocation());
+            service.setSignAssertions(sp.isSignAssertions());
+            service.setSignResponses(sp.isSignResponses());
+            service.setEncryptionOptional(sp.isEncryptionOptional());
+            service.setEncryptAssertions(sp.isEncryptAssertions());
+            service.setRequiredAuthenticationContextClass(sp.getRequiredAuthenticationContextClass());
+            service.setRequiredNameIdFormat(sp.getRequiredNameIdFormat().getNameId());
+            service.setSkewAllowance(sp.getSkewAllowance());
+            service.setNameIdQualifier(sp.getNameIdQualifier());
+            service.setAssertionAudiences(sp.getAssertionAudiences());
+            service.setServiceProviderNameIdQualifier(sp.getServiceProviderNameIdQualifier());
+
+            return service;
+        }
+
+        return null;
+    }
+
+    public static WAClientApp fromRegisteredService(final RegisteredService service) {
+        WAClientApp clientApp = new WAClientApp();
+
+        if (service.getAuthenticationPolicy() != null) {
+            DefaultAuthPolicyConf authPolicyConf = new DefaultAuthPolicyConf();
+            DefaultAuthPolicyCriteriaConf criteria = new DefaultAuthPolicyCriteriaConf();
+            criteria.setAll(((DefaultAuthPolicyCriteriaConf) service.getAuthenticationPolicy().getCriteria()).isAll());
+            authPolicyConf.setCriteria(criteria);
+
+            clientApp.setAuthPolicyConf(authPolicyConf);
+        }
+
+        if (service.getAccessStrategy() != null) {
+            DefaultAccessPolicyConf accessPolicyConf = new DefaultAccessPolicyConf();
+            accessPolicyConf.setEnabled(
+                    ((DefaultRegisteredServiceAccessStrategy) service.getAccessStrategy()).isEnabled());
+            accessPolicyConf.setSsoEnabled(
+                    ((DefaultRegisteredServiceAccessStrategy) service.getAccessStrategy()).isSsoEnabled());
+            accessPolicyConf.getRequiredAttributes().putAll(
+                    ((DefaultRegisteredServiceAccessStrategy) service.getAccessStrategy()).getRejectedAttributes());
+
+            clientApp.setAccessPolicyConf(accessPolicyConf);
+        }
+
+        if (service.getAttributeReleasePolicy() != null) {
+
+            if (service.getAttributeReleasePolicy() instanceof ReturnAllowedAttributeReleasePolicy) {
+                ReturnAllowedAttributeReleasePolicy returnAllowedAttributeReleasePolicy =
+                        ReturnAllowedAttributeReleasePolicy.class.cast(service.getAttributeReleasePolicy());
+                AllowedAttrReleasePolicyConf allowedAttrReleasePolicyConf = new AllowedAttrReleasePolicyConf();
+                allowedAttrReleasePolicyConf.getAllowedAttributes().addAll(returnAllowedAttributeReleasePolicy.
+                        getAllowedAttributes());
+
+                clientApp.setAttrReleasePolicyConf(allowedAttrReleasePolicyConf);
+            }
+        }
+
+        if (service instanceof OidcRegisteredService) {
+            OidcRegisteredService oidc = OidcRegisteredService.class.cast(service);
+            OIDCRPTO oidcrpto = new OIDCRPTO();
+
+            oidcrpto.getRedirectUris().addAll(Arrays.asList(oidc.getServiceId().split("|")));
+            oidcrpto.setName(oidc.getName());
+            oidcrpto.setDescription(oidc.getDescription());
+            oidcrpto.setClientId(oidc.getClientId());
+            oidcrpto.setClientSecret(oidc.getClientSecret());
+            oidcrpto.setSignIdToken(oidc.isSignIdToken());
+            oidcrpto.setJwks(oidc.getJwks());
+            oidcrpto.setSubjectType(OIDCSubjectType.valueOf(oidc.getSubjectType()));
+            oidcrpto.getSupportedGrantTypes().addAll(oidc.getSupportedGrantTypes());
+            oidcrpto.getSupportedResponseTypes().addAll(oidc.getSupportedResponseTypes());
+
+            clientApp.setClientAppTO(oidcrpto);
+        } else if (service instanceof SamlRegisteredService) {
+            SamlRegisteredService saml = SamlRegisteredService.class.cast(service);
+            SAML2SPTO saml2spto = new SAML2SPTO();
+
+            saml2spto.setEntityId(saml.getServiceId());
+            saml2spto.setName(saml.getName());
+            saml2spto.setDescription(saml.getDescription());
+
+            saml2spto.setMetadataLocation(saml.getMetadataLocation());
+            saml2spto.setMetadataSignatureLocation(saml.getMetadataSignatureLocation());
+            saml2spto.setSignAssertions(saml.isSignAssertions());
+            saml2spto.setSignResponses(saml.isSignResponses());
+            saml2spto.setEncryptionOptional(saml.isEncryptionOptional());
+            saml2spto.setEncryptAssertions(saml.isEncryptAssertions());
+            saml2spto.setRequiredAuthenticationContextClass(saml.getRequiredAuthenticationContextClass());
+            saml2spto.setRequiredNameIdFormat(SAML2SPNameId.valueOf(saml.getRequiredNameIdFormat()));
+            saml2spto.setSkewAllowance(saml.getSkewAllowance());
+            saml2spto.setNameIdQualifier(saml.getNameIdQualifier());
+            saml2spto.setAssertionAudiences(saml.getAssertionAudiences());
+            saml2spto.setServiceProviderNameIdQualifier(saml.getServiceProviderNameIdQualifier());
+
+            clientApp.setClientAppTO(saml2spto);
+        }
+
+        return clientApp;
+    }
+}
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/rest/SyncopeServiceRegistry.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeServiceRegistry.java
similarity index 61%
rename from wa/starter/src/main/java/org/apache/syncope/wa/starter/rest/SyncopeServiceRegistry.java
rename to wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeServiceRegistry.java
index b042262..1eddd9f 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/rest/SyncopeServiceRegistry.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeServiceRegistry.java
@@ -16,16 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.syncope.wa.starter.rest;
+package org.apache.syncope.wa.starter;
 
 import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
-import javax.ws.rs.core.Response;
 import org.apache.syncope.common.lib.types.ClientAppType;
-import org.apache.syncope.common.rest.api.service.RegisteredClientAppService;
 import org.apache.syncope.wa.WARestClient;
-import org.apache.syncope.wa.mapper.RegisteredServiceMapper;
 import org.apereo.cas.services.AbstractServiceRegistry;
 import org.apereo.cas.services.OidcRegisteredService;
 import org.apereo.cas.services.RegisteredService;
@@ -34,6 +31,7 @@ import org.apereo.cas.support.saml.services.SamlRegisteredService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.context.ConfigurableApplicationContext;
+import org.apache.syncope.common.rest.api.service.wa.WAClientAppService;
 
 public class SyncopeServiceRegistry extends AbstractServiceRegistry {
 
@@ -41,49 +39,32 @@ public class SyncopeServiceRegistry extends AbstractServiceRegistry {
 
     private final WARestClient restClient;
 
-    private final RegisteredServiceMapper mapper;
-
-    public SyncopeServiceRegistry(final WARestClient restClient,
+    public SyncopeServiceRegistry(
+            final WARestClient restClient,
             final ConfigurableApplicationContext applicationContext,
             final Collection<ServiceRegistryListener> serviceRegistryListeners) {
 
         super(applicationContext, serviceRegistryListeners);
         this.restClient = restClient;
-        this.mapper = new RegisteredServiceMapper();
     }
 
     @Override
     public RegisteredService save(final RegisteredService registeredService) {
-        if (WARestClient.isReady()) {
-            LOG.info("Create application definitions");
-            Response response =
-                    restClient.getSyncopeClient().getService(RegisteredClientAppService.class).create(mapper.
-                            fromRegisteredService(registeredService));
-            if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
-                return registeredService;
-            }
-        }
-        LOG.debug("Syncope client is not yet ready to fetch application definitions");
-        return null;
+        throw new UnsupportedOperationException("Saving registered services from WA is not supported");
     }
 
     @Override
     public boolean delete(final RegisteredService registeredService) {
-        if (WARestClient.isReady()) {
-            LOG.info("Delete application definitions");
-            return restClient.getSyncopeClient().getService(RegisteredClientAppService.class).
-                    delete(registeredService.getName());
-        }
-        LOG.debug("Syncope client is not yet ready to fetch application definitions");
-        return false;
+        throw new UnsupportedOperationException("Deleting registered services from WA is not supported");
     }
 
     @Override
     public Collection<RegisteredService> load() {
         if (WARestClient.isReady()) {
             LOG.info("Loading application definitions");
-            return restClient.getSyncopeClient().getService(RegisteredClientAppService.class).list().stream().
-                    map(clientApp -> mapper.toRegisteredService(clientApp)).collect(Collectors.toList());
+            return restClient.getSyncopeClient().getService(WAClientAppService.class).list().stream().
+                    map(clientApp -> RegisteredServiceMapper.toRegisteredService(clientApp)).
+                    collect(Collectors.toList());
         }
         LOG.debug("Syncope client is not yet ready to fetch application definitions");
         return List.of();
@@ -93,8 +74,8 @@ public class SyncopeServiceRegistry extends AbstractServiceRegistry {
     public RegisteredService findServiceById(final long id) {
         if (WARestClient.isReady()) {
             LOG.info("Searching for application definition by id {}", id);
-            return mapper.toRegisteredService(restClient.getSyncopeClient().
-                    getService(RegisteredClientAppService.class).read(id));
+            return RegisteredServiceMapper.toRegisteredService(restClient.getSyncopeClient().
+                    getService(WAClientAppService.class).read(id, null));
         }
         LOG.debug("Syncope client is not yet ready to fetch application definitions");
         return null;
@@ -102,15 +83,15 @@ public class SyncopeServiceRegistry extends AbstractServiceRegistry {
 
     @Override
     @SuppressWarnings("unchecked")
-    public <T extends RegisteredService> T findServiceByExactServiceName(final String name, final Class<T> clazz) {
+    public <T extends RegisteredService> T findServiceById(final long id, final Class<T> clazz) {
         if (WARestClient.isReady()) {
-            LOG.info("Searching for application definition by name {} and type {}", name, clazz);
+            LOG.info("Searching for application definition by id {} and type {}", id, clazz);
             if (clazz.isInstance(OidcRegisteredService.class)) {
-                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
-                        getService(RegisteredClientAppService.class).read(name, ClientAppType.OIDCRP));
+                return (T) RegisteredServiceMapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(WAClientAppService.class).read(id, ClientAppType.OIDCRP));
             } else if (clazz.isInstance(SamlRegisteredService.class)) {
-                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
-                        getService(RegisteredClientAppService.class).read(name, ClientAppType.SAML2SP));
+                return (T) RegisteredServiceMapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(WAClientAppService.class).read(id, ClientAppType.SAML2SP));
             }
         }
         LOG.debug("Syncope client is not yet ready to fetch application definitions");
@@ -118,31 +99,30 @@ public class SyncopeServiceRegistry extends AbstractServiceRegistry {
     }
 
     @Override
-    public RegisteredService findServiceByExactServiceName(final String name) {
+    @SuppressWarnings("unchecked")
+    public <T extends RegisteredService> T findServiceByExactServiceName(final String name, final Class<T> clazz) {
         if (WARestClient.isReady()) {
-            LOG.info("Searching for application definition by name {}", name);
-            return mapper.toRegisteredService(restClient.getSyncopeClient().
-                    getService(RegisteredClientAppService.class).read(name));
+            LOG.info("Searching for application definition by name {} and type {}", name, clazz);
+            if (clazz.isInstance(OidcRegisteredService.class)) {
+                return (T) RegisteredServiceMapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(WAClientAppService.class).read(name, ClientAppType.OIDCRP));
+            } else if (clazz.isInstance(SamlRegisteredService.class)) {
+                return (T) RegisteredServiceMapper.toRegisteredService(restClient.getSyncopeClient().
+                        getService(WAClientAppService.class).read(name, ClientAppType.SAML2SP));
+            }
         }
         LOG.debug("Syncope client is not yet ready to fetch application definitions");
         return null;
     }
 
     @Override
-    @SuppressWarnings("unchecked")
-    public <T extends RegisteredService> T findServiceById(final long id, final Class<T> clazz) {
+    public RegisteredService findServiceByExactServiceName(final String name) {
         if (WARestClient.isReady()) {
-            LOG.info("Searching for application definition by id {} and type {}", id, clazz);
-            if (clazz.isInstance(OidcRegisteredService.class)) {
-                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
-                        getService(RegisteredClientAppService.class).read(id, ClientAppType.OIDCRP));
-            } else if (clazz.isInstance(SamlRegisteredService.class)) {
-                return (T) mapper.toRegisteredService(restClient.getSyncopeClient().
-                        getService(RegisteredClientAppService.class).read(id, ClientAppType.SAML2SP));
-            }
+            LOG.info("Searching for application definition by name {}", name);
+            return RegisteredServiceMapper.toRegisteredService(restClient.getSyncopeClient().
+                    getService(WAClientAppService.class).read(name, null));
         }
         LOG.debug("Syncope client is not yet ready to fetch application definitions");
         return null;
     }
-
 }
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
index 887ad97..1b43386 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAApplication.java
@@ -18,12 +18,14 @@
  */
 package org.apache.syncope.wa.starter;
 
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Date;
+import org.apache.commons.lang.StringUtils;
 import org.apereo.cas.configuration.CasConfigurationProperties;
 import org.apereo.cas.configuration.CasConfigurationPropertiesValidator;
 import org.apereo.cas.util.AsciiArtUtils;
 import org.apereo.cas.util.DateTimeUtils;
-
-import org.apache.commons.lang.StringUtils;
 import org.quartz.Job;
 import org.quartz.JobBuilder;
 import org.quartz.JobDetail;
@@ -62,10 +64,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
 import org.springframework.transaction.annotation.EnableTransactionManagement;
 
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.util.Date;
-
 @PropertySource("classpath:wa.properties")
 @PropertySource(value = "file:${conf.directory}/wa.properties", ignoreResourceNotFound = true)
 @SpringBootApplication(exclude = {
@@ -112,7 +110,7 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
     private static void validateConfiguration(final ApplicationReadyEvent event) {
         if (!Boolean.getBoolean("SKIP_CONFIG_VALIDATION")) {
             CasConfigurationPropertiesValidator validator =
-                new CasConfigurationPropertiesValidator(event.getApplicationContext());
+                    new CasConfigurationPropertiesValidator(event.getApplicationContext());
             validator.validate();
         }
     }
@@ -132,7 +130,7 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
     private void scheduleJobToRefreshContext() {
         try {
             Date date = Date.from(LocalDateTime.now().plusSeconds(this.contextRefreshDelay).
-                atZone(ZoneId.systemDefault()).toInstant());
+                    atZone(ZoneId.systemDefault()).toInstant());
             Trigger trigger = TriggerBuilder.newTrigger().startAt(date).build();
             JobKey jobKey = new JobKey(getClass().getSimpleName());
 
@@ -144,6 +142,7 @@ public class SyncopeWAApplication extends SpringBootServletInitializer {
     }
 
     private class RefreshApplicationContextJob implements Job {
+
         @Override
         public void execute(final JobExecutionContext jobExecutionContext) {
             try {
diff --git a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
index 07661e0..938eb39 100644
--- a/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
+++ b/wa/starter/src/main/java/org/apache/syncope/wa/starter/SyncopeWAConfiguration.java
@@ -23,7 +23,6 @@ import org.apache.syncope.common.keymaster.client.api.model.NetworkService;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStart;
 import org.apache.syncope.common.keymaster.client.api.startstop.KeymasterStop;
 import org.apache.syncope.wa.WARestClient;
-import org.apache.syncope.wa.starter.rest.SyncopeServiceRegistry;
 import org.apereo.cas.services.ServiceRegistryExecutionPlanConfigurer;
 import org.apereo.cas.services.ServiceRegistryListener;
 import org.springframework.beans.factory.annotation.Autowired;