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();