[java-idp-plugin-duo] branch main updated: JDUO-33 - Add non-browser support to the new flow

Scott Cantor cantor.2 at osu.edu
Fri Feb 12 19:21:17 UTC 2021


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch main
in repository java-idp-plugin-duo.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=ab638ef5111364f072a2c5075bd9327ef649dca6

The following commit(s) were added to refs/heads/main by this push:
       new  ab638ef   JDUO-33 - Add non-browser support to the new flow
ab638ef is described below

commit ab638ef5111364f072a2c5075bd9327ef649dca6
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Feb 12 14:21:14 2021 -0500

    JDUO-33 - Add non-browser support to the new flow
    
    https://issues.shibboleth.net/jira/browse/JDUO-33
---
 idp-duo-impl/pom.xml                               | 10 ++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  2 +-
 .../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml   | 53 ++++++++++++++++++++--
 .../flows/authn/DuoOIDC/duo-oidc-authn-flow.xml    | 20 +++++++-
 .../impl/AbstractAuthnXmlFlowExecutionTests.java   |  1 +
 .../plugin/authn/duo/impl/DuoAuthnFlowTest.java    |  3 +-
 .../duo/nimbus/conf/authn/duo-oidc.properties      | 20 +++++++-
 .../authn/duo/sdk/conf/authn/duo-oidc.properties   | 20 +++++++-
 8 files changed, 118 insertions(+), 11 deletions(-)

diff --git a/idp-duo-impl/pom.xml b/idp-duo-impl/pom.xml
index 20df8d4..89d64a7 100644
--- a/idp-duo-impl/pom.xml
+++ b/idp-duo-impl/pom.xml
@@ -53,6 +53,16 @@
 			<artifactId>idp-authn-api</artifactId>
 			<scope>provided</scope>
 		</dependency>
+        <!--
+        Needed to reuse Duo AuthAPI code from original flow.
+        Can be removed if/when original flow is discontinued.
+        -->
+        <dependency>
+            <groupId>${idp.groupId}</groupId>
+            <artifactId>idp-authn-impl</artifactId>
+            <version>${idp.version}</version>
+            <scope>provided</scope>
+        </dependency>
 		<dependency>
 			<groupId>${opensaml.groupId}</groupId>
 			<artifactId>opensaml-messaging-api</artifactId>
diff --git a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index e353312..285d8e0 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -27,7 +27,7 @@
             p:subjectDecorator="#{getObject('%{idp.authn.DuoOIDC.subjectDecorator:}'.trim())}">
         <property name="supportedPrincipalsByString">
             <bean parent="shibboleth.CommaDelimStringArray"
-                c:_0="#{'%{idp.authn.DuoOIDC.supportedPrincipals:%{idp.authn.Duo.supportedPrincipals}}'.trim()}" />
+                c:_0="#{'%{idp.authn.DuoOIDC.supportedPrincipals:%{idp.authn.Duo.supportedPrincipals:}}'.trim()}" />
         </property>
     </bean>
 
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
index e26a3fe..b2efad2 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
@@ -24,16 +24,25 @@
 
     <!-- Default Duo Integration -->
     <bean id="shibboleth.authn.DuoOIDC.DuoIntegration"
-        class="net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration" p:APIHost="%{idp.duo.oidc.apiHost:none}"
-        p:clientId="%{idp.duo.oidc.clientId:none}" p:secretKey="%{idp.duo.oidc.secretKey:none}"
+        class="net.shibboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration"
+        p:APIHost="%{idp.duo.oidc.apiHost:none}"
+        p:clientId="%{idp.duo.oidc.clientId:none}"
+        p:secretKey="%{idp.duo.oidc.secretKey:none}"
         p:redirectURI="%{idp.duo.oidc.redirectURL:none}" 
         p:healthCheckEndpoint="%{idp.duo.oidc.endpoint.health:/oauth/v1/health_check}"
         p:tokenEndpoint="%{idp.duo.oidc.endpoint.token:/oauth/v1/token}"
-        p:authorizeEndpoint="%{idp.duo.oidc.endpoint.authorize:/oauth/v1/authorize}"
-        />
+        p:authorizeEndpoint="%{idp.duo.oidc.endpoint.authorize:/oauth/v1/authorize}" />
     <bean id="shibboleth.authn.DuoOIDC.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant"
         c:target-ref="shibboleth.authn.DuoOIDC.DuoIntegration" />
 
+    <!-- Default "optional" non-browser integration. -->
+    <bean id="shibboleth.authn.DuoOIDC.NonBrowser.DuoIntegration" class="net.shibboleth.idp.authn.duo.BasicDuoIntegration"
+        p:APIHost="%{idp.duo.oidc.nonbrowser.apiHost:%{idp.duo.oidc.apiHost:none}}"
+        p:integrationKey="%{idp.duo.oidc.nonbrowser.integrationKey:none}"
+        p:secretKey="%{idp.duo.oidc.nonbrowser.secretKey:none}" />
+    <bean id="shibboleth.authn.DuoOIDC.NonBrowser.DuoIntegrationStrategy" parent="shibboleth.Functions.Constant"
+        c:target-ref="shibboleth.authn.DuoOIDC.NonBrowser.DuoIntegration" />
+
     <!-- Default username comes from previous c14n or session. -->
     <bean id="shibboleth.authn.DuoOIDC.UsernameLookupStrategy"
         class="net.shibboleth.idp.session.context.navigate.CanonicalUsernameLookupStrategy" />
@@ -51,13 +60,47 @@
     to change the location of the user config file. -->
     <import resource="conditional:%{idp.home}/conf/authn/%{idp.duo.oidc.user.config:duo-oidc-authn-config.xml}" />
 
+    <!--
+    Non-Browser actions and beans. Code lives in idp-authn-impl for now, may migrate here later.
+    -->
+    <bean id="ExtractDuoAuthenticationFromHeaders" scope="prototype"
+        class="net.shibboleth.idp.authn.duo.impl.ExtractDuoAuthenticationFromHeaders"
+        p:httpServletRequest-ref="shibboleth.HttpServletRequest"
+        p:autoAuthenticationSupported="%{idp.duo.oidc.nonbrowser.auto:true}"
+        p:clientAdddressTrusted="%{idp.duo.oidc.nonbrowser.clientAddressTrusted:true}"
+        p:factorHeader="%{idp.duo.oidc.nonbrowser.header.factor:X-Shibboleth-Duo-Factor}"
+        p:deviceHeader="%{idp.duo.oidc.nonbrowser.header.device:X-Shibboleth-Duo-Device}"
+        p:passcodeHeader="%{idp.duo.oidc.nonbrowser.header.passcode:X-Shibboleth-Duo-Passcode}"
+        p:pushInfoLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.PushInfoLookupStrategy')}" />
+
+    <bean id="DuoPreauthAuthenticator" lazy-init="true"
+        class="net.shibboleth.idp.authn.duo.impl.DuoPreauthAuthenticator"
+        p:objectMapper-ref="shibboleth.JSONObjectMapper"
+        p:httpClient="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+        p:httpClientSecurityParameters="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClientSecurityParameters')}" />
+
+    <bean id="DuoAuthAuthenticator" lazy-init="true"
+        class="net.shibboleth.idp.authn.duo.impl.DuoAuthAuthenticator"
+        p:objectMapper-ref="shibboleth.JSONObjectMapper"
+        p:httpClient="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
+        p:httpClientSecurityParameters="#{getObject('shibboleth.authn.DuoOIDC.NonBrowser.HttpClientSecurityParameters')}" />
+
+    <bean id="ValidateDuoAuthAPI" scope="prototype"
+        class="net.shibboleth.idp.authn.duo.impl.ValidateDuoAuthAPI"
+        p:usernameLookupStrategy-ref="shibboleth.authn.DuoOIDC.UsernameLookupStrategy"
+        p:duoIntegrationLookupStrategy-ref="shibboleth.authn.DuoOIDC.NonBrowser.DuoIntegrationStrategy"
+        p:addDefaultPrincipals="%{idp.authn.DuoOIDC.addDefaultPrincipals:true}"
+        p:preauthAuthenticator-ref="DuoPreauthAuthenticator"
+        p:authAuthenticator-ref="DuoAuthAuthenticator"
+        p:classifiedMessages="#{getObject('shibboleth.authn.DuoOIDC.ClassifiedMessageMap')}"
+        p:resultCachingPredicate="#{getObject('shibboleth.authn.DuoOIDC.resultCachingPredicate')}" />
+
     <!-- Prototype per conversation beans -->
     <bean id="PopulateDuoAuthenticationContext" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.PopulateDuoAuthenticationContext"
         p:duoIntegrationLookupStrategy-ref="shibboleth.authn.DuoOIDC.DuoIntegrationStrategy"
         p:usernameLookupStrategy-ref="shibboleth.authn.DuoOIDC.UsernameLookupStrategy"
         p:clientRegistry-ref="shibboleth.authn.DuoOIDC.clientRegistry" />
-       
 
     <bean id="HealthCheckDuoOIDCAuthAPI" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.HealthCheckDuoOIDCAuthAPI" />
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml
index 5d4805f..9b95f96 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-flow.xml
@@ -4,6 +4,25 @@
 
     <!-- This is a login flow for the Duo OIDC authentication flow -->
 
+    <action-state id="ExtractDuoAuthenticationFromHeaders">
+        <evaluate expression="ExtractDuoAuthenticationFromHeaders" />
+        <evaluate expression="'proceed'" />
+        
+        <transition on="proceed" to="ValidateDuoAuthAPI" />
+        
+        <!-- Fall through to a different flow if header extract fails on a passive or non-browser request. -->
+        <transition on="#{ opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).isPassive() || !opensamlProfileRequestContext.isBrowserProfile() }" to="ReselectFlow" />
+        
+        <transition on="NoCredentials" to="CheckDuoOIDCAuthAPI" />
+    </action-state>
+
+    <action-state id="ValidateDuoAuthAPI">
+        <evaluate expression="ValidateDuoAuthAPI" />
+        <evaluate expression="'proceed'" />
+    
+        <transition on="proceed" to="proceed" />
+    </action-state>
+
     <!-- TODO: throws an AuthnException if the endpoint is not healthy, no backoff etc. -->
     <action-state id="CheckDuoOIDCAuthAPI">
         <evaluate expression="PopulateDuoAuthenticationContext" />
@@ -12,7 +31,6 @@
         <transition on="proceed" to="Duo2FAAuthorizationRequest" />
     </action-state>
     
-
     <view-state id="Duo2FAAuthorizationRequest"
         view="externalRedirect:#{T(net.shibboleth.idp.authn.ExternalAuthentication).getExternalRedirect(flowRequestContext.getActiveFlow().getApplicationContext().getBean('shibboleth.authn.DuoOIDC.externalAuthnPathStrategy').apply(opensamlProfileRequestContext) + '/authorize', flowExecutionContext.getKey().toString())}">
         <attribute name="csrf_excluded" value="true" type="boolean" />
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
index 0de0f8e..531c37b 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
@@ -414,6 +414,7 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
         sc.setIdPSession(session);
         prc.addSubcontext(sc);
         
+        prc.setBrowserProfile(true);
         return prc;
     }
   
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
index 47823f6..30d7035 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
@@ -125,10 +125,9 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false,true));
         updateFlowExecution(flowExecution);
         flowExecution.start(inputMap, externalContext);    
-        //TODO: only check it has ended, should check correct state - but it does now allow this
+        //TODO: only check it has ended, should check correct state - but it does not allow this
         //find a way to check last state of an ended flow.
         assertFlowExecutionEnded();
-
     }
     
     /** Test the Duo flow up to the external authorization request.*/
diff --git a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties b/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties
index e3bae0a..ced668f 100644
--- a/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties
+++ b/idp-duo-nimbus-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/nimbus/conf/authn/duo-oidc.properties
@@ -1,13 +1,31 @@
 ## DuoOIDC default integration settings
 
+# Generic flow properties to adjust behavior
+#idp.authn.DuoOIDC.order = 1000
+#idp.authn.DuoOIDC.nonBrowserSupported = false
+#idp.authn.DuoOIDC.forcedAuthenticationSupported = true
+# The list below should be changed to reflect whatever locally- or
+# community-defined values are appropriate to represent Duo. It is
+# strongly advised that the value not be specific to Duo or any
+# particular technology to avoid lock-in.
+idp.authn.DuoOIDC.supportedPrincipals = \
+    saml2/http://example.org/ac/classes/mfa, \
+    saml1/http://example.org/ac/classes/mfa
+
+
 # Configure for your particular integration settings
 idp.duo.oidc.apiHost = hostname
 idp.duo.oidc.clientId = clientid
 idp.duo.oidc.redirectURL = https://<hostname>:<port>/idp/profile/Authn/Duo/2FA/duo-callback
-
 # We suggest defining this in credentials/secrets.properties
 #idp.duo.oidc.secretKey = key
 
+# Non-Browser AuthAPI integration if desired
+#idp.duo.oidc.nonbrowser.apiHost = %{idp.duo.oidc.apiHost}
+#idp.duo.oidc.nonbrowser.integrationKey = ikey
+# Suggest defining this in credentials/secrets.properties
+#idp.duo.oidc nonbrowser.secretKey = key
+
 # Shouldn't need changing
 #idp.duo.oidc.endpoint.health = /oauth/v1/health_check
 #idp.duo.oidc.endpoint.token = /oauth/v1/token
diff --git a/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties b/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties
index 04a6e41..2f85ccd 100644
--- a/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties
+++ b/idp-duo-sdk-client-impl/src/main/resources/net/shibboleth/idp/plugin/authn/duo/sdk/conf/authn/duo-oidc.properties
@@ -1,13 +1,31 @@
 ## DuoOIDC default integration settings
 
+# Generic flow properties to adjust behavior
+#idp.authn.DuoOIDC.order = 1000
+#idp.authn.DuoOIDC.nonBrowserSupported = false
+#idp.authn.DuoOIDC.forcedAuthenticationSupported = true
+# The list below should be changed to reflect whatever locally- or
+# community-defined values are appropriate to represent Duo. It is
+# strongly advised that the value not be specific to Duo or any
+# particular technology to avoid lock-in.
+idp.authn.DuoOIDC.supportedPrincipals = \
+    saml2/http://example.org/ac/classes/mfa, \
+    saml1/http://example.org/ac/classes/mfa
+
+
 # Configure for your particular integration settings
 idp.duo.oidc.apiHost = hostname
 idp.duo.oidc.clientId = clientid
 idp.duo.oidc.redirectURL = https://<hostname>:<port>/idp/profile/Authn/Duo/2FA/duo-callback
-
 # We suggest defining this in credentials/secrets.properties
 #idp.duo.oidc.secretKey = key
 
+# Non-Browser AuthAPI integration if desired
+#idp.duo.oidc.nonbrowser.apiHost = %{idp.duo.oidc.apiHost}
+#idp.duo.oidc.nonbrowser.integrationKey = ikey
+# Suggest defining this in credentials/secrets.properties
+#idp.duo.oidc nonbrowser.secretKey = key
+
 # Shouldn't need changing
 #idp.duo.oidc.endpoint.health = /oauth/v1/health_check
 #idp.duo.oidc.endpoint.token = /oauth/v1/token

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list