Skip to content

Commit

Permalink
[SYNCOPE-1843] Support Support Azure AD authentication and attribute …
Browse files Browse the repository at this point in the history
…resolution
  • Loading branch information
ilgrosso committed Nov 14, 2024
1 parent 538aea9 commit c004650
Show file tree
Hide file tree
Showing 9 changed files with 262 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ interface Mapper {
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.attr;

import java.util.Map;
import org.apache.syncope.common.lib.AbstractAzureActiveDirectoryConf;
import org.apache.syncope.common.lib.to.AttrRepoTO;

public class AzureActiveDirectoryAttrRepoConf extends AbstractAzureActiveDirectoryConf implements AttrRepoConf {

private static final long serialVersionUID = -2365294132437794196L;

/**
* Whether attribute repository should consider the underlying attribute names in a case-insensitive manner.
*/
private boolean caseInsensitive;

public boolean isCaseInsensitive() {
return caseInsensitive;
}

public void setCaseInsensitive(final boolean caseInsensitive) {
this.caseInsensitive = caseInsensitive;
}

@Override
public Map<String, Object> map(final AttrRepoTO attrRepo, final Mapper mapper) {
return mapper.map(attrRepo, this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ interface Mapper {

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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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]
====
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand All @@ -32,6 +33,7 @@
import org.apache.syncope.wa.bootstrap.WARestClient;
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;
Expand Down Expand Up @@ -115,4 +117,21 @@ public Map<String, Object> map(final AttrRepoTO attrRepoTO, final SyncopeAttrRep

return prefix("cas.authn.attribute-repository.syncope.", WAConfUtils.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[].", WAConfUtils.asMap(props));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand All @@ -49,6 +50,7 @@
import org.apache.syncope.common.lib.types.AuthModuleState;
import org.apache.syncope.wa.bootstrap.WARestClient;
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;
Expand Down Expand Up @@ -394,6 +396,23 @@ public Map<String, Object> map(final AuthModuleTO authModuleTO, final SyncopeAut
return prefix("cas.authn.syncope.", WAConfUtils.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.setLoginUrl(conf.getLoginUrl());
props.setResource(conf.getResource());
props.setClientId(conf.getClientId());
props.setClientSecret(conf.getClientSecret());
props.setTenant(conf.getTenant());
props.setScope(conf.getScope());
props.setCredentialCriteria(conf.getCredentialCriteria());

return prefix("cas.authn.azure-active-directory.", WAConfUtils.asMap(props));
}

@Override
public Map<String, Object> map(final AuthModuleTO authModuleTO, final GoogleMfaAuthModuleConf conf) {
GoogleAuthenticatorMultifactorProperties props = new GoogleAuthenticatorMultifactorProperties();
Expand Down

0 comments on commit c004650

Please sign in to comment.