[java-idp-plugin-duo] branch master updated: Add test for forced authn with previous auth_time.

Phil Smart philip.smart at jisc.ac.uk
Mon Aug 10 10:18:43 UTC 2020


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

philsmart pushed a commit to branch master
in repository java-idp-plugin-duo.

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

The following commit(s) were added to refs/heads/master by this push:
       new  cd05365   Add test for forced authn with previous auth_time.
cd05365 is described below

commit cd053659831f4482bde55722d75a9f89ba044a49
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Aug 10 11:18:37 2020 +0100

    Add test for forced authn with previous auth_time.
---
 .../impl/ValidateDuoTokenAuthenticationTime.java   |   2 +-
 .../authn/duo/impl/ValidateDuoTokenSubject.java    |   3 +-
 .../plugin/authn/duo/impl/DuoAuthnFlowTest.java    | 143 +++++++++++++++++++++
 .../plugin/authn/mock/MockDuoOIDCClient_OK.java    |   3 +-
 ...ava => MockDuoOIDCClient_OK_OLD_AUTH_TIME.java} |   7 +-
 .../authn/mock/MockDuoOIDCClient_UNKNOWN.java      |   3 +-
 6 files changed, 154 insertions(+), 7 deletions(-)

diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTime.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTime.java
index ff62ef1..7be6089 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTime.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenAuthenticationTime.java
@@ -151,7 +151,7 @@ public class ValidateDuoTokenAuthenticationTime extends AbstractDuoAuthenticatio
         // Check time of authentication has not expired
         if (expiration.isBefore(now)) {
             log.warn(
-                    "{} Authentication has expired: auth_time was '{}', expired at: '{}', current time: '{}'",
+                    "{} Authentication required (forced) but has expired: auth_time was '{}', expired at: '{}', current time: '{}'",
                     getLogPrefix(), authTime, expiration, now);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.AUTHN_EXCEPTION);
         }
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubject.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubject.java
index 1fd0cd5..b3648a6 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubject.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateDuoTokenSubject.java
@@ -33,8 +33,7 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
 
 /**
  * An action that verifies the Subject (sub) claim in the Duo token matches that user which
- * is currently authenticating. This validation is suggested by the Duo Modern MFA developer
- * documentation.
+ * is currently authenticating.
  * 
  * @pre <pre>
  *      ProfileRequestContext.getSubcontext(AuthenticationContext.class, false) != null
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 9dd7584..d116228 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
@@ -18,12 +18,14 @@
 package net.shibboleth.idp.plugin.authn.duo.impl;
 
 
+import java.security.Principal;
 import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nonnull;
 
 import org.junit.Test;
+import org.opensaml.profile.context.EventContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -35,13 +37,18 @@ import org.springframework.webflow.execution.FlowExecution;
 import net.shbboleth.idp.plugin.authn.duo.DefaultDuoOIDCIntegration;
 import net.shbboleth.idp.plugin.authn.duo.DuoClientException;
 import net.shbboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
+import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClientFactory_FAIL_Client;
 import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClientFactory_OK_Client;
 import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClient_OK;
+import net.shibboleth.idp.plugin.authn.mock.MockDuoOIDCClient_OK_OLD_AUTH_TIME;
 import net.shibboleth.idp.plugin.authn.mock.MockFlowBuilder;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.idp.saml.authn.principal.AuthnContextClassRefPrincipal;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 
@@ -330,7 +337,143 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
         
     }
+    
+    /** Test the Duo flow from the external authorization request to the end of the flow with a requested
+     * principal set.
+     * 
+     * @throws DuoClientException if the client can not be created.*/
+    @Test 
+    public void testDuoAuthnFlowFromAuthorizationCallbackWithRPC() throws DuoClientException {
+        
+        setFlowPath(FLOW);
+        setFlowModelResources(flowResources);
+        setSubflows(subflows);
+        setClientFactory(new MockDuoOIDCClientFactory_OK_Client());
+        
+        
+        final Map<String,String> mockProperties = Map.of(
+                "idp.duo.oidc.config.user","classpath:/conf/authn/duo-oidc-authn-config.xml");
+        
+        setMockProperties(mockProperties);
 
+        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        final ProfileRequestContext prc =  buildProfileRequestContext(false,false);
+        //add a DuoContext
+        final DuoOIDCAuthenticationContext duoContext = new DuoOIDCAuthenticationContext();
+        final String nonce = DuoSupport.generateNonce(32);
+        duoContext.setAuthorizationCode("adummycode");
+        duoContext.setRequestState(nonce);
+        duoContext.setResponseState(nonce);
+        duoContext.setUsername("jdoe");
+        
+        final DefaultDuoOIDCIntegration integ = new DefaultDuoOIDCIntegration();
+        integ.setClientId("DIU6GEFWG5LIUBVV2M3P");
+        integ.setAPIHost("api-c9f24c5a.duosecurity.com");
+        integ.setSecretKey("rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh");
+        integ.setRedirectURI("http://localhost/authorization-callback");
+        
+        duoContext.setIntegration(integ);
+        
+        //add the mock client as was not added by the populate stage
+        duoContext.setClient(new MockDuoOIDCClient_OK(duoContext.getIntegration()));
+        
+        prc.getSubcontext(AuthenticationContext.class).addSubcontext(duoContext);
+        
+        //add a requested principal context.
+        RequestedPrincipalContext rpc = new RequestedPrincipalContext();
+        
+        List<Principal> requestedPrincipals = List.of(new AuthnContextClassRefPrincipal("http://example.org/ac/classes/mfa"));
+        rpc.setRequestedPrincipals(requestedPrincipals);
+        rpc.setOperator("exact");
+        prc.getSubcontext(AuthenticationContext.class).addSubcontext(rpc);
+        
+        //override the authflowdescriptor
+        AuthenticationFlowDescriptor afd = new AuthenticationFlowDescriptor();
+        afd.setId("authn/DuoOIDC");
+        afd.setSupportedPrincipals(List.of(new AuthnContextClassRefPrincipal("http://example.org/ac/classes/mfa")));
+        prc.getSubcontext(AuthenticationContext.class).setAttemptedFlow(afd);
+        
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("Duo2FAAuthorizationRequest");       
+        resumeFlow(externalContext);
+        
+        //assert success conditions
+        assertFlowExecutionEnded();
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class));
+        assertNotNull(prc.getSubcontext(AuthenticationContext.class).
+                getSubcontext(DuoOIDCAuthenticationContext.class));
+        final DuoOIDCAuthenticationContext contextFromPrc = prc.getSubcontext(AuthenticationContext.class).
+                getSubcontext(DuoOIDCAuthenticationContext.class);
+        assertNotNull(contextFromPrc.getAuthToken());
+        assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class));
+        assertEquals(prc.getSubcontext(SubjectCanonicalizationContext.class).getPrincipalName(),"jdoe");
+        
+    }
+
+    /** 
+     * Test the Duo flow from the external authorization request. This should fail, as forced authn is
+     * requested by the auth_time is from a previous authentication (to far in the past).
+     * 
+     * @throws DuoClientException if the client can not be created.*/
+    @Test 
+    public void testDuoAuthnFlowFromAuthorizationCallbackForceAuthnFailure() throws DuoClientException {
+        
+        setFlowPath(FLOW);
+        setFlowModelResources(flowResources);
+        setSubflows(subflows);
+        setClientFactory(new MockDuoOIDCClientFactory_OK_Client());
+        
+        
+        final Map<String,String> mockProperties = Map.of(
+                "idp.duo.oidc.config.user","classpath:/conf/authn/duo-oidc-authn-config.xml");
+        
+        setMockProperties(mockProperties);
+
+        final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
+                .createFlowExecution(getFlowDefinition());
+        //true for forced authn
+        final ProfileRequestContext prc =  buildProfileRequestContext(true,false);
+        //add a DuoContext
+        final DuoOIDCAuthenticationContext duoContext = new DuoOIDCAuthenticationContext();
+        final String nonce = DuoSupport.generateNonce(32);
+        duoContext.setAuthorizationCode("adummycode");
+        duoContext.setRequestState(nonce);
+        duoContext.setResponseState(nonce);
+        duoContext.setUsername("jdoe");
+        
+        final DefaultDuoOIDCIntegration integ = new DefaultDuoOIDCIntegration();
+        integ.setClientId("DIU6GEFWG5LIUBVV2M3P");
+        integ.setAPIHost("api-c9f24c5a.duosecurity.com");
+        integ.setSecretKey("rFvDfPul27v3Wew2zb6xRPzAJewJ34MP2w8UitPh");
+        integ.setRedirectURI("http://localhost/authorization-callback");
+        
+        duoContext.setIntegration(integ);
+        
+        //add the mock client as was not added by the populate stage
+        duoContext.setClient(new MockDuoOIDCClient_OK_OLD_AUTH_TIME(duoContext.getIntegration()));
+        
+        prc.getSubcontext(AuthenticationContext.class).addSubcontext(duoContext);
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext",prc);
+        updateFlowExecution(flowExecution);
+        
+        //set start view and ending event to transition on.
+        externalContext.setEventId("proceed");
+        setCurrentState("Duo2FAAuthorizationRequest");       
+        resumeFlow(externalContext);
+        
+        //assert success conditions
+        assertFlowExecutionEnded();        
+        assertNotNull(prc.getSubcontext(EventContext.class));
+        assertNotNull(prc.getSubcontext(EventContext.class).getEvent());
+        assertTrue(prc.getSubcontext(EventContext.class).getEvent() instanceof String);
+        assertEquals(AuthnEventIds.AUTHN_EXCEPTION, prc.getSubcontext(EventContext.class).getEvent());
+        
+    }
    
 }
 
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
index c78efc2..b9595ae 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
@@ -97,7 +97,8 @@ public class MockDuoOIDCClient_OK implements DuoOIDCClient{
                         withFactor("duo_push")
                         .withUsername("jdoe")
                         .withUserKey("XXXXXXX")
-                        .build())
+                        .build()).
+                withAuthTime(Math.toIntExact(Instant.now().getEpochSecond()))
                 .build();
     }
 
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK_OLD_AUTH_TIME.java
similarity index 90%
copy from idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
copy to idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK_OLD_AUTH_TIME.java
index c78efc2..f51e310 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_OK_OLD_AUTH_TIME.java
@@ -37,8 +37,10 @@ import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterI
 
 /**
  * Mock a Duo client which is available and returns a valid response.
+ * The token has an *old* auth_time to test a situation where a previous authentication
+ * result was used - which would prevent a forced authn from succeeding. 
  */
-public class MockDuoOIDCClient_OK implements DuoOIDCClient{    
+public class MockDuoOIDCClient_OK_OLD_AUTH_TIME implements DuoOIDCClient{    
     
     @Nonnull private static final String STATUS = "OK";
     
@@ -47,7 +49,7 @@ public class MockDuoOIDCClient_OK implements DuoOIDCClient{
     
     @NonnullAfterInit private DuoOIDCIntegration integration;
     
-    public MockDuoOIDCClient_OK(final @Nonnull DuoOIDCIntegration integ) throws DuoClientException {       
+    public MockDuoOIDCClient_OK_OLD_AUTH_TIME(final @Nonnull DuoOIDCIntegration integ) throws DuoClientException {       
         integration = integ;        
     }
 
@@ -98,6 +100,7 @@ public class MockDuoOIDCClient_OK implements DuoOIDCClient{
                         .withUsername("jdoe")
                         .withUserKey("XXXXXXX")
                         .build())
+                .withAuthTime(Math.toIntExact(Instant.now().minus(10,ChronoUnit.MINUTES).getEpochSecond()))
                 .build();
     }
 
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_UNKNOWN.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_UNKNOWN.java
index 445a68a..917b7ee 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_UNKNOWN.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/mock/MockDuoOIDCClient_UNKNOWN.java
@@ -93,7 +93,8 @@ public class MockDuoOIDCClient_UNKNOWN implements DuoOIDCClient{
                         withApplication(DuoApplication.builder()
                                 .withKey("DIU6GEFXXXXXXX")
                                 .withName("Test").build()).
-                        withFactor("duo_push").withUsername("jdoe").withUserKey("XXXXXXX").build()).build();
+                        withFactor("duo_push").withUsername("jdoe").withUserKey("XXXXXXX").build()).
+                withAuthTime(Math.toIntExact(Instant.now().getEpochSecond())).build();
     }
 
 }
\ No newline at end of file

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


More information about the commits mailing list