[java-identity-provider] branch master updated: IDP-1494 - Login flow for proxied SAML authentication

Scott Cantor cantor.2 at osu.edu
Wed Nov 27 14:09:45 EST 2019


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=38345927349c829c13baf0501995bb0e066b0ab7

The following commit(s) were added to refs/heads/master by this push:
       new  3834592   IDP-1494 - Login flow for proxied SAML authentication
3834592 is described below

commit 38345927349c829c13baf0501995bb0e066b0ab7
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Nov 27 14:09:41 2019 -0500

    IDP-1494 - Login flow for proxied SAML authentication
    
    https://issues.shibboleth.net/jira/browse/IDP-1494
    
    Implement proxy count reuse and flow selection controls.
    Fix some warnings and remove some old classes.
---
 .../idp/authn/AuthenticationFlowDescriptor.java    |  69 +++++-
 .../net/shibboleth/idp/authn/AuthnEventIds.java    |   5 +-
 .../authn/MultiFactorAuthenticationTransition.java |   1 +
 .../idp/authn/impl/ExternalAuthenticationImpl.java |   1 +
 .../idp/authn/impl/FilterFlowsByAttribute.java     | 258 ---------------------
 .../idp/authn/impl/FilterFlowsByForcedAuthn.java   |   6 +-
 .../authn/impl/FilterFlowsByNonBrowserSupport.java |   6 +-
 .../idp/authn/impl/FilterFlowsByPassivity.java     |  87 -------
 .../idp/authn/impl/FinalizeAuthentication.java     |   1 +
 .../idp/authn/impl/SelectAuthenticationFlow.java   |  62 +++--
 .../impl/TransitionMultiFactorAuthentication.java  |   6 +
 .../shibboleth/idp/authn/impl/X509AuthServlet.java |   1 +
 .../idp/authn/impl/FilterFlowsByAttributeTest.java | 144 ------------
 .../idp/authn/impl/FilterFlowsByPassivityTest.java |  73 ------
 .../main/resources/conf/authn/general-authn.xml    |  24 +-
 idp-conf/src/main/resources/conf/errors.xml        |   1 +
 .../src/main/resources/system/conf/utilities.xml   |   5 +
 .../system/flows/authn/authn-abstract-flow.xml     |   2 +
 18 files changed, 152 insertions(+), 600 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
index 067b39a..8f45d20 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthenticationFlowDescriptor.java
@@ -33,6 +33,7 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.security.auth.Subject;
 
+import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.authn.principal.PrincipalSupportingComponent;
 import net.shibboleth.idp.profile.FlowDescriptor;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
@@ -78,9 +79,12 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     /** Whether this flow supports forced authentication. */
     private boolean supportsForced;
     
-    /** Whether this flow should honor proxy restrictions. */
+    /** Whether this flow should honor proxy restrictions toward RPs. */
     private boolean proxyRestrictionsEnforced;
-    
+
+    /** Whether this flow should honor proxy scoping restrictions toward IdPs. */
+    private boolean proxyScopingEnforced;
+
     /** Whether this flow allows reuse of its results. */
     @Nonnull private Predicate<ProfileRequestContext> reuseCondition;
 
@@ -109,7 +113,8 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     public AuthenticationFlowDescriptor() {
         supportsNonBrowser = true;
         proxyRestrictionsEnforced = true;
-        reuseCondition = Predicates.alwaysTrue();
+        proxyScopingEnforced = false;
+        reuseCondition = new ProxyCountPredicate();
         supportedPrincipals = new Subject();
         activationCondition = Predicates.alwaysTrue();
         inactivityTimeout = Duration.ofMinutes(30);
@@ -177,9 +182,9 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     }
     
     /**
-     * Gets whether this flow's results should honor restrictions on proxying.
+     * Gets whether this flow's results should honor restrictions on proxying toward RPs.
      * 
-     * @return true iff upstream proxying restrictions should be honored
+     * @return true iff proxying restrictions issued by IdPs should be honored
      * 
      * @since 4.0.0
      */
@@ -188,7 +193,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     }
     
     /**
-     * Sets whether this flow's results should honor restrictions on proxying.
+     * Sets whether this flow's results should honor restrictions on proxying toward RPs
      * 
      * <p>Defaults to true.</p>
      * 
@@ -199,8 +204,32 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     public void setProxyRestrictionsEnforced(final boolean flag) {
         proxyRestrictionsEnforced = flag;
     }
+
+    /**
+     * Gets whether this flow's results should honor restrictions on proxying toward IdPs.
+     * 
+     * @return true iff proxying restrictions issued by RPs should be honored
+     * 
+     * @since 4.0.0
+     */
+    public boolean isProxyScopingEnforced() {
+        return proxyScopingEnforced;
+    }
     
     /**
+     * Sets whether this flow's results should honor restrictions on proxying toward IdPs.
+     * 
+     * <p>Defaults to false. Should be enabled for flows that represent proxied authentication.</p>
+     * 
+     * @param flag flag to set
+     * 
+     * @since 4.0.0
+     */
+    public void setProxyScopingEnforced(final boolean flag) {
+        proxyScopingEnforced = flag;
+    }
+
+    /**
      * Set condition controlling whether results from this flow should be reused for SSO.
      * 
      * <p>Defaults to {@link Predicates#alwaysTrue()}.</p>
@@ -212,7 +241,9 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     public void setReuseCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
 
-        reuseCondition = Constraint.isNotNull(condition, "Predicate cannot be null");
+        // Auto-installs a guard against use of a proxied result if requester proxy count is zero.
+        reuseCondition = PredicateSupport.and(new ProxyCountPredicate(),
+                Constraint.isNotNull(condition, "Predicate cannot be null"));
     }
 
     /**
@@ -381,7 +412,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
      */
     @Nonnull public AuthenticationResult newAuthenticationResult(@Nonnull final Subject subject) {
         final AuthenticationResult result = new AuthenticationResult(getId(), subject);
-        
+
         if (proxyRestrictionsEnforced) {
             result.setReuseCondition(PredicateSupport.and(reuseCondition, result.new ProxyRestrictionReusePredicate()));
         } else {
@@ -427,6 +458,7 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
      * 
      * @since 4.0.0
      */
+    @SuppressWarnings("unchecked")
     @Nullable public <T extends Principal> T getHighestWeighted(
             @Nonnull @NonnullElements final Collection<T> principals) {
         if (principals.isEmpty()) {
@@ -478,7 +510,6 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
     private class WeightedComparator<T> implements Comparator<T> {
 
         /** {@inheritDoc} */
-        @Override
         public int compare(final T o1, final T o2) {
             
             final int weight1 = principalWeightMap.containsKey(o1) ? principalWeightMap.get(o1) : 0;
@@ -494,6 +525,26 @@ public class AuthenticationFlowDescriptor extends AbstractIdentifiableInitializa
         
     }
     
+    /**
+     * A {@link Predicate} that implements a cross-check between an effective proxy count of zero and
+     * whether a descriptor is honoring the limit.
+     */
+    private class ProxyCountPredicate implements Predicate<ProfileRequestContext> {
+
+        /** {@inheritDoc} */
+        public boolean test(@Nullable final ProfileRequestContext input) {
+            
+            if (proxyScopingEnforced) {
+                final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
+                if (authnCtx != null && authnCtx.getProxyCount() != null && authnCtx.getProxyCount() == 0) {
+                    return false;
+                }
+            }
+            
+            return true;
+        }
+    }
+    
     static {
         STORAGE_EXPIRATION_OFFSET = Duration.ofMinutes(10);
     }
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java
index c449094..ac0356b 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AuthnEventIds.java
@@ -80,7 +80,10 @@ public final class AuthnEventIds {
 
     /** ID of event returned if an authenticated subject doesn't match the identity in an existing session. */
     @Nonnull @NotEmpty public static final String IDENTITY_SWITCH = "IdentitySwitch";
-    
+
+    /** ID of event returned if proxying is disallowed. */
+    @Nonnull @NotEmpty public static final String PROXY_COUNT_EXCEEDED = "ProxyCountExceeded";
+
     /** ID of event returned if authentication throws an exception unrelated to credential validation. */
     @Nonnull @NotEmpty public static final String AUTHN_EXCEPTION = "AuthenticationException";
 
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java
index a861f22..6e5e78c 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/MultiFactorAuthenticationTransition.java
@@ -90,6 +90,7 @@ public class MultiFactorAuthenticationTransition {
      * 
      * @param map map of transition rules
      */
+    @SuppressWarnings("unchecked")
     public void setNextFlowStrategyMap(@Nonnull @NonnullElements final Map<String,Object> map) {
         Constraint.isNotNull(map, "Transition strategy map cannot be null");
         
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java
index 1b4c258..12e30d5 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/ExternalAuthenticationImpl.java
@@ -110,6 +110,7 @@ public class ExternalAuthenticationImpl extends ExternalAuthentication {
 
  // Checkstyle: CyclomaticComplexity|MethodLength OFF
     /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
     @Override
     protected void doFinish(@Nonnull final HttpServletRequest request, @Nonnull final HttpServletResponse response,
             @Nonnull final ProfileRequestContext profileRequestContext,
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByAttribute.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByAttribute.java
deleted file mode 100644
index 5d1f683..0000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByAttribute.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.impl;
-
-import java.security.Principal;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Objects;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
-import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.idp.attribute.context.AttributeContext;
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
-import net.shibboleth.idp.authn.AuthenticationResult;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.DeprecationSupport;
-import net.shibboleth.utilities.java.support.primitive.DeprecationSupport.ObjectType;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * An authentication action that filters out potential authentication flows by comparing an {@link IdPAttribute}'s
- * values to the custom principals supported by each flow.
- * 
- * <p>It optionally (and by default) filters out active {@link AuthenticationResult} objects from possible reuse
- * for SSO.</p>
- * 
- * <p>The type of principals is ignored, and only string-based values of an attribute are supported.</p>
- * 
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
- * @post AuthenticationContext.getPotentialFlows() and AuthenticationContext.getActiveResults() are modified as above.
- */
-public class FilterFlowsByAttribute extends AbstractAuthenticationAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(FilterFlowsByAttribute.class);
-
-    /** Lookup strategy for locating {@link AttributeContext}. */
-    @Nonnull private Function<ProfileRequestContext, AttributeContext> attributeContextLookupStrategy;
-
-    /** The attribute ID to look for. */
-    @Nullable private String attributeId;
-    
-    /** Whether to also filter active results to limit SSO. */
-    private boolean filterActiveResults;
-
-    /** The attribute to match against. */
-    @Nullable private IdPAttribute attribute;
-
-    /** Constructor. */
-    public FilterFlowsByAttribute() {
-        attributeContextLookupStrategy =
-                new ChildContextLookup<>(AttributeContext.class).compose(
-                        new ChildContextLookup<>(AuthenticationContext.class));
-        filterActiveResults = true;
-    }
-
-    /**
-     * Set the lookup strategy for the {@link AttributeContext}.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setAttributeContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, AttributeContext> strategy) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        attributeContextLookupStrategy =
-                Constraint.isNotNull(strategy, "AttributeContext lookup strategy cannot be null");
-    }
-
-    /**
-     * Set the attribute ID to look for.
-     * 
-     * @param id attribute ID to look for
-     */
-    public void setAttributeId(@Nullable final String id) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        attributeId = StringSupport.trimOrNull(id);
-    }
-    
-    /**
-     * Set whether to filter active results (those usable for SSO) as well as inactive flows.
-     * 
-     * <p>Defaults to true</p>
-     * 
-     * @param flag  flag to set
-     */
-    public void setFilterActiveResults(final boolean flag) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        filterActiveResults = flag;
-    }
-
-    /** {@inheritDoc} */
-    @Override protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        if (!super.doPreExecute(profileRequestContext, authenticationContext) || attributeId == null) {
-            return false;
-        }
-        
-        DeprecationSupport.warnOnce(ObjectType.ACTION, getClass().getSimpleName(), null, "the MFA login flow");
-
-        final AttributeContext attributeCtx = attributeContextLookupStrategy.apply(profileRequestContext);
-        if (attributeCtx == null) {
-            log.debug("{} Request does not contain an AttributeContext, nothing to do", getLogPrefix());
-            return false;
-        }
-
-        attribute = attributeCtx.getIdPAttributes().get(attributeId);
-        if (attribute == null || attribute.getValues().isEmpty()) {
-            log.debug("{} Attribute {} has no values, nothing to do", getLogPrefix(), attributeId);
-            return false;
-        }
-
-        return true;
-    }
-
-    /** {@inheritDoc} */
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        log.debug("{} Filtering inactive flows available for use", getLogPrefix());
-        
-        final Map<String,AuthenticationFlowDescriptor> potentialFlows = authenticationContext.getPotentialFlows();
-
-        final Iterator<Entry<String,AuthenticationFlowDescriptor>> descriptorItr = potentialFlows.entrySet().iterator();
-        while (descriptorItr.hasNext()) {
-            final AuthenticationFlowDescriptor descriptor = descriptorItr.next().getValue();
-            final String match = getMatch(descriptor);
-            if (match != null) {
-                log.debug("{} Retaining flow {}, matched custom Principal {}", getLogPrefix(), descriptor.getId(),
-                        match);
-            } else {
-                log.debug("{} Removing flow {}, Principals did not match any attribute values", getLogPrefix(),
-                        descriptor.getId());
-                descriptorItr.remove();
-            }
-        }
-
-        if (potentialFlows.size() == 0) {
-            log.info("{} No potential authentication flows remain after filtering", getLogPrefix());
-        } else {
-            log.debug("{} Potential authentication flows left after filtering: {}", getLogPrefix(), potentialFlows);
-        }
-        
-        if (filterActiveResults) {
-            log.debug("{} Filtering active results available for reuse", getLogPrefix());
-            
-            final Map<String,AuthenticationResult> activeResults = authenticationContext.getActiveResults();
-            
-            final Iterator<Entry<String,AuthenticationResult>> resultItr = activeResults.entrySet().iterator();
-            while (resultItr.hasNext()) {
-                final AuthenticationResult result = resultItr.next().getValue();
-                final String match = getMatch(result);
-                if (match != null) {
-                    log.debug("{} Retaining active result from flow {}, matched custom Principal {}", getLogPrefix(),
-                            result.getAuthenticationFlowId(), match);
-                } else {
-                    log.debug("{} Removing active result from flow {}, Principals did not match any attribute values",
-                            getLogPrefix(), result.getAuthenticationFlowId());
-                    resultItr.remove();
-                }
-            }
-    
-            if (activeResults.size() == 0) {
-                log.info("{} No active authentication results remain after filtering", getLogPrefix());
-            } else {
-                log.debug("{} Active authentication results left after filtering: {}", getLogPrefix(), activeResults);
-            }
-        }
-    }
-
-    /**
-     * Compare the flow's custom principal names to the string values of the attribute.
-     * 
-     * @param flow flow to examine
-     * 
-     * @return a match between the flow's principal names and the attribute's string values, or null
-     */
-    @Nullable private String getMatch(@Nonnull final AuthenticationFlowDescriptor flow) {
-
-        log.debug("{} Looking for match for flow {} against values for attribute {}", getLogPrefix(), flow.getId(),
-                attribute.getId());
-        for (final Principal p : flow.getSupportedPrincipals()) {
-            log.debug("{} Comparing principal {} against attribute values {}", getLogPrefix(), p.getName(),
-                    attribute.getValues());
-            for (final IdPAttributeValue val : attribute.getValues()) {
-                if (val instanceof StringAttributeValue) {
-                    final StringAttributeValue stringVal = (StringAttributeValue) val;
-                    if (Objects.equals(stringVal.getValue(), p.getName())) {
-                        return p.getName();
-                    }
-                }
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Compare the result's custom principal names to the string values of the attribute.
-     * 
-     * @param result result to examine
-     * 
-     * @return a match between the result's principal names and the attribute's string values, or null
-     */
-    @Nullable private String getMatch(@Nonnull final AuthenticationResult result) {
-
-        log.debug("{} Looking for match for active result of flow {} against values for attribute {}",
-                getLogPrefix(), result.getAuthenticationFlowId(), attribute.getId());
-        for (final Principal p : result.getSupportedPrincipals(Principal.class)) {
-            log.debug("{} Comparing principal {} against attribute values {}", getLogPrefix(), p.getName(),
-                    attribute.getValues());
-            for (final IdPAttributeValue val : attribute.getValues()) {
-                if (val instanceof StringAttributeValue) {
-                    final StringAttributeValue stringVal = (StringAttributeValue) val;
-                    if (val instanceof StringAttributeValue && Objects.equals(stringVal.getValue(), p.getName())) {
-                        return p.getName();
-                    }
-                }
-            }
-        }
-
-        return null;
-    }
-
-}
\ No newline at end of file
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 79cfe4d..d68caba 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
@@ -49,12 +49,16 @@ public class FilterFlowsByForcedAuthn extends AbstractAuthenticationAction {
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
+        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+            return false;
+        }
+
         if (!authenticationContext.isForceAuthn() && authenticationContext.getMaxAge() == null) {
             log.debug("{} Request does not have forced authentication requirement, nothing to do", getLogPrefix());
             return false;
         }
         
-        return super.doPreExecute(profileRequestContext, authenticationContext);
+        return true;
     }
     
     /** {@inheritDoc} */
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByNonBrowserSupport.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByNonBrowserSupport.java
index 69b4327..86311e5 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByNonBrowserSupport.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByNonBrowserSupport.java
@@ -49,12 +49,16 @@ public class FilterFlowsByNonBrowserSupport extends AbstractAuthenticationAction
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
+        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+            return false;
+        }
+
         if (profileRequestContext.isBrowserProfile()) {
             log.debug("{} Request does not have non-browser requirement, nothing to do", getLogPrefix());
             return false;
         }
         
-        return super.doPreExecute(profileRequestContext, authenticationContext);
+        return true;
     }
     
     /** {@inheritDoc} */
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByPassivity.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByPassivity.java
deleted file mode 100644
index d777b0a..0000000
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FilterFlowsByPassivity.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.impl;
-
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import javax.annotation.Nonnull;
-
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
-import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * An authentication action that filters out potential authentication flows if the request requires
- * passive behavior and the flows don't support passive authentication.
- * 
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
- * @post AuthenticationContext.getPotentialFlows() is modified as above.
- */
-public class FilterFlowsByPassivity extends AbstractAuthenticationAction {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(FilterFlowsByPassivity.class);
-
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-        
-        if (!authenticationContext.isPassive()) {
-            log.debug("{} Request does not have passive requirement, nothing to do", getLogPrefix());
-            return false;
-        }
-        
-        return super.doPreExecute(profileRequestContext, authenticationContext);
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
-
-        final Map<String, AuthenticationFlowDescriptor> potentialFlows = authenticationContext.getPotentialFlows();
-
-        final Iterator<Entry<String, AuthenticationFlowDescriptor>> descriptorItr =
-                potentialFlows.entrySet().iterator();
-        while (descriptorItr.hasNext()) {
-            final AuthenticationFlowDescriptor descriptor = descriptorItr.next().getValue();
-            if (descriptor.isPassiveAuthenticationSupported()) {
-                log.debug("{} Retaining flow {}, it supports passive authentication", getLogPrefix(),
-                        descriptor.getId());
-            } else {
-                log.debug("{} Removing flow {}, it does not support passive authentication", getLogPrefix(),
-                        descriptor.getId());
-                descriptorItr.remove();
-            }
-        }
-
-        if (potentialFlows.size() == 0) {
-            log.info("{} No potential authentication flows remain after filtering", getLogPrefix());
-        } else {
-            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/FinalizeAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FinalizeAuthentication.java
index 93bea52..7f96bb0 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FinalizeAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/FinalizeAuthentication.java
@@ -245,6 +245,7 @@ public class FinalizeAuthentication extends AbstractAuthenticationAction {
                 for (final Principal candidate
                         : authenticationContext.getAuthenticationResult().getSupportedPrincipals(p.getClass())) {
                     if (predicate.test(new PrincipalSupportingComponent() {
+                        @SuppressWarnings("unchecked")
                         public <T extends Principal> Set<T> getSupportedPrincipals(final Class<T> c) {
                             return Collections.<T>singleton((T) candidate);
                         }
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 db0a41b..c530a1a 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
@@ -86,7 +86,10 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
     @Nullable private RequestedPrincipalContext requestedPrincipalCtx; 
 
     /** A subordinate PreferredPrincipalContext, if any. */
-    @Nullable private PreferredPrincipalContext preferredPrincipalCtx; 
+    @Nullable private PreferredPrincipalContext preferredPrincipalCtx;
+    
+    /** Tracks a proxy count of zero for the request. */
+    private boolean noProxying;
 
     /**
      * Get whether SSO should trump explicit relying party requirements preference.
@@ -117,6 +120,8 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
             return false;
         }
         
+        noProxying = authenticationContext.getProxyCount() != null && authenticationContext.getProxyCount() == 0;
+        
         requestedPrincipalCtx = authenticationContext.getSubcontext(RequestedPrincipalContext.class);
         if (requestedPrincipalCtx != null) {
             if (requestedPrincipalCtx.getOperator() == null
@@ -184,8 +189,15 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
         
         log.debug("{} Attempting to honor signaled flow {}", getLogPrefix(), flow.getId());
 
-        // If not forced, check for an active result for that flow.
+        if (noProxying && flow.isProxyScopingEnforced()) {
+            log.error("{} Signaled flow {} disallowed due to proxy count of zero", getLogPrefix(), flow.getId());
+            ActionSupport.buildEvent(profileRequestContext,
+                    authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE : AuthnEventIds.PROXY_COUNT_EXCEEDED);
+            return;
+        }
 
+        // If not forced, check for an active result for that flow.
+        
         AuthenticationResult activeResult = null;
         if (!authenticationContext.isForceAuthn()) {
             activeResult = authenticationContext.getActiveResults().get(flow.getId());
@@ -268,7 +280,8 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
             if (flow == null) {
                 log.info("{} No potential flows left to choose from, authentication failed", getLogPrefix());
                 ActionSupport.buildEvent(profileRequestContext,
-                        authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE : AuthnEventIds.NO_POTENTIAL_FLOW);
+                        authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE :
+                            (noProxying ? AuthnEventIds.PROXY_COUNT_EXCEEDED : AuthnEventIds.NO_POTENTIAL_FLOW));
                 return;
             }
             selectInactiveFlow(profileRequestContext, authenticationContext, flow);
@@ -302,7 +315,8 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
         if (flow == null) {
             log.info("{} No potential flows left to choose from, authentication failed", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext,
-                    authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE : AuthnEventIds.NO_POTENTIAL_FLOW);
+                    authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE :
+                        (noProxying ? AuthnEventIds.PROXY_COUNT_EXCEEDED : AuthnEventIds.NO_POTENTIAL_FLOW));
             return;
         }
         selectInactiveFlow(profileRequestContext, authenticationContext, flow);
@@ -326,10 +340,12 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
         for (final AuthenticationFlowDescriptor flow : authenticationContext.getPotentialFlows().values()) {
             if (!authenticationContext.getIntermediateFlows().containsKey(flow.getId())) {
                 if (!authenticationContext.isPassive() || flow.isPassiveAuthenticationSupported()) {
-                    if (flow.test(profileRequestContext)) {
-                        selectedFlow = flow;
-                        if (preferredPrincipalCtx == null || preferredPrincipalCtx.isAcceptable(flow)) {
-                            break;
+                    if (!noProxying || !flow.isProxyScopingEnforced()) {
+                        if (flow.test(profileRequestContext)) {
+                            selectedFlow = flow;
+                            if (preferredPrincipalCtx == null || preferredPrincipalCtx.isAcceptable(flow)) {
+                                break;
+                            }
                         }
                     }
                 }
@@ -397,6 +413,7 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
         }
     }
 
+// Checkstyle: CyclomaticComplexity OFF
     /**
      * Selects an inactive flow in the presence of specific requested Principals, and completes processing.
      * 
@@ -411,7 +428,7 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
         // Check each flow for compatibility with request. Don't check for an active result also.
         // Also omit anything in the intermediates collection already.
         for (final Principal p : requestedPrincipalCtx.getRequestedPrincipals()) {
-            log.debug("{} Checking for an inactive flow compatible with operator '{}' and principal '{}'",
+            log.debug("{} Checking for inactive flow compatible with operator '{}' and principal '{}'",
                     getLogPrefix(), requestedPrincipalCtx.getOperator(), p.getName());
             final PrincipalEvalPredicate predicate = requestedPrincipalCtx.getPredicate(p);
             if (predicate != null) {
@@ -419,8 +436,12 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
                     if (!authenticationContext.getIntermediateFlows().containsKey(descriptor.getId())
                             && predicate.test(descriptor) && descriptor.test(profileRequestContext)) {
                         if (!authenticationContext.isPassive() || descriptor.isPassiveAuthenticationSupported()) {
-                            selectInactiveFlow(profileRequestContext, authenticationContext, descriptor);
-                            return;
+                            if (!noProxying || !descriptor.isProxyScopingEnforced()) {
+                                selectInactiveFlow(profileRequestContext, authenticationContext, descriptor);
+                                return;
+                            }
+                            log.debug("{} Flow '{}' disallowed by effective proxy count of zero", getLogPrefix(),
+                                    descriptor.getId());
                         }
                     }
                 }
@@ -431,12 +452,15 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
             }
         }
         
-        
         log.info("{} None of the potential authentication flows can satisfy the request", getLogPrefix());
         ActionSupport.buildEvent(profileRequestContext,
-                authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE : AuthnEventIds.REQUEST_UNSUPPORTED);
+                authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE :
+                    (noProxying ? AuthnEventIds.PROXY_COUNT_EXCEEDED : AuthnEventIds.REQUEST_UNSUPPORTED));
     }
+// Checkstyle: CyclomaticComplexity ON
     
+    
+// Checkstyle: MethodLength|CyclomaticComplexity|ReturnCount OFF
     /**
      * Selects a flow or an active result in the presence of specific requested Principals and completes processing.
      * 
@@ -444,7 +468,6 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
      * @param authenticationContext the current authentication context
      * @param activeResults active results that may be reused
      */
-// Checkstyle: MethodLength|CyclomaticComplexity|ReturnCount OFF
     private void selectRequestedFlow(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext,
             @Nonnull @NonnullElements final Map<String,AuthenticationResult> activeResults) {
@@ -504,8 +527,12 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
                                         result.getAuthenticationFlowId());
                             }
                             if (!authenticationContext.isPassive() || descriptor.isPassiveAuthenticationSupported()) {
-                                selectInactiveFlow(profileRequestContext, authenticationContext, descriptor);
-                                return;
+                                if (!noProxying || !descriptor.isProxyScopingEnforced()) {
+                                    selectInactiveFlow(profileRequestContext, authenticationContext, descriptor);
+                                    return;
+                                }
+                                log.debug("{} Flow '{}' disallowed by effective proxy count of zero", getLogPrefix(),
+                                        descriptor.getId());
                             }
                         } else {
                             selectActiveResult(profileRequestContext, authenticationContext, result);
@@ -522,7 +549,8 @@ public class SelectAuthenticationFlow extends AbstractAuthenticationAction {
         
         log.info("{} None of the potential authentication flows can satisfy the request", getLogPrefix());
         ActionSupport.buildEvent(profileRequestContext,
-                authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE : AuthnEventIds.REQUEST_UNSUPPORTED);
+                authenticationContext.isPassive() ? AuthnEventIds.NO_PASSIVE :
+                    (noProxying ? AuthnEventIds.PROXY_COUNT_EXCEEDED : AuthnEventIds.REQUEST_UNSUPPORTED));
     }
 // Checkstyle: MethodLength|CyclomaticComplexity|ReturnCount ON
         
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
index 53b2b6d..d09247f 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/TransitionMultiFactorAuthentication.java
@@ -302,6 +302,12 @@ public class TransitionMultiFactorAuthentication extends AbstractAuthenticationA
                 ActionSupport.buildEvent(profileRequestContext, authenticationContext.isPassive() ?
                         AuthnEventIds.NO_PASSIVE : AuthnEventIds.REQUEST_UNSUPPORTED);
                 return;
+            } else if (flow.isProxyScopingEnforced() &&
+                    authenticationContext.getProxyCount() != null && authenticationContext.getProxyCount() == 0) {
+                log.error("{} Targeted login flow '{}' cannot be used with an effective ProxyCount of zero",
+                        getLogPrefix(), flowId);
+                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.PROXY_COUNT_EXCEEDED);
+                return;
             }
         }
         
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
index 2143f67..26dab75 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/X509AuthServlet.java
@@ -77,6 +77,7 @@ public class X509AuthServlet extends HttpServlet {
     }
     
     /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
     @Override
     public void init(final ServletConfig config) throws ServletException {
         super.init(config);
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByAttributeTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByAttributeTest.java
deleted file mode 100644
index 523f4c0..0000000
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByAttributeTest.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.impl;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import javax.security.auth.Subject;
-
-import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.StringAttributeValue;
-import net.shibboleth.idp.attribute.context.AttributeContext;
-import net.shibboleth.idp.authn.AuthenticationResult;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.authn.principal.TestPrincipal;
-import net.shibboleth.idp.profile.ActionTestingSupport;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-
-import org.springframework.webflow.execution.Event;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-/** {@link FilterFlowsByAttribute} unit test. */
-public class FilterFlowsByAttributeTest extends BaseAuthenticationContextTest {
-    
-    private FilterFlowsByAttribute action; 
-    
-    @BeforeMethod public void setUp() throws Exception {
-        super.setUp();
-        
-        action = new FilterFlowsByAttribute();
-        action.setAttributeId("foo");
-        
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        final AuthenticationResult active = new AuthenticationResult("test3", new Subject());
-        active.getSubject().getPrincipals().add(new TestPrincipal("test3"));
-        authCtx.setActiveResults(Arrays.asList(active));
-    }
-
-    @Test public void testNoAttributeID() throws ComponentInitializationException {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        
-        action = new FilterFlowsByAttribute();
-        action.initialize();
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 3);
-        Assert.assertEquals(authCtx.getActiveResults().size(), 1);
-    }
-
-    @Test public void testNoAttribute() throws ComponentInitializationException {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        
-        action.initialize();
-        Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 3);
-        Assert.assertEquals(authCtx.getActiveResults().size(), 1);
-        
-        authCtx.getSubcontext(AttributeContext.class, true).setIdPAttributes(
-                Collections.singletonList(new IdPAttribute("foo")));
-        event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 3);
-        Assert.assertEquals(authCtx.getActiveResults().size(), 1);
-    }
-    
-    @Test public void testNoMatch() throws ComponentInitializationException {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        final IdPAttribute attr = new IdPAttribute("foo");
-        authCtx.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singletonList(attr));
-        attr.setValues(Collections.singletonList(new StringAttributeValue("bar")));
-        
-        action.setFilterActiveResults(false);
-        action.initialize();
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 0);
-        Assert.assertEquals(authCtx.getActiveResults().size(), 1);
-    }
-
-    @Test public void testMatch() throws ComponentInitializationException {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        final IdPAttribute attr = new IdPAttribute("foo");
-        authCtx.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singletonList(attr));
-        attr.setValues(Collections.singletonList(new StringAttributeValue("bar")));
-        
-        authCtx.getPotentialFlows().get("test1").getSupportedPrincipals().add(new TestPrincipal("baz"));
-        authCtx.getPotentialFlows().get("test2").getSupportedPrincipals().add(new TestPrincipal("bar"));
-        authCtx.getPotentialFlows().get("test3").getSupportedPrincipals().add(new TestPrincipal("bay"));
-        
-        action.setFilterActiveResults(false);
-        action.initialize();
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 1);
-        Assert.assertEquals(authCtx.getPotentialFlows().entrySet().iterator().next().getValue().getId(), "test2");
-        Assert.assertEquals(authCtx.getActiveResults().size(), 1);
-    }
-    
-    @Test public void testMatchActive() throws ComponentInitializationException {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        final IdPAttribute attr = new IdPAttribute("foo");
-        authCtx.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singletonList(attr));
-        attr.setValues(Collections.singletonList(new StringAttributeValue("test3")));
-        
-        action.initialize();
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 0);
-        Assert.assertEquals(authCtx.getActiveResults().size(), 1);
-    }
-
-    @Test public void testNoMatchActive() throws ComponentInitializationException {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        final IdPAttribute attr = new IdPAttribute("foo");
-        authCtx.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singletonList(attr));
-        attr.setValues(Collections.singletonList(new StringAttributeValue("test2")));
-        
-        action.initialize();
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 0);
-        Assert.assertEquals(authCtx.getActiveResults().size(), 0);
-    }
-    
-}
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByPassivityTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByPassivityTest.java
deleted file mode 100644
index 19abfae..0000000
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/FilterFlowsByPassivityTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.impl;
-
-import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.profile.ActionTestingSupport;
-
-import org.springframework.webflow.execution.Event;
-import org.testng.Assert;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-/** {@link FilterFlowsByPassivity} unit test. */
-public class FilterFlowsByPassivityTest extends BaseAuthenticationContextTest {
-    
-    private FilterFlowsByPassivity action; 
-    
-    @BeforeMethod public void setUp() throws Exception {
-        super.setUp();
-        
-        action = new FilterFlowsByPassivity();
-        action.initialize();
-    }
-    
-    @Test public void testNonPassive() {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        authCtx.setIsPassive(false);
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 3);
-    }
-
-    @Test public void testNoFiltering() {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        authCtx.setIsPassive(true);
-        for (final AuthenticationFlowDescriptor fd : authCtx.getPotentialFlows().values()) {
-            fd.setPassiveAuthenticationSupported(true);
-        }
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 3);
-    }
-
-    @Test public void testPartialFiltering() {
-        final AuthenticationContext authCtx = prc.getSubcontext(AuthenticationContext.class);
-        authCtx.setIsPassive(true);
-        authCtx.getPotentialFlows().get("test2").setPassiveAuthenticationSupported(true);
-        
-        final Event event = action.execute(src);
-        ActionTestingSupport.assertProceedEvent(event);
-        Assert.assertEquals(authCtx.getPotentialFlows().size(), 1);
-        Assert.assertNull(authCtx.getPotentialFlows().get("test1"));
-        Assert.assertNotNull(authCtx.getPotentialFlows().get("test2"));
-    }
-}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/conf/authn/general-authn.xml b/idp-conf/src/main/resources/conf/authn/general-authn.xml
index 906db6c..f3b660f 100644
--- a/idp-conf/src/main/resources/conf/authn/general-authn.xml
+++ b/idp-conf/src/main/resources/conf/authn/general-authn.xml
@@ -51,14 +51,6 @@
             </property>
         </bean>
         
-        <bean id="authn/External" parent="shibboleth.AuthenticationFlow"
-            p:nonBrowserSupported="false" />
-
-        <bean id="authn/RemoteUser" parent="shibboleth.AuthenticationFlow"
-            p:nonBrowserSupported="false" />
-
-        <bean id="authn/RemoteUserInternal" parent="shibboleth.AuthenticationFlow" />
-
         <bean id="authn/Function" parent="shibboleth.AuthenticationFlow" />
 
         <bean id="authn/X509" parent="shibboleth.AuthenticationFlow"
@@ -137,7 +129,21 @@
         <bean id="authn/Proxy" parent="shibboleth.AuthenticationFlow"
                 p:nonBrowserSupported="false"
                 p:passiveAuthenticationSupported="true"
-                p:forcedAuthenticationSupported="true" />
+                p:forcedAuthenticationSupported="true"
+                p:proxyScopingEnforced="true" />
+
+        <!--
+        These flows are often, though not exclusively, used to proxy authentication, so may need
+        the proxyScopingEnforced property enabled by hand to honor RP/local proxy count limits.
+        -->
+
+        <bean id="authn/External" parent="shibboleth.AuthenticationFlow"
+            p:nonBrowserSupported="false" />
+
+        <bean id="authn/RemoteUser" parent="shibboleth.AuthenticationFlow"
+            p:nonBrowserSupported="false" />
+
+        <bean id="authn/RemoteUserInternal" parent="shibboleth.AuthenticationFlow" />
 
     </util:list>
 
diff --git a/idp-conf/src/main/resources/conf/errors.xml b/idp-conf/src/main/resources/conf/errors.xml
index a2e8ca4..a5a8790 100644
--- a/idp-conf/src/main/resources/conf/errors.xml
+++ b/idp-conf/src/main/resources/conf/errors.xml
@@ -90,6 +90,7 @@
         <entry key="SessionNotFound" value-ref="shibboleth.SAML2Status.UnknownPrincipal" />
         
         <entry key="InvalidNameIDPolicy" value-ref="shibboleth.SAML2Status.InvalidNameIDPolicy" />
+        <entry key="ProxyCountExceeded" value-ref="shibboleth.SAML2Status.ProxyCountExceeded" />
         
         <entry key="ChannelBindingsError" value-ref="shibboleth.SAML2Status.ChannelBindingsError" />
     </util:map>
diff --git a/idp-conf/src/main/resources/system/conf/utilities.xml b/idp-conf/src/main/resources/system/conf/utilities.xml
index ba7afe0..532e842 100644
--- a/idp-conf/src/main/resources/system/conf/utilities.xml
+++ b/idp-conf/src/main/resources/system/conf/utilities.xml
@@ -331,6 +331,11 @@
         <util:constant static-field="org.opensaml.saml.saml2.core.StatusCode.INVALID_NAMEID_POLICY" />
     </util:list>
 
+    <util:list id="shibboleth.SAML2Status.ProxyCountExceeded">
+        <util:constant static-field="org.opensaml.saml.saml2.core.StatusCode.RESPONDER" />
+        <util:constant static-field="org.opensaml.saml.saml2.core.StatusCode.PROXY_COUNT_EXCEEDED" />
+    </util:list>
+
     <util:list id="shibboleth.SAML2Status.ChannelBindingsError">
         <util:constant static-field="org.opensaml.saml.saml2.core.StatusCode.REQUESTER" />
         <util:constant static-field="org.opensaml.saml.common.xml.SAMLConstants.SAML20CB_NS" />
diff --git a/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml b/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml
index ba54cbf..1531b04 100644
--- a/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml
+++ b/idp-conf/src/main/resources/system/flows/authn/authn-abstract-flow.xml
@@ -31,6 +31,7 @@
     <end-state id="NoCredentials" />
     <end-state id="NoPassive" />
     <end-state id="NoPotentialFlow" />
+    <end-state id="ProxyCountExceeded" />
     <end-state id="RequestUnsupported" />
     <end-state id="ReselectFlow" />
     <end-state id="RestartAuthentication" />
@@ -65,6 +66,7 @@
         <transition on="NoCredentials" to="NoCredentials" />
         <transition on="NoPassive" to="NoPassive" />
         <transition on="NoPotentialFlow" to="NoPotentialFlow" />
+        <transition on="ProxyCountExceeded" to="ProxyCountExceeded" />
         <transition on="RequestUnsupported" to="RequestUnsupported" />
         <transition on="ReselectFlow" to="ReselectFlow" />
         <transition on="RestartAuthentication" to="RestartAuthentication" />

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


More information about the commits mailing list