[java-identity-provider] branch master updated: IDP-1277 - Maximum Authentication Age

Scott Cantor cantor.2 at osu.edu
Wed Aug 29 10:59:17 EDT 2018


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

scantor pushed a commit to branch master
in repository java-identity-provider.

View the commit online:
http://git.shibboleth.net/view/?p=java-identity-provider.git;a=commit;h=e2831e533c703534466f031a9355934b6a50d7e9

The following commit(s) were added to refs/heads/master by this push:
       new  e2831e5   IDP-1277 - Maximum Authentication Age
e2831e5 is described below

commit e2831e533c703534466f031a9355934b6a50d7e9
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Aug 29 10:59:15 2018 -0400

    IDP-1277 - Maximum Authentication Age
    
    https://issues.shibboleth.net/jira/browse/IDP-1277
---
 .../idp/authn/context/AuthenticationContext.java   | 36 ++++++++++++++++++++++
 .../idp/authn/impl/FilterFlowsByForcedAuthn.java   |  5 +--
 .../PopulateMultiFactorAuthenticationContext.java  |  9 +++++-
 .../authn/impl/FilterFlowsByForceAuthnTest.java    | 10 +++---
 ...pulateMultiFactorAuthenticationContextTest.java | 26 +++++++++++++++-
 .../impl/ExtractActiveAuthenticationResults.java   | 11 ++++++-
 6 files changed, 88 insertions(+), 9 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
index cffda7e..8a5e3f6 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/AuthenticationContext.java
@@ -30,6 +30,7 @@ import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.principal.PrincipalEvalPredicateFactoryRegistry;
 import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
+import net.shibboleth.utilities.java.support.annotation.Duration;
 import net.shibboleth.utilities.java.support.annotation.constraint.Live;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonNegative;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -69,6 +70,9 @@ public final class AuthenticationContext extends BaseContext {
     
     /** A non-normative hint some protocols support to indicate who the subject might be. */
     @Nullable private String hintedName;
+    
+    /** Allowed time in ms since an {@link AuthenticationResult} was created that it can be reused for this request. */
+    @NonNegative @Duration private long maxAge;
 
     /** Flows that are known to the system. */
     @Nonnull @NonnullElements private final Map<String,AuthenticationFlowDescriptor> availableFlows;
@@ -317,6 +321,37 @@ public final class AuthenticationContext extends BaseContext {
         hintedName = StringSupport.trimOrNull(hint);
         return this;
     }
+    
+    /**
+     * Get duration in milliseconds since an {@link AuthenticationResult} was created that
+     * allows it to be reused for this request.
+     * 
+     * <p>If zero, no constraint is applied.</p>
+     * 
+     * @return duration in milliseconds, or zero
+     * 
+     * @since 3.4.0
+     */
+    @NonNegative @Duration public long getMaxAge() {
+        return maxAge;
+    }
+    
+    /**
+     * Set duration in milliseconds since an {@link AuthenticationResult} was created that
+     * allows it to be reused for this request.
+     * 
+     * <p>Set to zero to apply no constraint.</p>
+     * 
+     * @param age duration in milliseconds, or zero
+     * 
+     * @return this context
+     * 
+     * @since 3.4.0
+     */
+    @Nonnull public AuthenticationContext setMaxAge(@NonNegative @Duration final long age) {
+        maxAge = Constraint.isGreaterThanOrEqual(0, age, "MaxAge cannot be negative");
+        return this;
+    }
 
     /**
      * Get the authentication flow that was attempted in order to authenticate the user.
@@ -542,6 +577,7 @@ public final class AuthenticationContext extends BaseContext {
                 .add("isPassive", isPassive)
                 .add("forceAuthn", forceAuthn)
                 .add("hintedName", hintedName)
+                .add("maxAge", maxAge)
                 .add("potentialFlows", potentialFlows.keySet())
                 .add("activeResults", activeResults.keySet())
                 .add("attemptedFlow", attemptedFlow)
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java
index 910fc9d..40609ad 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByForcedAuthn.java
@@ -33,7 +33,7 @@ import org.slf4j.LoggerFactory;
 
 /**
  * An authentication action that filters out potential authentication flows if the request requires
- * forced authentication behavior and the flows don't support forced authentication.
+ * forced authentication or max age behavior and the flows don't support forced authentication.
  * 
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
  * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
@@ -49,7 +49,7 @@ public class FilterFlowsByForcedAuthn extends AbstractAuthenticationAction {
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        if (!authenticationContext.isForceAuthn()) {
+        if (!authenticationContext.isForceAuthn() && authenticationContext.getMaxAge() == 0) {
             log.debug("{} Request does not have forced authentication requirement, nothing to do", getLogPrefix());
             return false;
         }
@@ -84,4 +84,5 @@ public class FilterFlowsByForcedAuthn extends AbstractAuthenticationAction {
             log.debug("{} Potential authentication flows left after filtering: {}", getLogPrefix(), potentialFlows);
         }
     }
+    
 }
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
index f037707..26aed1f 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContext.java
@@ -232,7 +232,14 @@ public class PopulateMultiFactorAuthenticationContext extends AbstractAuthentica
             if (descriptor != null) {
                 if (descriptor.apply(profileRequestContext)) {
                     if (descriptor.isResultActive(candidate)) {
-                        results.add(candidate);
+                        if (authenticationContext.getMaxAge() > 0
+                                && candidate.getAuthenticationInstant() + authenticationContext.getMaxAge()
+                                    < System.currentTimeMillis()) {
+                            log.debug("{} Ignoring active result from login flow {} due to maxAge on request",
+                                    getLogPrefix(), candidate.getAuthenticationFlowId());
+                        } else {
+                            results.add(candidate);
+                        }
                     } else {
                         log.debug("{} Result from login flow {} has expired", getLogPrefix(), descriptor.getId());
                     }
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java
index 308391c..4c09972 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByForceAuthnTest.java
@@ -39,8 +39,9 @@ public class FilterFlowsByForceAuthnTest extends BaseAuthenticationContextTest {
     }
     
     @Test public void testNonForced() throws Exception {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class, false);
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
         authCtx.setForceAuthn(false);
+        authCtx.setMaxAge(0);
         
         final Event event = action.execute(src);
         ActionTestingSupport.assertProceedEvent(event);
@@ -48,7 +49,7 @@ public class FilterFlowsByForceAuthnTest extends BaseAuthenticationContextTest {
     }
 
     @Test public void testNoFiltering() {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class, false);
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
         authCtx.setForceAuthn(true);
         for (final AuthenticationFlowDescriptor fd : authCtx.getPotentialFlows().values()) {
             fd.setForcedAuthenticationSupported(true);
@@ -60,8 +61,8 @@ public class FilterFlowsByForceAuthnTest extends BaseAuthenticationContextTest {
     }
 
     @Test public void testPartialFiltering() {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class, false);
-        authCtx.setForceAuthn(true);
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+        authCtx.setMaxAge(3600 * 1000);
         authCtx.getPotentialFlows().get("test2").setForcedAuthenticationSupported(true);
         
         final Event event = action.execute(src);
@@ -70,4 +71,5 @@ public class FilterFlowsByForceAuthnTest extends BaseAuthenticationContextTest {
         Assert.assertNull(authCtx.getPotentialFlows().get("test1"));
         Assert.assertNotNull(authCtx.getPotentialFlows().get("test2"));
     }
+    
 }
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
index b6f0a73..3f64bae 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/PopulateMultiFactorAuthenticationContextTest.java
@@ -96,6 +96,12 @@ public class PopulateMultiFactorAuthenticationContextTest {
         subject.getPrincipals().add(new AuthenticationResultPrincipal(result));
         result = new AuthenticationResult("baz", new Subject());
         subject.getPrincipals().add(new AuthenticationResultPrincipal(result));
+        result = new AuthenticationResult("bav", new Subject());
+        result.setAuthenticationInstant(System.currentTimeMillis() - 1000 * 1000);
+        subject.getPrincipals().add(new AuthenticationResultPrincipal(result));
+        result = new AuthenticationResult("bag", new Subject());
+        result.setAuthenticationInstant(System.currentTimeMillis() - 2000 * 1000);
+        subject.getPrincipals().add(new AuthenticationResultPrincipal(result));
         
         ac.getActiveResults().put("authn/MFA", new AuthenticationResult("authn/MFA", subject));
         
@@ -113,6 +119,22 @@ public class PopulateMultiFactorAuthenticationContextTest {
         desc.initialize();
         ac.getAvailableFlows().put(desc.getId(), desc);
 
+        desc = new AuthenticationFlowDescriptor();
+        desc.setId("bav");
+        desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
+        desc.setLifetime(3600 * 1000);
+        desc.initialize();
+        ac.getAvailableFlows().put(desc.getId(), desc);
+
+        desc = new AuthenticationFlowDescriptor();
+        desc.setId("bag");
+        desc.setResultSerializer(new DefaultAuthenticationResultSerializer());
+        desc.setLifetime(3600 * 1000);
+        desc.initialize();
+        ac.getAvailableFlows().put(desc.getId(), desc);
+
+        ac.setMaxAge(1800 * 1000);
+        
         action.setTransitionMapLookupStrategy(
                 FunctionSupport.<ProfileRequestContext,Map<String,MultiFactorAuthenticationTransition>>constant(
                         Collections.singletonMap("", new MultiFactorAuthenticationTransition())));
@@ -123,10 +145,12 @@ public class PopulateMultiFactorAuthenticationContextTest {
         final MultiFactorAuthenticationContext mfa = ac.getSubcontext(MultiFactorAuthenticationContext.class);
         Assert.assertNotNull(mfa);
         Assert.assertEquals(ac.getAttemptedFlow(), mfa.getAuthenticationFlowDescriptor());
-        Assert.assertEquals(mfa.getActiveResults().size(), 1);
+        Assert.assertEquals(mfa.getActiveResults().size(), 2);
         Assert.assertNull(mfa.getActiveResults().get("foo"));
         Assert.assertNotNull(mfa.getActiveResults().get("bar"));
         Assert.assertNull(mfa.getActiveResults().get("baz"));
+        Assert.assertNotNull(mfa.getActiveResults().get("bav"));
+        Assert.assertNull(mfa.getActiveResults().get("bag"));
     }
     
 }
\ No newline at end of file
diff --git a/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java b/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java
index 4c81071..77f1595 100644
--- a/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java
+++ b/idp-session-impl/src/main/java/net/shibboleth/idp/session/impl/ExtractActiveAuthenticationResults.java
@@ -89,7 +89,7 @@ public class ExtractActiveAuthenticationResults extends AbstractAuthenticationAc
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
-
+        
         final SessionContext ctx = sessionContextLookupStrategy.apply(profileRequestContext);
         if (ctx != null) {
             session = ctx.getIdPSession();
@@ -110,6 +110,9 @@ public class ExtractActiveAuthenticationResults extends AbstractAuthenticationAc
             authenticationContext.setHintedName(session.getPrincipalName());
         }
         
+        final long maxAge = authenticationContext.getMaxAge();
+        final long now = System.currentTimeMillis();
+        
         final List<AuthenticationResult> actives = new ArrayList<>();
         for (final AuthenticationResult result : session.getAuthenticationResults()) {
             final AuthenticationFlowDescriptor descriptor =
@@ -121,6 +124,12 @@ public class ExtractActiveAuthenticationResults extends AbstractAuthenticationAc
             }
             
             if (descriptor.isResultActive(result)) {
+                if (maxAge > 0 && result.getAuthenticationInstant() + maxAge < now) {
+                    log.debug("{} Authentication result {} exceeds maxAge setting, skiping it", getLogPrefix(),
+                            result.getAuthenticationFlowId());
+                    continue;
+                }
+                
                 log.debug("{} Authentication result {} is active, copying from session", getLogPrefix(),
                         result.getAuthenticationFlowId());
                 actives.add(result);

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


More information about the commits mailing list