[java-identity-provider] branch master updated: IDP-1276 - Voluntary acr OIDC feature

Scott Cantor cantor.2 at osu.edu
Fri Aug 24 15:01:09 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=d66e515382c3d2e2637285a06cfe29674d76f376

The following commit(s) were added to refs/heads/master by this push:
       new  d66e515   IDP-1276 - Voluntary acr OIDC feature
d66e515 is described below

commit d66e515382c3d2e2637285a06cfe29674d76f376
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Fri Aug 24 15:01:03 2018 -0400

    IDP-1276 - Voluntary acr OIDC feature
    
    https://issues.shibboleth.net/jira/browse/IDP-1276
    
    Add PreferredPrincipalContext with impact on flow selection.
---
 .../authn/context/PreferredPrincipalContext.java   | 125 +++++++++++++++++++++
 .../idp/authn/impl/SelectAuthenticationFlow.java   |  37 +++++-
 .../authn/impl/SelectAuthenticationFlowTest.java   |  89 +++++++++++++++
 3 files changed, 245 insertions(+), 6 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java
new file mode 100644
index 0000000..9d7a26d
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/context/PreferredPrincipalContext.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID 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 net.shibboleth.idp.authn.context;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * A context that holds information about an authentication request's
+ * preference for a specific custom {@link Principal}.
+ * 
+ * <p>Authentication protocols with features for preferring specific forms of
+ * authentication with optional semantics will populate this context type with
+ * an expression of those preferences in the form of an ordered list of custom
+ * {@link Principal} objects.</p>
+ * 
+ * @parent {@link AuthenticationContext}
+ * @added Before the authentication process begins
+ * 
+ * @since 3.4.0
+ */
+public class PreferredPrincipalContext extends BaseContext {
+
+    /** The principals reflecting the preference. */
+    @Nonnull @NonnullElements private List<Principal> preferredPrincipals;
+    
+    /** Constructor. */
+    public PreferredPrincipalContext() {
+        preferredPrincipals = Collections.emptyList();
+    }
+
+    /**
+     * Get an immutable list of principals reflecting the request preferences.
+     * 
+     * @return  immutable list of principals 
+     */
+    @Nonnull @NonnullElements @Unmodifiable @NotLive public List<Principal> getPreferredPrincipals() {
+        return preferredPrincipals;
+    }
+    
+    /**
+     * Set list of principals reflecting the request preferences.
+     * 
+     * @param principals list of principals
+     * 
+     * @return this context
+     */
+    @Nonnull public PreferredPrincipalContext setPreferredPrincipals(
+            @Nonnull @NonnullElements final List<Principal> principals) {
+        Constraint.isNotNull(principals, "Principal list cannot be null");
+        
+        preferredPrincipals = ImmutableList.copyOf(Collections2.filter(principals, Predicates.notNull()));
+        return this;
+    }
+        
+    /**
+     * Helper method that evaluates a {@link PrincipalSupportingComponent} against
+     * this context to determine if the input is compatible with it.
+     * 
+     * @param component component to evaluate
+     * 
+     * @return true iff the input is compatible with the requested authentication preferences
+     */
+    public boolean isAcceptable(@Nonnull final PrincipalSupportingComponent component) {
+        
+        return !Collections.disjoint(preferredPrincipals, component.getSupportedPrincipals(Principal.class));
+    }
+
+    /**
+     * Helper method that evaluates {@link Principal} objects against this context
+     * to determine if the input is compatible with it.
+     * 
+     * @param principals principal(s) to evaluate
+     * 
+     * @return true iff the input is compatible with the requested authentication preferences
+     */
+    public boolean isAcceptable(@Nonnull @NonnullElements final Collection<Principal> principals) {
+        return !Collections.disjoint(preferredPrincipals, principals);
+    }
+
+    /**
+     * Helper method that evaluates a {@link Principal} object against this context
+     * to determine if the input is compatible with it.
+     * 
+     * @param <T> type of principal
+     * @param principal principal to evaluate
+     * 
+     * @return true iff the input is compatible with the requested authentication preferences
+     */
+    public <T extends Principal> boolean isAcceptable(@Nonnull final T principal) {
+        return preferredPrincipals.contains(principal);
+    }
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
index f3ae99d..60de1f0 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlow.java
@@ -29,6 +29,7 @@ import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.PreferredPrincipalContext;
 import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
 import net.shibboleth.idp.authn.principal.PrincipalEvalPredicate;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -84,7 +85,10 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
     
     /** A subordinate RequestedPrincipalContext, if any. */
     @Nullable private RequestedPrincipalContext requestedPrincipalCtx; 
-    
+
+    /** A subordinate PreferredPrincipalContext, if any. */
+    @Nullable private PreferredPrincipalContext preferredPrincipalCtx; 
+
     /**
      * Get whether SSO should trump explicit relying party requirements preference.
      * 
@@ -122,6 +126,11 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
             }
         }
         
+        preferredPrincipalCtx = authenticationContext.getSubcontext(PreferredPrincipalContext.class);
+        if (preferredPrincipalCtx != null && preferredPrincipalCtx.getPreferredPrincipals().isEmpty()) {
+            preferredPrincipalCtx = null;
+        }
+        
         // Detect a previous attempted flow, and move it to the intermediate collection.
         // This will prevent re-selecting the same (probably failed) flow again as part of
         // general flow selection. A flow might signal to explicitly re-run another flow anyway.
@@ -277,16 +286,26 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
             return;
         }
 
-        // Pick a result to reuse if possible.
+        // Pick a result to reuse if possible, honoring any preferences if necessary.
+        
+        AuthenticationResult resultToSelect = null;
+        
         for (final AuthenticationResult activeResult : authenticationContext.getActiveResults().values()) {
             final AuthenticationFlowDescriptor flow = authenticationContext.getPotentialFlows().get(
                     activeResult.getAuthenticationFlowId());
             if (flow != null && flow.getReuseCondition().apply(profileRequestContext)) {
-                selectActiveResult(profileRequestContext, authenticationContext, activeResult);
-                return;
+                resultToSelect = activeResult;
+                if (preferredPrincipalCtx == null || preferredPrincipalCtx.isAcceptable(activeResult)) {
+                    break;
+                }
             }
         }
         
+        if (resultToSelect != null) {
+            selectActiveResult(profileRequestContext, authenticationContext, resultToSelect);
+            return;
+        }
+        
         log.debug("{} No usable active results available, selecting an inactive flow", getLogPrefix());
         final AuthenticationFlowDescriptor flow =
                 getUnattemptedInactiveFlow(profileRequestContext, authenticationContext);
@@ -311,17 +330,23 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
     @Nullable private AuthenticationFlowDescriptor getUnattemptedInactiveFlow(
             @Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
+        
+        AuthenticationFlowDescriptor selectedFlow = null;
+        
         for (final AuthenticationFlowDescriptor flow : authenticationContext.getPotentialFlows().values()) {
             if (!authenticationContext.getIntermediateFlows().containsKey(flow.getId())) {
                 if (!authenticationContext.isPassive() || flow.isPassiveAuthenticationSupported()) {
                     if (flow.apply(profileRequestContext)) {
-                        return flow;
+                        selectedFlow = flow;
+                        if (preferredPrincipalCtx == null || preferredPrincipalCtx.isAcceptable(flow)) {
+                            break;
+                        }
                     }
                 }
             }
         }
         
-        return null;
+        return selectedFlow;
     }
 
     /**
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java
index c27ef1c..1232ed4 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SelectAuthenticationFlowTest.java
@@ -26,6 +26,7 @@ import javax.security.auth.Subject;
 import net.shibboleth.idp.authn.AuthenticationResult;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.context.PreferredPrincipalContext;
 import net.shibboleth.idp.authn.context.RequestedPrincipalContext;
 import net.shibboleth.idp.authn.principal.TestPrincipal;
 import net.shibboleth.idp.authn.principal.impl.ExactPrincipalEvalPredicateFactory;
@@ -134,6 +135,94 @@ public class SelectAuthenticationFlowTest extends BaseAuthenticationContextTest
         ActionTestingSupport.assertEvent(event, AuthnEventIds.REQUEST_UNSUPPORTED);
     }
 
+    @Test public void testPreferredNoMatch() {
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+        final List<Principal> principals = Arrays.<Principal>asList(new TestPrincipal("test3"));
+        final PreferredPrincipalContext ppc = new PreferredPrincipalContext();
+        ppc.setPreferredPrincipals(principals);
+        authCtx.addSubcontext(ppc, true);
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertEvent(event, "test3");
+        
+        Assert.assertNull(authCtx.getAuthenticationResult());
+        Assert.assertEquals(authCtx.getAttemptedFlow().getId(), "test3");
+    }
+
+    @Test public void testPreferredNoneActive() {
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+        final List<Principal> principals = Arrays.<Principal>asList(new TestPrincipal("test3"));
+        final PreferredPrincipalContext ppc = new PreferredPrincipalContext();
+        ppc.setPreferredPrincipals(principals);
+        authCtx.addSubcontext(ppc, true);
+        authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(principals);
+        
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertEvent(event, "test3");
+        
+        Assert.assertNull(authCtx.getAuthenticationResult());
+        Assert.assertEquals(authCtx.getAttemptedFlow().getId(), "test3");
+    }
+    
+    @Test public void testPreferredPickActiveInitialNonMatch() throws ComponentInitializationException {
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+        final List<Principal> principals = Arrays.<Principal>asList(new TestPrincipal("test3"),
+                new TestPrincipal("test2"));
+        final PreferredPrincipalContext ppc = new PreferredPrincipalContext();
+        ppc.setPreferredPrincipals(principals);
+        authCtx.addSubcontext(ppc, true);
+        final AuthenticationResult active = new AuthenticationResult("test1", new Subject());
+        active.getSubject().getPrincipals().add(new TestPrincipal("test1"));
+        authCtx.setActiveResults(Arrays.asList(active));
+        authCtx.setInitialAuthenticationResult(active);
+        authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(ImmutableList.of(principals.get(0)));
+        
+        action = new SelectAuthenticationFlow();
+        action.initialize();
+        final Event event = action.execute(src);
+        ActionTestingSupport.assertProceedEvent(event);
+        
+        Assert.assertEquals(active, authCtx.getAuthenticationResult());
+    }
+    
+    @Test public void testPreferredPickActiveNonMatch() {
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+        final List<Principal> principals = Arrays.<Principal>asList(new TestPrincipal("test3"),
+                new TestPrincipal("test2"));
+        final PreferredPrincipalContext ppc = new PreferredPrincipalContext();
+        ppc.setPreferredPrincipals(principals);
+        authCtx.addSubcontext(ppc, true);
+        final AuthenticationResult active = new AuthenticationResult("test1", new Subject());
+        active.getSubject().getPrincipals().add(new TestPrincipal("test1"));
+        authCtx.setActiveResults(Arrays.asList(active));
+        authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(ImmutableList.of(principals.get(0)));
+        
+        final Event event = action.execute(src);
+        
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(active, authCtx.getAuthenticationResult());
+    }
+
+    @Test public void testPreferredPickActiveMatch() {
+        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
+        final List<Principal> principals = Arrays.<Principal>asList(new TestPrincipal("test3"),
+                new TestPrincipal("test2"));
+        final PreferredPrincipalContext ppc = new PreferredPrincipalContext();
+        ppc.setPreferredPrincipals(principals);
+        authCtx.addSubcontext(ppc, true);
+        final AuthenticationResult active1 = new AuthenticationResult("test1", new Subject());
+        final AuthenticationResult active3 = new AuthenticationResult("test3", new Subject());
+        active1.getSubject().getPrincipals().add(new TestPrincipal("test1"));
+        active3.getSubject().getPrincipals().add(new TestPrincipal("test3"));
+        authCtx.setActiveResults(Arrays.asList(active1, active3));
+        authCtx.getPotentialFlows().get("test3").setSupportedPrincipals(ImmutableList.of(principals.get(0)));
+        
+        final Event event = action.execute(src);
+        
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(active3, authCtx.getAuthenticationResult());
+    }
+
     @Test public void testRequestNoneActive() {
         final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
         final List<Principal> principals = Arrays.<Principal>asList(new TestPrincipal("test3"));

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


More information about the commits mailing list