This is an automated email from the ASF dual-hosted git repository.
mmoayyed pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/master by this push:
new ff32f95 SYNCOPE-1599: support for duo security mfa (#227)
ff32f95 is described below
commit ff32f95588b3ff975050c3ee68bfbb9093efd4ad
Author: Misagh Moayyed <[email protected]>
AuthorDate: Thu Nov 19 11:51:00 2020 +0400
SYNCOPE-1599: support for duo security mfa (#227)
---
.../common/lib/auth/DuoMfaAuthModuleConf.java | 64 ++++++++++++++++++++++
.../src/test/resources/domains/MasterContent.xml | 2 +
.../core/persistence/jpa/inner/AuthModuleTest.java | 36 ++++++++++++
.../src/test/resources/domains/MasterContent.xml | 2 +
.../apache/syncope/fit/core/AuthModuleITCase.java | 48 +++++++++++++++-
pom.xml | 5 ++
.../bootstrap/SyncopeWAPropertySourceLocator.java | 32 +++++++++++
wa/starter/pom.xml | 4 ++
8 files changed, 192 insertions(+), 1 deletion(-)
diff --git
a/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/DuoMfaAuthModuleConf.java
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/DuoMfaAuthModuleConf.java
new file mode 100644
index 0000000..15a4909
--- /dev/null
+++
b/common/am/lib/src/main/java/org/apache/syncope/common/lib/auth/DuoMfaAuthModuleConf.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+public class DuoMfaAuthModuleConf extends AbstractAuthModuleConf {
+
+ private static final long serialVersionUID = -2883257599439312426L;
+
+ private String integrationKey;
+
+ private String secretKey;
+
+ private String applicationKey;
+
+ private String apiHost;
+
+ public String getIntegrationKey() {
+ return integrationKey;
+ }
+
+ public void setIntegrationKey(final String integrationKey) {
+ this.integrationKey = integrationKey;
+ }
+
+ public String getSecretKey() {
+ return secretKey;
+ }
+
+ public void setSecretKey(final String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public String getApplicationKey() {
+ return applicationKey;
+ }
+
+ public void setApplicationKey(final String applicationKey) {
+ this.applicationKey = applicationKey;
+ }
+
+ public String getApiHost() {
+ return apiHost;
+ }
+
+ public void setApiHost(final String apiHost) {
+ this.apiHost = apiHost;
+ }
+}
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 3bc5e90..e93becb 100644
--- a/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa-json/src/test/resources/domains/MasterContent.xml
@@ -67,6 +67,8 @@ under the License.
description="JDBC auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.JDBCAuthModuleConf","sql":"SELECT
* FROM users_table WHERE name=?", "fieldPassword": "password"}'/>
<AuthModule id="DefaultGoogleMfaAuthModule"
description="Google Mfa auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf","codeDigits":6,"issuer":"SyncopeTest",
"label":"SyncopeTest", "timeStepSize":30, "windowSize":3}'/>
+ <AuthModule id="DefaultDuoMfaAuthModule"
+ description="Duo Mfa auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.DuoMfaAuthModuleConf","integrationKey":"DIOXVRZD2UMZ8XXMNFQ5","secretKey":"Q2IU2i8BFNd6VYflZT8Evl6lF7oPlj3PM15BmRU7",
"applicationKey":"u1IHBaREMB7Cb5S4QMISAgHycpj8lPBkDGfWt23I",
"apiHost":"theapi.duosecurity.com"}'/>
<AuthModule id="DefaultOIDCAuthModule"
description="OIDC auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.OIDCAuthModuleConf","discoveryUri":"https://accounts.google.com/.well-known/openid-configuration",
"id":"client-id", "secret": "client-secret" }'/>
<AuthModule id="DefaultSAML2IdPAuthModule"
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 876a0e0..92edeec 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
@@ -28,6 +28,7 @@ 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.DuoMfaAuthModuleConf;
import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
import org.apache.syncope.common.lib.auth.JaasAuthModuleConf;
@@ -74,6 +75,10 @@ public class AuthModuleTest extends AbstractTest {
assertNotNull(authModule);
assertTrue(authModule.getConf() instanceof GoogleMfaAuthModuleConf);
+ authModule = authModuleDAO.find("DefaultDuoMfaAuthModule");
+ assertNotNull(authModule);
+ assertTrue(authModule.getConf() instanceof DuoMfaAuthModuleConf);
+
authModule = authModuleDAO.find("DefaultOIDCAuthModule");
assertNotNull(authModule);
assertTrue(authModule.getConf() instanceof OIDCAuthModuleConf);
@@ -116,6 +121,9 @@ public class AuthModuleTest extends AbstractTest {
authModule -> isSpecificConf(authModule.getConf(),
GoogleMfaAuthModuleConf.class)
&& authModule.getKey().equals("DefaultGoogleMfaAuthModule")));
assertTrue(authModules.stream().anyMatch(
+ authModule -> isSpecificConf(authModule.getConf(),
DuoMfaAuthModuleConf.class)
+ && authModule.getKey().equals("DefaultDuoMfaAuthModule")));
+ assertTrue(authModules.stream().anyMatch(
authModule -> isSpecificConf(authModule.getConf(),
OIDCAuthModuleConf.class)
&& authModule.getKey().equals("DefaultOIDCAuthModule")));
assertTrue(authModules.stream().anyMatch(
@@ -185,6 +193,16 @@ public class AuthModuleTest extends AbstractTest {
}
@Test
+ public void saveWithDuoAuthenticatorModule() {
+ DuoMfaAuthModuleConf conf = new DuoMfaAuthModuleConf();
+ conf.setSecretKey("Q2IU2i6BFNd6VYflZT8Evl6lF7oPlj4PM15BmRU7");
+ conf.setIntegrationKey("DIOXVRZD1UMZ8XXMNFQ6");
+ conf.setApiHost("theapi.duosecurity.com");
+ conf.setApplicationKey("u4IHCaREMB7Cb0S6QMISAgHycpj6lPBkDGfWt99I");
+ saveAuthModule("DuoMfaAuthModuleTest", conf);
+ }
+
+ @Test
public void saveWithOIDCAuthModule() {
OIDCAuthModuleConf conf = new OIDCAuthModuleConf();
conf.setId("OIDCTestId");
@@ -293,6 +311,24 @@ public class AuthModuleTest extends AbstractTest {
}
@Test
+ public void updateWithDuoMfaModule() {
+ AuthModule module = authModuleDAO.find("DefaultDuoMfaAuthModule");
+ assertNotNull(module);
+ AuthModuleConf conf = module.getConf();
+ String secretKey = UUID.randomUUID().toString();
+ DuoMfaAuthModuleConf.class.cast(conf).setSecretKey(secretKey);
+ module.setConf(conf);
+
+ module = authModuleDAO.save(module);
+ assertNotNull(module);
+ assertNotNull(module.getKey());
+ AuthModule found = authModuleDAO.find(module.getKey());
+ assertNotNull(found);
+ assertEquals(secretKey,
DuoMfaAuthModuleConf.class.cast(found.getConf()).getSecretKey());
+ }
+
+
+ @Test
public void updateWithSAML2IdPModule() {
AuthModule module = authModuleDAO.find("DefaultSAML2IdPAuthModule");
assertNotNull(module);
diff --git a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
index 5f3c45f..2068f09 100644
--- a/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
+++ b/core/persistence-jpa/src/test/resources/domains/MasterContent.xml
@@ -67,6 +67,8 @@ under the License.
description="JDBC auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.JDBCAuthModuleConf","sql":"SELECT
* FROM users_table WHERE name=?", "fieldPassword": "password"}'/>
<AuthModule id="DefaultGoogleMfaAuthModule"
description="Google Mfa auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf","codeDigits":6,"issuer":"SyncopeTest",
"label":"SyncopeTest", "timeStepSize":30, "windowSize":3}'/>
+ <AuthModule id="DefaultDuoMfaAuthModule"
+ description="Duo Mfa auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.DuoMfaAuthModuleConf","integrationKey":"DIOXVRZD2UMZ8XXMNFQ5","secretKey":"Q2IU2i8BFNd6VYflZT8Evl6lF7oPlj3PM15BmRU7",
"applicationKey":"u1IHBaREMB7Cb5S4QMISAgHycpj8lPBkDGfWt23I",
"apiHost":"theapi.duosecurity.com"}'/>
<AuthModule id="DefaultOIDCAuthModule"
description="OIDC auth module"
jsonConf='{"_class":"org.apache.syncope.common.lib.auth.OIDCAuthModuleConf","discoveryUri":"https://accounts.google.com/.well-known/openid-configuration",
"id":"client-id", "secret": "client-secret" }'/>
<AuthModule id="DefaultSAML2IdPAuthModule"
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 2082339..e04fd31 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
@@ -25,6 +25,7 @@ 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.common.lib.auth.DuoMfaAuthModuleConf;
import org.apache.syncope.fit.AbstractITCase;
import org.junit.jupiter.api.Test;
import java.io.IOException;
@@ -52,6 +53,7 @@ public class AuthModuleITCase extends AbstractITCase {
private enum AuthModuleSupportedType {
GOOGLE_MFA,
+ DUO,
SAML2_IDP,
STATIC,
SYNCOPE,
@@ -91,6 +93,14 @@ public class AuthModuleITCase extends AbstractITCase {
GoogleMfaAuthModuleConf.class.cast(conf).setWindowSize(3);
break;
+ case DUO:
+ conf = new DuoMfaAuthModuleConf();
+
DuoMfaAuthModuleConf.class.cast(conf).setSecretKey("Q2IU2i6BFNd6VYflZT8Evl6lF7oPlj4PM15BmRU7");
+
DuoMfaAuthModuleConf.class.cast(conf).setIntegrationKey("DIOXVRZD1UMZ8XXMNFQ6");
+
DuoMfaAuthModuleConf.class.cast(conf).setApiHost("theapi.duosecurity.com");
+
DuoMfaAuthModuleConf.class.cast(conf).setApplicationKey("u4IHCaREMB7Cb0S6QMISAgHycpj6lPBkDGfWt99I");
+ break;
+
case JAAS:
conf = new JaasAuthModuleConf();
JaasAuthModuleConf.class.cast(conf).setKerberosKdcSystemProperty("sample-value");
@@ -183,6 +193,9 @@ public class AuthModuleITCase extends AbstractITCase {
authModule -> isSpecificConf(authModule.getConf(),
GoogleMfaAuthModuleConf.class)
&& authModule.getKey().equals("DefaultGoogleMfaAuthModule")));
assertTrue(authModuleTOs.stream().anyMatch(
+ authModule -> isSpecificConf(authModule.getConf(),
DuoMfaAuthModuleConf.class)
+ && authModule.getKey().equals("DefaultDuoMfaAuthModule")));
+ assertTrue(authModuleTOs.stream().anyMatch(
authModule -> isSpecificConf(authModule.getConf(),
OIDCAuthModuleConf.class)
&& authModule.getKey().equals("DefaultOIDCAuthModule")));
assertTrue(authModuleTOs.stream().anyMatch(
@@ -236,6 +249,15 @@ public class AuthModuleITCase extends AbstractITCase {
}
@Test
+ public void getDuoMfaAuthModule() {
+ AuthModuleTO authModuleTO =
authModuleService.read("DefaultDuoMfaAuthModule");
+
+ assertNotNull(authModuleTO);
+ assertTrue(StringUtils.isNotBlank(authModuleTO.getDescription()));
+ assertTrue(isSpecificConf(authModuleTO.getConf(),
DuoMfaAuthModuleConf.class));
+ }
+
+ @Test
public void getOIDCAuthModule() {
AuthModuleTO authModuleTO =
authModuleService.read("DefaultOIDCAuthModule");
@@ -306,7 +328,7 @@ public class AuthModuleITCase extends AbstractITCase {
}
@Test
- public void create() throws IOException {
+ public void create() {
EnumSet.allOf(AuthModuleSupportedType.class).forEach(type -> {
AuthModuleTO authModuleTO =
createAuthModule(buildAuthModuleTO(type));
assertNotNull(authModuleTO);
@@ -339,6 +361,30 @@ public class AuthModuleITCase extends AbstractITCase {
}
@Test
+ public void updateDuoMfaAuthModule() {
+ AuthModuleTO duoMfaAuthModuleTO =
authModuleService.read("DefaultDuoMfaAuthModule");
+ assertNotNull(duoMfaAuthModuleTO);
+
+ AuthModuleTO newDuoMfaAuthModuleTO =
buildAuthModuleTO(AuthModuleSupportedType.DUO);
+ newDuoMfaAuthModuleTO = createAuthModule(newDuoMfaAuthModuleTO);
+ assertNotNull(newDuoMfaAuthModuleTO);
+
+ AuthModuleConf conf = duoMfaAuthModuleTO.getConf();
+ assertNotNull(conf);
+ String secretKey = UUID.randomUUID().toString();
+ DuoMfaAuthModuleConf.class.cast(conf).setSecretKey(secretKey);
+ newDuoMfaAuthModuleTO.setConf(conf);
+
+ // update new auth module
+ authModuleService.update(newDuoMfaAuthModuleTO);
+ newDuoMfaAuthModuleTO =
authModuleService.read(newDuoMfaAuthModuleTO.getKey());
+ assertNotNull(newDuoMfaAuthModuleTO);
+
+ conf = newDuoMfaAuthModuleTO.getConf();
+ assertEquals(secretKey,
DuoMfaAuthModuleConf.class.cast(conf).getSecretKey());
+ }
+
+ @Test
public void updateLDAPAuthModule() {
AuthModuleTO ldapAuthModuleTO =
authModuleService.read("DefaultLDAPAuthModule");
assertNotNull(ldapAuthModuleTO);
diff --git a/pom.xml b/pom.xml
index 282bc39..5f5f526 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1624,6 +1624,11 @@ under the License.
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
+ <artifactId>cas-server-support-duo</artifactId>
+ <version>${cas.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-gauth-core-mfa</artifactId>
<version>${cas.version}</version>
</dependency>
diff --git
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
index 3c1be23..cee84b8 100644
---
a/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
+++
b/wa/bootstrap/src/main/java/org/apache/syncope/wa/bootstrap/SyncopeWAPropertySourceLocator.java
@@ -20,6 +20,8 @@ package org.apache.syncope.wa.bootstrap;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
+
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
@@ -27,6 +29,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.DuoMfaAuthModuleConf;
import org.apache.syncope.common.lib.auth.GoogleMfaAuthModuleConf;
import org.apache.syncope.common.lib.auth.JDBCAuthModuleConf;
import org.apache.syncope.common.lib.auth.JaasAuthModuleConf;
@@ -48,6 +51,7 @@ import
org.apereo.cas.configuration.model.support.jdbc.JdbcAuthenticationPropert
import
org.apereo.cas.configuration.model.support.jdbc.authn.QueryJdbcAuthenticationProperties;
import
org.apereo.cas.configuration.model.support.ldap.AbstractLdapAuthenticationProperties;
import
org.apereo.cas.configuration.model.support.ldap.LdapAuthenticationProperties;
+import
org.apereo.cas.configuration.model.support.mfa.DuoSecurityMultifactorProperties;
import
org.apereo.cas.configuration.model.support.mfa.MultifactorAuthenticationProperties;
import
org.apereo.cas.configuration.model.support.mfa.gauth.GoogleAuthenticatorMultifactorProperties;
import
org.apereo.cas.configuration.model.support.mfa.u2f.U2FMultifactorProperties;
@@ -148,6 +152,32 @@ public class SyncopeWAPropertySourceLocator implements
PropertySourceLocator {
}
private static Map<String, Object> mapAuthModule(
+ final String authModule,
+ final DuoMfaAuthModuleConf conf) {
+ DuoSecurityMultifactorProperties props = new
DuoSecurityMultifactorProperties();
+ props.setName(authModule);
+ props.setDuoApiHost(conf.getApiHost());
+ props.setDuoApplicationKey(conf.getApplicationKey());
+ props.setDuoIntegrationKey(conf.getIntegrationKey());
+ props.setDuoSecretKey(conf.getSecretKey());
+
+ CasConfigurationProperties casProperties = new
CasConfigurationProperties();
+ SimpleFilterProvider filterProvider = getParentCasFilterProvider();
+ casProperties.getAuthn().getMfa().setDuo(List.of(props));
+
+ filterProvider.
+ addFilter(AuthenticationProperties.class.getSimpleName(),
+ SimpleBeanPropertyFilter.filterOutAllExcept(
+
CasCoreConfigurationUtils.getPropertyName(AuthenticationProperties.class,
+ AuthenticationProperties::getMfa))).
+
addFilter(MultifactorAuthenticationProperties.class.getSimpleName(),
+ SimpleBeanPropertyFilter.filterOutAllExcept(
+
CasCoreConfigurationUtils.getPropertyName(MultifactorAuthenticationProperties.class,
+ MultifactorAuthenticationProperties::getDuo)));
+ return filterCasProperties(casProperties, filterProvider);
+ }
+
+ private static Map<String, Object> mapAuthModule(
final String authModule,
final GoogleMfaAuthModuleConf conf) {
@@ -408,6 +438,8 @@ public class SyncopeWAPropertySourceLocator implements
PropertySourceLocator {
(SyncopeAuthModuleConf) authConf,
syncopeClient.getAddress()));
} else if (authConf instanceof GoogleMfaAuthModuleConf) {
properties.putAll(mapAuthModule(authModuleTO.getKey(),
(GoogleMfaAuthModuleConf) authConf));
+ } else if (authConf instanceof DuoMfaAuthModuleConf) {
+ properties.putAll(mapAuthModule(authModuleTO.getKey(),
(DuoMfaAuthModuleConf) authConf));
} else if (authConf instanceof JaasAuthModuleConf) {
properties.putAll(mapAuthModule(authModuleTO.getKey(),
(JaasAuthModuleConf) authConf));
} else if (authConf instanceof JDBCAuthModuleConf) {
diff --git a/wa/starter/pom.xml b/wa/starter/pom.xml
index 356b500..67a0519 100644
--- a/wa/starter/pom.xml
+++ b/wa/starter/pom.xml
@@ -240,6 +240,10 @@ under the License.
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
+ <artifactId>cas-server-support-duo</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-gauth-core-mfa</artifactId>
</dependency>
<dependency>