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

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


The following commit(s) were added to refs/heads/3_0_X by this push:
     new fcc8d539f2 [SYNCOPE-1843] Support Support Azure AD authentication and 
attribute resolution
fcc8d539f2 is described below

commit fcc8d539f2f17cff111ef354c5320ac5ba4490c2
Author: Francesco Chicchiriccò <[email protected]>
AuthorDate: Thu Nov 14 15:02:27 2024 +0100

    [SYNCOPE-1843] Support Support Azure AD authentication and attribute 
resolution
---
 .../lib/AbstractAzureActiveDirectoryConf.java      | 119 +++++++++++++++++++++
 .../syncope/common/lib/attr/AttrRepoConf.java      |   2 +
 ....java => AzureActiveDirectoryAttrRepoConf.java} |  27 +++--
 .../syncope/common/lib/auth/AuthModuleConf.java    |   2 +
 .../auth/AzureActiveDirectoryAuthModuleConf.java   |  52 +++++++++
 .../src/test/resources/core-ojson-test.properties  |   1 +
 .../concepts/attributerepositories.adoc            |   1 +
 .../concepts/authenticationmodules.adoc            |   3 +-
 .../mapping/AttrRepoPropertySourceMapper.java      |  21 ++++
 .../mapping/AuthModulePropertySourceMapper.java    |  16 +++
 10 files changed, 232 insertions(+), 12 deletions(-)

diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/AbstractAzureActiveDirectoryConf.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/AbstractAzureActiveDirectoryConf.java
new file mode 100644
index 0000000000..d0255ed536
--- /dev/null
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/AbstractAzureActiveDirectoryConf.java
@@ -0,0 +1,119 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+public abstract class AbstractAzureActiveDirectoryConf implements Serializable 
{
+
+    private static final long serialVersionUID = 282571926999684266L;
+
+    private String clientId;
+
+    private String clientSecret;
+
+    /**
+     * This URL of the security token service that CAS goes to for acquiring 
tokens for resources and users.
+     * This URL allows CAS to establish what is called an 'authority'.
+     * You can think of the authority as the directory issuing the 
identities/tokens. The login URL here is then
+     * composed of {@code https://<instance>/<tenant>}, where 'instance' is 
the Azure AD host
+     * (such as {@code https://login.microsoftonline.com}) and 'tenant' is the 
domain name
+     * (such as {@code contoso.onmicrosoft.com}) or tenant ID of the directory.
+     * Examples of authority URL are:
+     *
+     * <ul>
+     * <li>{@code 
https://login.microsoftonline.com/f31e6716-26e8-4651-b323-2563936b4163}: for a 
single tenant
+     * application defined in the tenant</li>
+     * <li>{@code https://login.microsoftonline.com/contoso.onmicrosoft.com}: 
This representation is like the previous
+     * one, but uses the tenant domain name instead of the tenant Id.</li>
+     * <li>{@code https://login.microsoftonline.de/contoso.de}: also uses a 
domain name, but in this case the Azure AD
+     * tenant admins have set a custom domain for their tenant, and the
+     * instance URL here is for the German national cloud.</li>
+     * <li>{@code https://login.microsoftonline.com/common}: in the case of a 
multi-tenant application, that is an
+     * application available in several Azure AD tenants.</li>
+     * <li>It can finally be an Active Directory Federation Services (ADFS) 
URL, which is recognized
+     * with the convention that the URL should contain adfs like {@code 
https://contoso.com/adfs}.</li>
+     * </ul>
+     */
+    private String loginUrl = "https://login.microsoftonline.com/common/";;
+
+    /**
+     * Resource url for the graph API to fetch attributes.
+     */
+    private String resource = "https://graph.microsoft.com/";;
+
+    /**
+     * The microsoft tenant id.
+     */
+    private String tenant;
+
+    /**
+     * Scope used when fetching access tokens.
+     * Multiple scopes may be separated using a comma.
+     */
+    private String scope = "openid,email,profile,address";
+
+    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;
+    }
+
+    public String getLoginUrl() {
+        return loginUrl;
+    }
+
+    public void setLoginUrl(final String loginUrl) {
+        this.loginUrl = loginUrl;
+    }
+
+    public String getResource() {
+        return resource;
+    }
+
+    public void setResource(final String resource) {
+        this.resource = resource;
+    }
+
+    public String getTenant() {
+        return tenant;
+    }
+
+    public void setTenant(final String tenant) {
+        this.tenant = tenant;
+    }
+
+    public String getScope() {
+        return scope;
+    }
+
+    public void setScope(final String scope) {
+        this.scope = scope;
+    }
+}
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
index 18edec4b4e..682d875f94 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
@@ -35,6 +35,8 @@ public interface AttrRepoConf extends BaseBean {
         Map<String, Object> map(AttrRepoTO attrRepo, JDBCAttrRepoConf conf);
 
         Map<String, Object> map(AttrRepoTO attrRepo, SyncopeAttrRepoConf conf);
+
+        Map<String, Object> map(AttrRepoTO attrRepo, 
AzureActiveDirectoryAttrRepoConf conf);
     }
 
     Map<String, Object> map(AttrRepoTO attrRepo, Mapper mapper);
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AzureActiveDirectoryAttrRepoConf.java
similarity index 55%
copy from 
common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
copy to 
common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AzureActiveDirectoryAttrRepoConf.java
index 18edec4b4e..11d657a280 100644
--- 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AttrRepoConf.java
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/attr/AzureActiveDirectoryAttrRepoConf.java
@@ -18,24 +18,29 @@
  */
 package org.apache.syncope.common.lib.attr;
 
-import com.fasterxml.jackson.annotation.JsonTypeInfo;
 import java.util.Map;
-import org.apache.syncope.common.lib.BaseBean;
+import org.apache.syncope.common.lib.AbstractAzureActiveDirectoryConf;
 import org.apache.syncope.common.lib.to.AttrRepoTO;
 
-@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, 
property = "_class")
-public interface AttrRepoConf extends BaseBean {
+public class AzureActiveDirectoryAttrRepoConf extends 
AbstractAzureActiveDirectoryConf implements AttrRepoConf {
 
-    interface Mapper {
+    private static final long serialVersionUID = -2365294132437794196L;
 
-        Map<String, Object> map(AttrRepoTO attrRepo, StubAttrRepoConf conf);
+    /**
+     * Whether attribute repository should consider the underlying attribute 
names in a case-insensitive manner.
+     */
+    private boolean caseInsensitive;
 
-        Map<String, Object> map(AttrRepoTO attrRepo, LDAPAttrRepoConf conf);
-
-        Map<String, Object> map(AttrRepoTO attrRepo, JDBCAttrRepoConf conf);
+    public boolean isCaseInsensitive() {
+        return caseInsensitive;
+    }
 
-        Map<String, Object> map(AttrRepoTO attrRepo, SyncopeAttrRepoConf conf);
+    public void setCaseInsensitive(final boolean caseInsensitive) {
+        this.caseInsensitive = caseInsensitive;
     }
 
-    Map<String, Object> map(AttrRepoTO attrRepo, Mapper mapper);
+    @Override
+    public Map<String, Object> map(final AttrRepoTO attrRepo, final Mapper 
mapper) {
+        return mapper.map(attrRepo, this);
+    }
 }
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
index bf416cdc9d..ac3cc37a51 100644
--- 
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
@@ -52,6 +52,8 @@ public interface AuthModuleConf extends BaseBean {
 
         Map<String, Object> map(AuthModuleTO authModule, SyncopeAuthModuleConf 
conf);
 
+        Map<String, Object> map(AuthModuleTO authModule, 
AzureActiveDirectoryAuthModuleConf conf);
+
         Map<String, Object> map(AuthModuleTO authModule, X509AuthModuleConf 
conf);
 
         Map<String, Object> map(AuthModuleTO authModule, 
GoogleMfaAuthModuleConf conf);
diff --git 
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AzureActiveDirectoryAuthModuleConf.java
 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AzureActiveDirectoryAuthModuleConf.java
new file mode 100644
index 0000000000..5c0933a746
--- /dev/null
+++ 
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AzureActiveDirectoryAuthModuleConf.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.auth;
+
+import java.util.Map;
+import org.apache.syncope.common.lib.AbstractAzureActiveDirectoryConf;
+import org.apache.syncope.common.lib.to.AuthModuleTO;
+
+public class AzureActiveDirectoryAuthModuleConf extends 
AbstractAzureActiveDirectoryConf implements AuthModuleConf {
+
+    private static final long serialVersionUID = 6053163884651768614L;
+
+    /**
+     * A number of authentication handlers are allowed to determine whether 
they can operate on the provided credential
+     * and as such lend themselves to be tried and tested during the 
authentication handler selection phase.
+     * The credential criteria may be one of the following options:<ul>
+     * <li>A regular expression pattern that is tested against the credential 
identifier.</li>
+     * <li>A fully qualified class name of your own design that implements 
{@code Predicate}.</li>
+     * <li>Path to an external Groovy script that implements the same 
interface.</li>
+     * </ul>
+     */
+    private String credentialCriteria;
+
+    public String getCredentialCriteria() {
+        return credentialCriteria;
+    }
+
+    public void setCredentialCriteria(final String credentialCriteria) {
+        this.credentialCriteria = credentialCriteria;
+    }
+
+    @Override
+    public Map<String, Object> map(final AuthModuleTO authModule, final Mapper 
mapper) {
+        return mapper.map(authModule, this);
+    }
+}
diff --git 
a/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties 
b/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
index 96ce2aa12d..bc5d7a97a1 100644
--- a/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
+++ b/core/persistence-jpa-json/src/test/resources/core-ojson-test.properties
@@ -22,6 +22,7 @@ security.secretKey=${secretKey}
 
 persistence.domain[0].jdbcURL=jdbc:oracle:thin:@${DB_CONTAINER_IP}:1521/XEPDB1
 #persistence.domain[0].jdbcURL=jdbc:oracle:thin:@192.168.0.176:1521/orcl
+persistence.domain[0].dbSchema=
 persistence.domain[0].poolMaxActive=10
 persistence.domain[0].poolMinIdle=2
 
diff --git 
a/src/main/asciidoc/reference-guide/concepts/attributerepositories.adoc 
b/src/main/asciidoc/reference-guide/concepts/attributerepositories.adoc
index 146645e62b..82f56312f5 100644
--- a/src/main/asciidoc/reference-guide/concepts/attributerepositories.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/attributerepositories.adoc
@@ -27,6 +27,7 @@ Some attribute repositories are provided:
 * 
https://apereo.github.io/cas/6.6.x/integration/Attribute-Resolution-LDAP.html[LDAP^]
 * 
https://apereo.github.io/cas/6.6.x/integration/Attribute-Resolution-Stub.html[Stub^]
 * 
https://apereo.github.io/cas/6.6.x/integration/Attribute-Resolution-Syncope.html[Syncope^]
+* 
https://apereo.github.io/cas/6.6.x/integration/Attribute-Resolution-AzureAD.html[Azure
 Active Directory^]
 
 [TIP]
 ====
diff --git 
a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc 
b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
index 0de446d041..dff10d3350 100644
--- a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
@@ -29,12 +29,13 @@ Several authentication modules are provided:
     ** 
https://apereo.github.io/cas/6.6.x/authentication/LDAP-Authentication.html[LDAP^]
     ** 
https://apereo.github.io/cas/6.6.x/authentication/SPNEGO-Authentication.html[SPNEGO^]
     ** 
https://apereo.github.io/cas/6.6.x/authentication/Syncope-Authentication.html[Syncope^]
+    ** 
https://apereo.github.io/cas/6.6.x/authentication/Azure-ActiveDirectory-Authentication.html[Azure
 Active Directory^]
     ** 
https://apereo.github.io/cas/6.6.x/authentication/X509-Authentication.html[X509^]
     ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Generic-OpenID-Connect.html[OpenID
 Connect^]
     ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-OAuth20.html[OAuth2^]
     ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-SAML.htmll[SAML^]
     ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Apple.html[Apple
 Signin^]
-    ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Azure-AD.html[Azure
 Active Directory^]
+    ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Azure-AD.html[Azure
 Active Directory (OIDC)^]
     ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Google-OpenID-Connect.html[Google
 OpenID^]
     ** 
https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Keycloak.html[Keycloak^]
 * MFA:
diff --git 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java
 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java
index cbdf8bd665..5c6cf82f1d 100644
--- 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java
+++ 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AttrRepoPropertySourceMapper.java
@@ -23,6 +23,7 @@ import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.attr.AttrRepoConf;
+import org.apache.syncope.common.lib.attr.AzureActiveDirectoryAttrRepoConf;
 import org.apache.syncope.common.lib.attr.JDBCAttrRepoConf;
 import org.apache.syncope.common.lib.attr.LDAPAttrRepoConf;
 import org.apache.syncope.common.lib.attr.StubAttrRepoConf;
@@ -33,6 +34,7 @@ import org.apache.syncope.wa.bootstrap.WARestClient;
 import org.apereo.cas.configuration.CasCoreConfigurationUtils;
 import 
org.apereo.cas.configuration.model.core.authentication.AttributeRepositoryStates;
 import 
org.apereo.cas.configuration.model.core.authentication.StubPrincipalAttributesProperties;
+import 
org.apereo.cas.configuration.model.support.azuread.AzureActiveDirectoryAttributesProperties;
 import 
org.apereo.cas.configuration.model.support.jdbc.JdbcPrincipalAttributesProperties;
 import 
org.apereo.cas.configuration.model.support.ldap.LdapPrincipalAttributesProperties;
 import 
org.apereo.cas.configuration.model.support.syncope.SyncopePrincipalAttributesProperties;
@@ -116,4 +118,23 @@ public class AttrRepoPropertySourceMapper extends 
PropertySourceMapper implement
 
         return prefix("cas.authn.attribute-repository.syncope.", 
CasCoreConfigurationUtils.asMap(props));
     }
+
+    @Override
+    public Map<String, Object> map(final AttrRepoTO attrRepoTO, final 
AzureActiveDirectoryAttrRepoConf conf) {
+        AzureActiveDirectoryAttributesProperties props = new 
AzureActiveDirectoryAttributesProperties();
+        props.setId(attrRepoTO.getKey());
+        props.setOrder(attrRepoTO.getOrder());
+        props.setClientId(conf.getClientId());
+        props.setClientSecret(conf.getClientSecret());
+        props.setLoginBaseUrl(conf.getLoginUrl());
+        props.setResource(conf.getResource());
+        props.setTenant(conf.getTenant());
+        props.setScope(conf.getScope());
+        props.setCaseInsensitive(conf.isCaseInsensitive());
+        
props.setAttributes(attrRepoTO.getItems().stream().map(Item::getExtAttrName).collect(Collectors.joining(",")));
+
+        return prefix(
+                "cas.authn.attribute-repository.azure-active-directory[].",
+                CasCoreConfigurationUtils.asMap(props));
+    }
 }
diff --git 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java
 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java
index b2c43b2d65..fb1a20793b 100644
--- 
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java
+++ 
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/mapping/AuthModulePropertySourceMapper.java
@@ -28,6 +28,7 @@ import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.auth.AbstractOIDCAuthModuleConf;
 import org.apache.syncope.common.lib.auth.AppleOIDCAuthModuleConf;
 import org.apache.syncope.common.lib.auth.AuthModuleConf;
+import org.apache.syncope.common.lib.auth.AzureActiveDirectoryAuthModuleConf;
 import org.apache.syncope.common.lib.auth.AzureOIDCAuthModuleConf;
 import org.apache.syncope.common.lib.auth.DuoMfaAuthModuleConf;
 import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
@@ -51,6 +52,7 @@ import org.apache.syncope.common.lib.types.AuthModuleState;
 import org.apache.syncope.wa.bootstrap.WARestClient;
 import org.apereo.cas.configuration.CasCoreConfigurationUtils;
 import 
org.apereo.cas.configuration.model.core.authentication.AuthenticationHandlerStates;
+import 
org.apereo.cas.configuration.model.support.azuread.AzureActiveDirectoryAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.generic.AcceptAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.jaas.JaasAuthenticationProperties;
 import 
org.apereo.cas.configuration.model.support.jdbc.authn.QueryJdbcAuthenticationProperties;
@@ -402,6 +404,20 @@ public class AuthModulePropertySourceMapper extends 
PropertySourceMapper impleme
         return prefix("cas.authn.syncope.", 
CasCoreConfigurationUtils.asMap(props));
     }
 
+    @Override
+    public Map<String, Object> map(final AuthModuleTO authModuleTO, final 
AzureActiveDirectoryAuthModuleConf conf) {
+        AzureActiveDirectoryAuthenticationProperties props = new 
AzureActiveDirectoryAuthenticationProperties();
+        props.setName(authModuleTO.getKey());
+        props.setOrder(authModuleTO.getOrder());
+        
props.setState(AuthenticationHandlerStates.valueOf(authModuleTO.getState().name()));
+        props.setClientId(conf.getClientId());
+        props.setLoginUrl(conf.getLoginUrl());
+        props.setResource(conf.getResource());
+        props.setCredentialCriteria(conf.getCredentialCriteria());
+
+        return prefix("cas.authn.azure-active-directory.", 
CasCoreConfigurationUtils.asMap(props));
+    }
+
     @Override
     public Map<String, Object> map(final AuthModuleTO authModuleTO, final 
GoogleMfaAuthModuleConf conf) {
         GoogleAuthenticatorMultifactorProperties props = new 
GoogleAuthenticatorMultifactorProperties();

Reply via email to