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 2023/03/16 12:55:26 UTC

[syncope] 02/02: [SYNCOPE-1741] Provides support for Azure Active Directory delegated authentication (#427)

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 72ab59ebcf8edb0e2e595692c1e4b1df8f697d8e
Author: Fabio Martelli <fa...@tirasa.net>
AuthorDate: Thu Mar 16 13:50:13 2023 +0100

    [SYNCOPE-1741] Provides support for Azure Active Directory delegated authentication (#427)
---
 ...onf.java => AbstractOAuth20AuthModuleConf.java} |   2 +-
 .../lib/auth/AbstractOIDCAuthModuleConf.java       | 119 +++++++++++----------
 .../syncope/common/lib/auth/AuthModuleConf.java    |   2 +
 ...uthModuleConf.java => AzureAuthModuleConf.java} |  53 +++------
 .../common/lib/auth/OAuth20AuthModuleConf.java     |  15 ++-
 .../common/lib/auth/OIDCAuthModuleConf.java        |  67 +-----------
 .../concepts/authenticationmodules.adoc            |   2 +
 .../bootstrap/AuthModulePropertySourceMapper.java  |  28 +++++
 8 files changed, 128 insertions(+), 160 deletions(-)

diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOAuth20AuthModuleConf.java
similarity index 97%
copy from common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOAuth20AuthModuleConf.java
index 6af85787c4..c5478e7b9a 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOAuth20AuthModuleConf.java
@@ -21,7 +21,7 @@ package org.apache.syncope.common.lib.auth;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-public abstract class AbstractOIDCAuthModuleConf extends Pac4jAuthModuleConf {
+public abstract class AbstractOAuth20AuthModuleConf extends Pac4jAuthModuleConf {
 
     private static final long serialVersionUID = -4716170241796764061L;
 
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java
index 6af85787c4..d4a53dec2f 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AbstractOIDCAuthModuleConf.java
@@ -18,94 +18,105 @@
  */
 package org.apache.syncope.common.lib.auth;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
+public abstract class AbstractOIDCAuthModuleConf extends AbstractOAuth20AuthModuleConf {
 
-public abstract class AbstractOIDCAuthModuleConf extends Pac4jAuthModuleConf {
+    private static final long serialVersionUID = -471527731042579422L;
 
-    private static final long serialVersionUID = -4716170241796764061L;
+    protected String discoveryUri;
 
-    protected String clientId;
+    /**
+     * Whether an initial nonce should be to used
+     * initially for replay attack mitigation.
+     */
+    protected boolean useNonce;
 
-    protected String clientSecret;
+    /**
+     * The JWS algorithm to use forcefully when validating ID tokens.
+     * If none is defined, the first algorithm from metadata will be used.
+     */
+    protected String preferredJwsAlgorithm;
 
-    protected boolean enabled;
+    /**
+     * Clock skew in order to account for drift, when validating id tokens.
+     */
+    protected String maxClockSkew;
 
-    protected Map<String, String> customParams = new LinkedHashMap<>();
+    /**
+     * The response mode specifies how the result of the authorization request is formatted.
+     * Possible values includes "query", "fragment", "form_post", or "web_message"
+     */
+    protected String responseMode;
 
-    protected String tokenUrl;
+    /**
+     * Checks if sessions expire with token expiration.
+     */
+    protected boolean expireSessionWithToken;
 
-    protected String responseType = "code";
+    /**
+     * Default time period advance (in seconds) for considering an access token expired.
+     * This settings supports the java.time.Duration syntax.
+     * The format of the value will be PTnHnMnS, where n is the relevant hours, minutes or
+     * seconds part of the duration. Any fractional seconds are placed after a decimal point in the seconds section.
+     * If a section has a zero value, it is omitted. The hours, minutes and seconds will all have the same sign.
+     * Example values could be in the form of PT20S, PT15M, PT10H, PT6D, P2DT3H4M.
+     * If the value is set to 0 or never, the duration will be set to zero. If the value is blank, set to -1, or
+     * infinite, the value will effectively represent an unending duration.
+     */
+    protected String tokenExpirationAdvance;
 
-    protected String scope;
-
-    protected String userIdAttribute;
-
-    public String getClientId() {
-        return clientId;
-    }
-
-    public void setId(final String clientId) {
-        this.clientId = clientId;
-    }
-
-    public String getClientSecret() {
-        return clientSecret;
-    }
-
-    public void setClientSecret(final String clientSecret) {
-        this.clientSecret = clientSecret;
+    public String getDiscoveryUri() {
+        return discoveryUri;
     }
 
-    public boolean isEnabled() {
-        return enabled;
+    public void setDiscoveryUri(final String discoveryUri) {
+        this.discoveryUri = discoveryUri;
     }
 
-    public void setEnabled(final boolean enabled) {
-        this.enabled = enabled;
+    public boolean isUseNonce() {
+        return useNonce;
     }
 
-    public Map<String, String> getCustomParams() {
-        return customParams;
+    public void setUseNonce(final boolean useNonce) {
+        this.useNonce = useNonce;
     }
 
-    public void setCustomParams(final Map<String, String> customParams) {
-        this.customParams = customParams;
+    public String getPreferredJwsAlgorithm() {
+        return preferredJwsAlgorithm;
     }
 
-    public String getTokenUrl() {
-        return tokenUrl;
+    public void setPreferredJwsAlgorithm(final String preferredJwsAlgorithm) {
+        this.preferredJwsAlgorithm = preferredJwsAlgorithm;
     }
 
-    public void setTokenUrl(final String tokenUrl) {
-        this.tokenUrl = tokenUrl;
+    public String getMaxClockSkew() {
+        return maxClockSkew;
     }
 
-    public String getResponseType() {
-        return responseType;
+    public void setMaxClockSkew(final String maxClockSkew) {
+        this.maxClockSkew = maxClockSkew;
     }
 
-    public void setResponseType(final String responseType) {
-        this.responseType = responseType;
+    public String getResponseMode() {
+        return responseMode;
     }
 
-    public String getScope() {
-        return scope;
+    public void setResponseMode(final String responseMode) {
+        this.responseMode = responseMode;
     }
 
-    public void setScope(final String scope) {
-        this.scope = scope;
+    public boolean isExpireSessionWithToken() {
+        return expireSessionWithToken;
     }
 
-    public void setClientId(final String clientId) {
-        this.clientId = clientId;
+    public void setExpireSessionWithToken(final boolean expireSessionWithToken) {
+        this.expireSessionWithToken = expireSessionWithToken;
     }
 
-    public String getUserIdAttribute() {
-        return userIdAttribute;
+    public String getTokenExpirationAdvance() {
+        return tokenExpirationAdvance;
     }
 
-    public void setUserIdAttribute(final String userIdAttribute) {
-        this.userIdAttribute = userIdAttribute;
+    public void setTokenExpirationAdvance(final String tokenExpirationAdvance) {
+        this.tokenExpirationAdvance = tokenExpirationAdvance;
     }
 }
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 c41588b85d..2cca813cdf 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
@@ -51,6 +51,8 @@ public interface AuthModuleConf extends BaseBean {
         Map<String, Object> map(AuthModuleTO authModule, SimpleMfaAuthModuleConf conf);
 
         Map<String, Object> map(AuthModuleTO authModule, OAuth20AuthModuleConf conf);
+
+        Map<String, Object> map(AuthModuleTO authModule, AzureAuthModuleConf conf);
     }
 
     Map<String, Object> map(AuthModuleTO authModule, Mapper mapper);
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AzureAuthModuleConf.java
similarity index 53%
copy from common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java
copy to common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AzureAuthModuleConf.java
index 86fbde2282..c5cb26a85d 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/AzureAuthModuleConf.java
@@ -21,52 +21,31 @@ package org.apache.syncope.common.lib.auth;
 import java.util.Map;
 import org.apache.syncope.common.lib.to.AuthModuleTO;
 
-public class OAuth20AuthModuleConf extends AbstractOIDCAuthModuleConf implements AuthModuleConf {
+public class AzureAuthModuleConf extends AbstractOIDCAuthModuleConf implements AuthModuleConf {
 
-    private static final long serialVersionUID = 299820485764241682L;
+    private static final long serialVersionUID = -471527731042579522L;
 
-    protected String authUrl;
+    /**
+     * Azure AD tenant name. After tenant is configured, #getDiscoveryUri() property will be overridden.
+     *
+     * Azure AD tenant name can take 4 different values:
+     * - organizations: Only users with work or school accounts from Azure AD can sign in.
+     * - consumers: Only users with a personal Microsoft account can sign in.
+     * - Specific tenant domain name or ID: Only user with account under that the specified tenant can login
+     */
+    protected String tenant;
 
-    protected String profileUrl;
-
-    protected boolean withState;
-
-    protected String profileVerb = "POST";
-
-    public String getAuthUrl() {
-        return authUrl;
-    }
-
-    public void setAuthUrl(final String authUrl) {
-        this.authUrl = authUrl;
-    }
-
-    public boolean isWithState() {
-        return withState;
-    }
-
-    public void setWithState(final boolean withState) {
-        this.withState = withState;
-    }
-
-    public String getProfileUrl() {
-        return profileUrl;
-    }
-
-    public void setProfileUrl(final String profileUrl) {
-        this.profileUrl = profileUrl;
-    }
-
-    public String getProfileVerb() {
-        return profileVerb;
+    public String getTenant() {
+        return tenant;
     }
 
-    public void setProfileVerb(final String profileVerb) {
-        this.profileVerb = profileVerb;
+    public void setTenant(final String tenant) {
+        this.tenant = tenant;
     }
 
     @Override
     public Map<String, Object> map(final AuthModuleTO authModule, final Mapper mapper) {
         return mapper.map(authModule, this);
     }
+
 }
diff --git a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java
index 86fbde2282..9b04b1fe8c 100644
--- a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java
+++ b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/OAuth20AuthModuleConf.java
@@ -18,16 +18,19 @@
  */
 package org.apache.syncope.common.lib.auth;
 
+import java.util.LinkedHashMap;
 import java.util.Map;
 import org.apache.syncope.common.lib.to.AuthModuleTO;
 
-public class OAuth20AuthModuleConf extends AbstractOIDCAuthModuleConf implements AuthModuleConf {
+public class OAuth20AuthModuleConf extends AbstractOAuth20AuthModuleConf implements AuthModuleConf {
 
     private static final long serialVersionUID = 299820485764241682L;
 
     protected String authUrl;
-
+    
     protected String profileUrl;
+    
+    protected Map<String, String> profileAttrs = new LinkedHashMap<>();
 
     protected boolean withState;
 
@@ -41,6 +44,14 @@ public class OAuth20AuthModuleConf extends AbstractOIDCAuthModuleConf implements
         this.authUrl = authUrl;
     }
 
+    public Map<String, String> getProfileAttrs() {
+        return profileAttrs;
+    }
+
+    public void setProfileAttrs(final Map<String, String> profileAttrs) {
+        this.profileAttrs = profileAttrs;
+    }
+
     public boolean isWithState() {
         return withState;
     }
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
index 1419e87730..0ea9339c3b 100644
--- 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
@@ -23,72 +23,7 @@ import org.apache.syncope.common.lib.to.AuthModuleTO;
 
 public class OIDCAuthModuleConf extends AbstractOIDCAuthModuleConf implements AuthModuleConf {
 
-    private static final long serialVersionUID = -471527731042579422L;
-
-    protected String discoveryUri;
-
-    /**
-     * Whether an initial nonce should be to used
-     * initially for replay attack mitigation.
-     */
-    protected boolean useNonce;
-
-    /**
-     * The JWS algorithm to use forcefully when validating ID tokens.
-     * If none is defined, the first algorithm from metadata will be used.
-     */
-    protected String preferredJwsAlgorithm;
-
-    /**
-     * Clock skew in order to account for drift, when validating id tokens.
-     */
-    protected String maxClockSkew;
-
-    /**
-     * The response mode specifies how the result of the authorization request is formatted.
-     * Possible values includes "query", "fragment", "form_post", or "web_message"
-     */
-    protected String responseMode;
-
-    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 getPreferredJwsAlgorithm() {
-        return preferredJwsAlgorithm;
-    }
-
-    public void setPreferredJwsAlgorithm(final String preferredJwsAlgorithm) {
-        this.preferredJwsAlgorithm = preferredJwsAlgorithm;
-    }
-
-    public String getMaxClockSkew() {
-        return maxClockSkew;
-    }
-
-    public void setMaxClockSkew(final String maxClockSkew) {
-        this.maxClockSkew = maxClockSkew;
-    }
-
-    public String getResponseMode() {
-        return responseMode;
-    }
-
-    public void setResponseMode(final String responseMode) {
-        this.responseMode = responseMode;
-    }
+    private static final long serialVersionUID = -471527711042579422L;
 
     @Override
     public Map<String, Object> map(final AuthModuleTO authModule, final Mapper mapper) {
diff --git a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
index 3d0cc4e61c..3e3e5c40ba 100644
--- a/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/authenticationmodules.adoc
@@ -28,9 +28,11 @@ Several authentication modules are provided:
     ** https://apereo.github.io/cas/6.6.x/authentication/JAAS-Authentication.html[JAAS^]
     ** https://apereo.github.io/cas/6.6.x/authentication/LDAP-Authentication.html[LDAP^]
     ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication.html[OpenID Connect^]
+    ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication.html[OAuth2^]
     ** https://apereo.github.io/cas/6.6.x/authentication/Syncope-Authentication.html[Static^]
     ** https://apereo.github.io/cas/6.6.x/authentication/Syncope-Authentication.html[Syncope^]
     ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication.html[SAML^]
+    ** https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-Azure-AD.html[Azure Active Directory^]
 * MFA:
     ** https://apereo.github.io/cas/6.6.x/mfa/DuoSecurity-Authentication.html[Duo Security^]
     ** https://apereo.github.io/cas/6.6.x/mfa/FIDO-U2F-Authentication.html[Fido U2F^]
diff --git a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java
index 04386f5606..a40b63086e 100644
--- a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java
+++ b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/AuthModulePropertySourceMapper.java
@@ -26,6 +26,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.auth.AuthModuleConf;
+import org.apache.syncope.common.lib.auth.AzureAuthModuleConf;
 import org.apache.syncope.common.lib.auth.DuoMfaAuthModuleConf;
 import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
 import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
@@ -55,6 +56,7 @@ import org.apereo.cas.configuration.model.support.mfa.gauth.LdapGoogleAuthentica
 import org.apereo.cas.configuration.model.support.mfa.simple.CasSimpleMultifactorAuthenticationProperties;
 import org.apereo.cas.configuration.model.support.mfa.u2f.U2FMultifactorAuthenticationProperties;
 import org.apereo.cas.configuration.model.support.pac4j.oauth.Pac4jOAuth20ClientProperties;
+import org.apereo.cas.configuration.model.support.pac4j.oidc.Pac4jAzureOidcClientProperties;
 import org.apereo.cas.configuration.model.support.pac4j.oidc.Pac4jGenericOidcClientProperties;
 import org.apereo.cas.configuration.model.support.pac4j.oidc.Pac4jOidcClientProperties;
 import org.apereo.cas.configuration.model.support.pac4j.saml.Pac4jSamlClientProperties;
@@ -161,6 +163,8 @@ public class AuthModulePropertySourceMapper extends PropertySourceMapper impleme
         props.setResponseType(conf.getResponseType());
         props.setScope(conf.getScope());
         props.setPrincipalIdAttribute(conf.getUserIdAttribute());
+        props.setExpireSessionWithToken(conf.isExpireSessionWithToken());
+        props.setTokenExpirationAdvance(conf.getTokenExpirationAdvance());
         Pac4jOidcClientProperties client = new Pac4jOidcClientProperties();
         client.setGeneric(props);
 
@@ -311,4 +315,28 @@ public class AuthModulePropertySourceMapper extends PropertySourceMapper impleme
 
         return prefix("cas.authn.mfa.simple.", CasCoreConfigurationUtils.asMap(props));
     }
+
+    @Override
+    public Map<String, Object> map(final AuthModuleTO authModuleTO, final AzureAuthModuleConf conf) {
+        Pac4jAzureOidcClientProperties props = new Pac4jAzureOidcClientProperties();
+        props.setId(conf.getClientId());
+        props.setSecret(conf.getClientSecret());
+        props.setClientName(Optional.ofNullable(conf.getClientName()).orElse(authModuleTO.getKey()));
+        props.setEnabled(authModuleTO.getState() == AuthModuleState.ACTIVE);
+        props.setCustomParams(conf.getCustomParams());
+        props.setDiscoveryUri(conf.getDiscoveryUri());
+        props.setMaxClockSkew(conf.getMaxClockSkew());
+        props.setPreferredJwsAlgorithm(conf.getPreferredJwsAlgorithm());
+        props.setResponseMode(conf.getResponseMode());
+        props.setResponseType(conf.getResponseType());
+        props.setScope(conf.getScope());
+        props.setPrincipalIdAttribute(conf.getUserIdAttribute());
+        props.setExpireSessionWithToken(conf.isExpireSessionWithToken());
+        props.setTokenExpirationAdvance(conf.getTokenExpirationAdvance());
+        props.setTenant(conf.getTenant());
+        Pac4jOidcClientProperties client = new Pac4jOidcClientProperties();
+        client.setAzure(props);
+
+        return prefix("cas.authn.pac4j.oidc[].azure.", CasCoreConfigurationUtils.asMap(props));
+    }
 }