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>

Reply via email to