[java-identity-provider] branch main updated: IDP-2353 - C14n flow that runs a deployer-defined bean

Scott Cantor cantor.2 at osu.edu
Tue Feb 25 20:51:39 UTC 2025


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

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

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

The following commit(s) were added to refs/heads/main by this push:
     new 0c0581726 IDP-2353 - C14n flow that runs a deployer-defined bean
0c0581726 is described below

commit 0c0581726d269851795d3c32ec6bbaa6096fef04
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Feb 25 15:51:32 2025 -0500

    IDP-2353 - C14n flow that runs a deployer-defined bean
    
    https://shibboleth.atlassian.net/browse/IDP-2353
    
    Revamp c14n to support direct bean execution in place of subflows.
    Reimplement "c14n/simple" as a bean and adjust wiring for compatibility.
---
 .../idp/authn/AbstractSubjectCanonicalizer.java    | 209 +++++++++++++++++++++
 .../SubjectCanonicalizationFlowDescriptor.java     |  36 +++-
 .../shibboleth/idp/authn/SubjectCanonicalizer.java |  38 ++++
 .../impl/SelectSubjectCanonicalizationFlow.java    |  41 +++-
 .../authn/impl/SimpleSubjectCanonicalization.java  | 104 ++++------
 .../impl/SimpleSubjectCanonicalizationTest.java    |  37 ++--
 .../shibboleth/idp/conf/subject-c14n-system.xml    |  10 +
 .../net/shibboleth/idp/conf/webflow-config.xml     |   1 -
 .../idp/flows/c14n/simple-subject-c14n-beans.xml   |  24 ---
 .../idp/flows/c14n/simple-subject-c14n-flow.xml    |  17 --
 .../idp/flows/c14n/subject-c14n-flow.xml           |   6 +
 11 files changed, 381 insertions(+), 142 deletions(-)

diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractSubjectCanonicalizer.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractSubjectCanonicalizer.java
new file mode 100644
index 000000000..0e1e36f15
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/AbstractSubjectCanonicalizer.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed 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;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * A base class for "stand-alone" subject c14n implementations.
+ */
+public abstract class AbstractSubjectCanonicalizer extends AbstractIdentifiableInitializableComponent
+        implements SubjectCanonicalizer {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractSubjectCanonicalizer.class);
+    
+    /** Cached log prefix. */
+    @NonnullAfterInit private String logPrefix;
+    
+    /** Match patterns and replacement strings to apply. */
+    @Nonnull private List<Pair<Pattern,String>> transforms;
+
+    /** Convert to uppercase prior to transforms? */
+    private boolean uppercase;
+    
+    /** Convert to lowercase prior to transforms? */
+    private boolean lowercase;
+    
+    /** Trim prior to transforms? */
+    private boolean trim;
+    
+    /** Constructor. */
+    public AbstractSubjectCanonicalizer() {
+        transforms = CollectionSupport.emptyList();
+
+        uppercase = false;
+        lowercase = false;
+        trim = false;
+    }
+
+    /**
+     * A collection of regular expression and replacement pairs.
+     * 
+     * @param newTransforms collection of replacement transforms
+     */
+    public void setTransforms(@Nullable final Collection<Pair<String, String>> newTransforms) {
+        checkSetterPreconditions();
+        if (newTransforms != null) {
+            transforms = new ArrayList<>();
+            for (final Pair<String,String> p : newTransforms) {
+                final Pattern pattern = Pattern.compile(StringSupport.trimOrNull(p.getFirst()));
+                transforms.add(new Pair<>(pattern, Constraint.isNotNull(
+                        StringSupport.trimOrNull(p.getSecond()), "Replacement expression cannot be null")));
+            }
+        } else {
+            transforms = CollectionSupport.emptyList();
+        }
+    }
+
+    /**
+     * Controls conversion to uppercase prior to applying any transforms.
+     * 
+     * @param flag  uppercase flag
+     */
+    public void setUppercase(final boolean flag) {
+        checkSetterPreconditions();
+        uppercase = flag;
+    }
+
+    /**
+     * Controls conversion to lowercase prior to applying any transforms.
+     * 
+     * @param flag lowercase flag
+     */
+    public void setLowercase(final boolean flag) {
+        checkSetterPreconditions();
+        lowercase = flag;
+    }
+    
+    /**
+     * Controls whitespace trimming prior to applying any transforms.
+     * 
+     * @param flag trim flag
+     */
+    public void setTrim(final boolean flag) {
+        checkSetterPreconditions();
+        trim = flag;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        logPrefix = "SubjectCanonicalizer " + ensureId() + ":";
+    }
+    
+    /**
+     * Performs this c14n action's pre-execute step. Default implementation just returns true iff a subject
+     * is set.
+     * 
+     * @param c14nContext the current subject canonicalization context
+     * 
+     * @return event indicating result of function
+     */
+    @Nullable public String apply(@Nullable final SubjectCanonicalizationContext c14nContext) {
+        checkComponentActive();
+        
+        if (c14nContext == null) {
+            return AuthnEventIds.INVALID_SUBJECT_C14N_CTX;
+        }
+        
+        if (c14nContext.getSubject() == null) {
+            c14nContext.setException(new SubjectCanonicalizationException("No Subject found in context"));
+            return AuthnEventIds.INVALID_SUBJECT;
+        }
+        
+        return doApply(c14nContext);
+    }
+    
+    /**
+     * Performs c14n if possible.
+     * 
+     * @param c14nContext the current subject canonicalization context
+     * 
+     * @return event indicating result of function
+     */
+    @Nonnull protected abstract String doApply(@Nonnull final SubjectCanonicalizationContext c14nContext);
+    
+    
+    /**
+     * Apply any configured regular expression replacements to an input value and return the result.
+     * 
+     * @param input the input string
+     * 
+     * @return  the result of applying the expressions
+     */
+    @Nonnull @NotEmpty protected String applyTransforms(@Nonnull @NotEmpty final String input) {
+        
+        String s = input;
+        
+        if (trim) {
+            log.debug("{} trimming whitespace of input string '{}'", getLogPrefix(), s);
+            s = s.trim();
+        }
+        
+        if (lowercase) {
+            log.debug("{} converting input string '{}' to lowercase", getLogPrefix(), s);
+            s = s.toLowerCase();
+        } else if (uppercase) {
+            log.debug("{} converting input string '{}' to uppercase", getLogPrefix(), s);
+            s = s.toUpperCase();
+        }
+
+        for (final Pair<Pattern,String> p : transforms) {
+            final Pattern pattern = p.getFirst();
+            if (pattern != null) {
+                final Matcher m = pattern.matcher(s);
+                log.debug("{} applying replacement expression '{}' against input '{}'", getLogPrefix(),
+                        pattern.pattern(), s);
+                s = m.replaceAll(p.getSecond());
+                log.debug("{} result of replacement is '{}'", getLogPrefix(), s);
+            }
+        }
+        
+        assert s != null;
+        return s;
+    }
+    
+    /**
+     * Return a prefix for logging messages for this component.
+     * 
+     * @return a string for insertion at the beginning of any log messages
+     */
+    @NonnullAfterInit protected String getLogPrefix() {
+        return logPrefix;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java
index 0478fe962..0927800df 100644
--- a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizationFlowDescriptor.java
@@ -51,6 +51,9 @@ public class SubjectCanonicalizationFlowDescriptor extends AbstractIdentifiableI
     /** Predicate that must be true for this flow to be usable for a given request. */
     @Nonnull private Predicate<ProfileRequestContext> activationCondition;
 
+    /** Optional "inline" c14n implementation to execute without running a subflow. */
+    @Nullable private SubjectCanonicalizer subjectCanonicalizer;
+    
     /** Constructor. */
     public SubjectCanonicalizationFlowDescriptor() {
         activationCondition = PredicateSupport.alwaysTrue();
@@ -61,7 +64,7 @@ public class SubjectCanonicalizationFlowDescriptor extends AbstractIdentifiableI
      * 
      * @return flow ID
      */
-    @NonnullAfterInit public String getFlowId() {
+    @Nullable public String getFlowId() {
         return flowId;
     }
 
@@ -94,13 +97,40 @@ public class SubjectCanonicalizationFlowDescriptor extends AbstractIdentifiableI
         activationCondition = Constraint.isNotNull(condition, "Activation condition predicate cannot be null");
     }
     
+    /**
+     * Get an "inline" implementation of subject c14n that does not require use of a Spring WebFlow.
+     * 
+     * @return implementation of {@link SubjectCanonicalizer} interface to use
+     * 
+     * @since 5.2.0
+     */
+    @Nullable public SubjectCanonicalizer getImplementation() {
+        return subjectCanonicalizer;
+    }
+    
+    /**
+     * Set an "inline" implementation of subject c14n that does not require use of a Spring WebFlow.
+     * 
+     * @param impl implementation of {@link SubjectCanonicalizer} interface to use
+     * 
+     * @since 5.2.0
+     */
+    public void setImplementation(@Nullable final SubjectCanonicalizer impl) {
+        checkSetterPreconditions();
+        subjectCanonicalizer = impl;
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
         
-        // Backfill flow ID with component ID.
-        if (flowId == null) {
+        if (subjectCanonicalizer != null) {
+            if (flowId != null) {
+                throw new ComponentInitializationException("Implementation object and flow ID are mutually exclusive.");
+            }
+        } else if (flowId == null) {
+            // Backfill flow ID with component ID.
             flowId = ensureId();
             if (!flowId.startsWith("c14n/")) {
                 throw new ComponentInitializationException("Defaulted flow ID must begin with c14n/ prefix.");
diff --git a/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizer.java b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizer.java
new file mode 100644
index 000000000..d03089810
--- /dev/null
+++ b/idp-authn-api/src/main/java/net/shibboleth/idp/authn/SubjectCanonicalizer.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed 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;
+
+import java.util.function.Function;
+
+import org.opensaml.profile.action.EventIds;
+
+import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+
+/**
+ * Marker interface for the ability to perform subject canonicalization "inline" without
+ * leveraging the Spring Webflow layer.
+ * 
+ * <p>Most c14n implementations are functional and don't involve a user interface or views
+ * and do not need the full capability of SWF. Some implementations may choose to leverage
+ * it for code modularity or reuse reasons.</p>
+ * 
+ * <p>The output of this function is a SWF event string ({@link EventIds#PROCEED_EVENT_ID}
+ * on success).</p>
+ * 
+ * @since 5.2.0
+ */
+public interface SubjectCanonicalizer extends Function<SubjectCanonicalizationContext,String> {
+
+}
\ No newline at end of file
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectSubjectCanonicalizationFlow.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectSubjectCanonicalizationFlow.java
index 3183e4922..c8c2b054e 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectSubjectCanonicalizationFlow.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SelectSubjectCanonicalizationFlow.java
@@ -20,9 +20,11 @@ import javax.annotation.Nullable;
 import net.shibboleth.idp.authn.AbstractSubjectCanonicalizationAction;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.SubjectCanonicalizationFlowDescriptor;
+import net.shibboleth.idp.authn.SubjectCanonicalizer;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 
 import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -35,11 +37,17 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * {@link SubjectCanonicalizationContext} has been fully populated. It uses the potential flows,
  * and their associated activation conditions to decide how to proceed.</p>
  * 
- * <p>This is a rare case in that the standard default event,
- * {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}, cannot be returned,
- * because the action must either dispatch to a flow by name, or signal an error.</p>
- * 
+ * <p>An enhancement added allows use of "inline" functional c14n implementations that
+ * don't require the sophistication of a webflow. When an inline bean is executed,
+ * its resulting events are relayed back as the result of this action in the case
+ * of {@link EventIds#PROCEED_EVENT_ID} or {@link AuthnEventIds#INVALID_SUBJECT},
+ * or transformed into {@link AuthnEventIds#RESELECT_FLOW} for any other event.</p>
+ *
+ * <p>After exhausting all options, {@link AuthnEventIds#NO_POTENTIAL_FLOW} is returned.</p>
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
  * @event {@link AuthnEventIds#NO_POTENTIAL_FLOW}
+ * @event {@link AuthnEventIds#RESELECT_FLOW}
  * @event Selected flow ID to execute
  * @pre <pre>ProfileRequestContext.getSubcontext(SubjectCanonicalizationContext.class) != null</pre>
  */
@@ -80,9 +88,26 @@ public class SelectSubjectCanonicalizationFlow extends AbstractSubjectCanonicali
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_POTENTIAL_FLOW);
             return;
         }
-        log.debug("{} Selecting c14n descriptor {} (WebFlow ID: {})", getLogPrefix(), flow.ensureId(),
-                flow.getFlowId());
-        ActionSupport.buildEvent(profileRequestContext, flow.getFlowId());
+        
+        final SubjectCanonicalizer inline = flow.getImplementation();
+        if (inline != null) {
+            log.debug("{} Selecting c14n descriptor {} (Inline implementation)", getLogPrefix(), flow.ensureId());
+            final String event = inline.apply(c14nContext);
+            if (event == EventIds.PROCEED_EVENT_ID || event == AuthnEventIds.INVALID_SUBJECT) {
+                ActionSupport.buildEvent(profileRequestContext, event);
+            } else {
+                log.warn("{} Inline c14n implementation {} failed with event: {}", getLogPrefix(), flow.ensureId(),
+                        event);
+                // As with flow events, we remap anything else to a reselect signal to iterate the loop
+                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.RESELECT_FLOW);
+            }
+        } else {
+            final String flowId = flow.getFlowId();
+            assert flowId != null;
+            log.debug("{} Selecting c14n descriptor {} (WebFlow ID: {})", getLogPrefix(), flow.ensureId(),
+                    flow.getFlowId());
+            ActionSupport.buildEvent(profileRequestContext, flowId);
+        }
     }
 
     /**
@@ -106,7 +131,7 @@ public class SelectSubjectCanonicalizationFlow extends AbstractSubjectCanonicali
                     return flow;
                 }
                 final Exception ctxException = c14nContext.getException();
-                log.debug("{} C14N flow descriptor {} was not applicable: {}", getLogPrefix(), flow.getId(),
+                log.debug("{} C14N flow descriptor {} was not applicable: {}", getLogPrefix(), flow.ensureId(),
                         ctxException!= null ? ctxException.getMessage() : "reason unknown");
                 c14nContext.setException(null);
                 
diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalization.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalization.java
index 35b7425e7..5d6927333 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalization.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalization.java
@@ -21,10 +21,10 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.security.auth.Subject;
 
-import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.idp.authn.AbstractSubjectCanonicalizationAction;
+import net.shibboleth.idp.authn.AbstractSubjectCanonicalizer;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.SubjectCanonicalizationException;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
@@ -41,40 +41,45 @@ import net.shibboleth.idp.authn.principal.UsernamePrincipal;
  * @post <pre>SubjectCanonicalizationContext.getPrincipalName() != null
  *  || SubjectCanonicalizationContext.getException() != null</pre>
  */
-public class SimpleSubjectCanonicalization extends AbstractSubjectCanonicalizationAction {
-
-    /** Supplies logic for pre-execute test. */
-    @Nonnull private final ActivationCondition embeddedPredicate;
-    
-    /** The custom Principal to operate on. */
-    @Nullable private UsernamePrincipal usernamePrincipal;
-    
-    /** Constructor. */
-    public SimpleSubjectCanonicalization() {
-        embeddedPredicate = new ActivationCondition();
-    }
+public class SimpleSubjectCanonicalization extends AbstractSubjectCanonicalizer {
     
     /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext, 
-            @Nonnull final SubjectCanonicalizationContext c14nContext) {
+    @Nonnull public String doApply(@Nonnull final SubjectCanonicalizationContext c14nContext) {
 
-        if (embeddedPredicate.apply(profileRequestContext, c14nContext, true)) {
-            final Subject c14CtxSubject = c14nContext.getSubject();
-            assert c14CtxSubject != null;
-            usernamePrincipal = c14CtxSubject.getPrincipals(UsernamePrincipal.class).iterator().next();
-            return super.doPreExecute(profileRequestContext, c14nContext);
+        final UsernamePrincipal usernamePrincipal = getUsernamePrincipal(c14nContext);
+        if (usernamePrincipal == null) {
+            return AuthnEventIds.INVALID_SUBJECT;
         }
         
-        return false;
+        c14nContext.setPrincipalName(applyTransforms(usernamePrincipal.getName()));
+        return EventIds.PROCEED_EVENT_ID;
     }
     
-    /** {@inheritDoc} */
-    @Override
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext, 
+    /**
+     * Helper method that returns the first and only {@link UsernamePrincipal}, returning null
+     * otherwise.
+     * 
+     * @param c14nContext input context
+     * 
+     * @return the only matching principal or null
+     */
+    @Nullable private static UsernamePrincipal getUsernamePrincipal(
             @Nonnull final SubjectCanonicalizationContext c14nContext) {
-        assert usernamePrincipal != null;
-        c14nContext.setPrincipalName(applyTransforms(usernamePrincipal.getName()));
+        
+        final Subject subject = c14nContext.getSubject();
+        if (subject != null) {
+            final Set<UsernamePrincipal> prins = subject.getPrincipals(UsernamePrincipal.class);
+            if (prins == null || prins.isEmpty()) {
+                c14nContext.setException(new SubjectCanonicalizationException("No UsernamePrincipals were found"));
+            } else if (prins.size() > 1) {
+                c14nContext.setException(
+                        new SubjectCanonicalizationException("Multiple UsernamePrincipals were found"));
+            } else {
+                return prins.iterator().next();
+            }
+        }
+        
+        return null;
     }
      
     /** A predicate that determines if this action can run or not. */
@@ -87,53 +92,12 @@ public class SimpleSubjectCanonicalization extends AbstractSubjectCanonicalizati
                 final SubjectCanonicalizationContext c14nContext =
                         input.getSubcontext(SubjectCanonicalizationContext.class);
                 if (c14nContext != null) {
-                    return apply(input, c14nContext, false);
+                    return getUsernamePrincipal(c14nContext) != null;
                 }
             }
             
             return false;
         }
-
-        /**
-         * Helper method that runs either as part of the {@link Predicate} or directly from
-         * the {@link SimpleSubjectCanonicalization#doPreExecute(ProfileRequestContext, SubjectCanonicalizationContext)}
-         * method above.
-         * 
-         * @param profileRequestContext the current profile request context
-         * @param c14nContext   the current c14n context
-         * @param duringAction  true iff the method is run from the action above
-         * @return true iff the action can operate successfully on the candidate contexts
-         */
-        public boolean apply(@Nonnull final ProfileRequestContext profileRequestContext,
-                @Nonnull final SubjectCanonicalizationContext c14nContext, final boolean duringAction) {
-
-            final Set<UsernamePrincipal> usernames;
-            final Subject c14CtxSubject = c14nContext.getSubject();
-            if (c14CtxSubject  != null) {
-                usernames = c14CtxSubject .getPrincipals(UsernamePrincipal.class);
-            } else {
-                usernames = null;
-            }
-            
-            if (usernames == null || usernames.isEmpty()) {
-                c14nContext.setException(
-                        new SubjectCanonicalizationException("No UsernamePrincipals were found"));
-                if (duringAction) {
-                    ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_SUBJECT);
-                }
-                return false;
-            } else if (usernames.size() > 1) {
-                c14nContext.setException(
-                        new SubjectCanonicalizationException("Multiple UsernamePrincipals were found"));
-                if (duringAction) {
-                    ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_SUBJECT);
-                }
-                return false;
-            }
-            
-            return true;
-        }
-        
     }
 
 }
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalizationTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalizationTest.java
index 3742ff045..d5fbeb398 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalizationTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/SimpleSubjectCanonicalizationTest.java
@@ -22,11 +22,10 @@ import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
 import net.shibboleth.idp.authn.impl.testing.BaseAuthenticationContextTest;
 import net.shibboleth.idp.authn.principal.UsernamePrincipal;
-import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
-import org.springframework.webflow.execution.Event;
+import org.opensaml.profile.action.EventIds;
 import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
@@ -41,37 +40,37 @@ public class SimpleSubjectCanonicalizationTest extends BaseAuthenticationContext
         super.setUp();
         
         action = new SimpleSubjectCanonicalization();
+        action.setId("c14n/simple");
         action.setTransforms(Arrays.asList(new Pair<>("^(.+)@osu\\.edu$", "$1")));
         action.initialize();
     }
     
     @Test public void testNoContext() {
-        final Event event = action.execute(src);
-        
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT_C14N_CTX);
+        final String event = action.apply(null);
+        Assert.assertEquals(event, AuthnEventIds.INVALID_SUBJECT_C14N_CTX);
     }
 
     @Test public void testNoPrincipal() {
-        Subject subject = new Subject();
-        prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
+        final Subject subject = new Subject();
+        final var c14n = prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
         
-        final Event event = action.execute(src);
+        final String event = action.apply(c14n);
         
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT);
+        Assert.assertEquals(event, AuthnEventIds.INVALID_SUBJECT);
         final SubjectCanonicalizationContext scc = prc.getSubcontext(SubjectCanonicalizationContext.class);
         assert scc != null;
         Assert.assertNotNull(scc.getException());
     }
 
     @Test public void testMultiPrincipals() {
-        Subject subject = new Subject();
+        final Subject subject = new Subject();
         subject.getPrincipals().add(new UsernamePrincipal("foo"));
         subject.getPrincipals().add(new UsernamePrincipal("bar"));
-        prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
+        final var c14n = prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
         
-        final Event event = action.execute(src);
+        final String event = action.apply(c14n);
         
-        ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT);
+        Assert.assertEquals(event, AuthnEventIds.INVALID_SUBJECT);
         final SubjectCanonicalizationContext scc = prc.getSubcontext(SubjectCanonicalizationContext.class);
         assert scc != null;
         Assert.assertNotNull(scc.getException());
@@ -80,11 +79,11 @@ public class SimpleSubjectCanonicalizationTest extends BaseAuthenticationContext
     @Test public void testSuccess() {
         Subject subject = new Subject();
         subject.getPrincipals().add(new UsernamePrincipal("foo"));
-        prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
+        final var c14n = prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
         
-        final Event event = action.execute(src);
+        final String event = action.apply(c14n);
         
-        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
         SubjectCanonicalizationContext sc = prc.getSubcontext(SubjectCanonicalizationContext.class);
         assert sc != null;
         Assert.assertEquals(sc.getPrincipalName(), "foo");
@@ -93,11 +92,11 @@ public class SimpleSubjectCanonicalizationTest extends BaseAuthenticationContext
     @Test public void testTransform() {
         Subject subject = new Subject();
         subject.getPrincipals().add(new UsernamePrincipal("foo at osu.edu"));
-        prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
+        final var c14n = prc.ensureSubcontext(SubjectCanonicalizationContext.class).setSubject(subject);
         
-        final Event event = action.execute(src);
+        final String event = action.apply(c14n);
         
-        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(event, EventIds.PROCEED_EVENT_ID);
         SubjectCanonicalizationContext sc = prc.getSubcontext(SubjectCanonicalizationContext.class);
         assert sc != null;
         Assert.assertEquals(sc.getPrincipalName(), "foo");
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml
index d6276c3fb..c9542820e 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/subject-c14n-system.xml
@@ -23,11 +23,21 @@
     <util:list id="shibboleth.ProxyNameTransformFormats" />
 
     <import resource="${idp.home}/conf/c14n/subject-c14n.xml" />
+    
+    <!-- Legacy compatibility. -->
+    <import resource="conditional:%{idp.home}/conf/c14n/simple-subject-c14n-config.xml" />
 
     <bean id="shibboleth.PostLoginSubjectCanonicalizationFlow" abstract="true"
         class="net.shibboleth.idp.authn.PostLoginSubjectCanonicalizationFlowDescriptor" />
 
     <bean id="c14n/simple" parent="shibboleth.PostLoginSubjectCanonicalizationFlow">
+        <property name="implementation">
+            <bean class="net.shibboleth.idp.authn.impl.SimpleSubjectCanonicalization"
+                p:lowercase="#{getObject('shibboleth.c14n.simple.Lowercase') ?: %{idp.c14n.simple.lowercase:false}}"
+                p:uppercase="#{getObject('shibboleth.c14n.simple.Uppercase') ?: %{idp.c14n.simple.uppercase:false}}"
+                p:trim="#{getObject('shibboleth.c14n.simple.Trim') ?: %{idp.c14n.simple.trim:true}}"
+                p:transforms="#{getObject('shibboleth.c14n.simple.Transforms')}" />
+        </property>
         <property name="activationCondition">
             <bean class="net.shibboleth.idp.authn.impl.SimpleSubjectCanonicalization.ActivationCondition" />
         </property>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
index 925ddd429..0fc0c2aa2 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/conf/webflow-config.xml
@@ -99,7 +99,6 @@
                 <entry key="c14n" value="classpath:/net/shibboleth/idp/flows/c14n/subject-c14n-flow.xml" />
         
                 <!-- Standard post-login C14N methods. -->
-                <entry key="c14n/simple" value="classpath:/net/shibboleth/idp/flows/c14n/simple-subject-c14n-flow.xml" />
                 <entry key="c14n/x500" value="classpath:/net/shibboleth/idp/flows/c14n/x500-subject-c14n-flow.xml" />
                 <entry key="c14n/attribute" value="classpath:/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-flow.xml" />
         
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/simple-subject-c14n-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/simple-subject-c14n-beans.xml
deleted file mode 100644
index db9ad348f..000000000
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/simple-subject-c14n-beans.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<beans xmlns="http://www.springframework.org/schema/beans"
-       xmlns:context="http://www.springframework.org/schema/context"
-       xmlns:util="http://www.springframework.org/schema/util"
-       xmlns:p="http://www.springframework.org/schema/p"
-       xmlns:c="http://www.springframework.org/schema/c"
-       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
-                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
-                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
-                           
-       default-init-method="initialize"
-       default-destroy-method="destroy">
-
-    <import resource="conditional:%{idp.home}/conf/c14n/simple-subject-c14n-config.xml" />
-    
-    <bean id="SimpleSubjectCanonicalization"
-        class="net.shibboleth.idp.authn.impl.SimpleSubjectCanonicalization" scope="prototype"
-        p:lowercase="#{getObject('shibboleth.c14n.simple.Lowercase') ?: %{idp.c14n.simple.lowercase:false}}"
-        p:uppercase="#{getObject('shibboleth.c14n.simple.Uppercase') ?: %{idp.c14n.simple.uppercase:false}}"
-        p:trim="#{getObject('shibboleth.c14n.simple.Trim') ?: %{idp.c14n.simple.trim:true}}"
-        p:transforms="#{getObject('shibboleth.c14n.simple.Transforms')}" />
-    
-</beans>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/simple-subject-c14n-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/simple-subject-c14n-flow.xml
deleted file mode 100644
index e28a1c2e2..000000000
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/simple-subject-c14n-flow.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<flow xmlns="http://www.springframework.org/schema/webflow"
-      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-      xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
-      parent="c14n.abstract">
-
-    <!-- This is a one-step subflow that does Subject Canonicalization using the simplest built-in action. -->
-    
-    <action-state id="SimpleSubjectCanonicalization">
-        <evaluate expression="SimpleSubjectCanonicalization" />
-        <evaluate expression="'proceed'" />
-        
-        <transition on="proceed" to="proceed" />
-    </action-state>
-
-    <bean-import resource="simple-subject-c14n-beans.xml" />
-
-</flow>
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/subject-c14n-flow.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/subject-c14n-flow.xml
index 029c62e66..d7e0bb0ab 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/subject-c14n-flow.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/subject-c14n-flow.xml
@@ -15,11 +15,17 @@
     <action-state id="SelectSubjectCanonicalizationFlow">
         <evaluate expression="SelectSubjectCanonicalizationFlow" />
 
+        <!-- Indicates an inline implementation succeeded. -->
+        <transition on="proceed" to="proceed" />
+
         <!-- Call a subflow with the same ID as the event. -->
         <transition on="#{currentEvent.id.startsWith('c14n/')}" to="CallSubjectCanonicalizationFlow" />
         
         <!-- Remap the case of no flows to run into a more recognizeable fatal error. -->
         <transition on="NoPotentialFlow" to="SubjectCanonicalizationError" />
+        
+        <!-- Signals us to loop back for another flow. -->
+        <transition on="ReselectFlow" to="SelectSubjectCanonicalizationFlow" />
     </action-state>
 
     <!--

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


More information about the commits mailing list