[java-identity-provider] branch main updated: IDP-1729 - Extend attribute-sourced c14n to IdPAttributePrincipal

Scott Cantor cantor.2 at osu.edu
Wed Dec 30 22:32:30 UTC 2020


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=fad5d5597fcb425e670289905f31373055f4fd2f

The following commit(s) were added to refs/heads/main by this push:
       new  fad5d5597 IDP-1729 - Extend attribute-sourced c14n to IdPAttributePrincipal
fad5d5597 is described below

commit fad5d5597fcb425e670289905f31373055f4fd2f
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Wed Dec 30 17:32:24 2020 -0500

    IDP-1729 - Extend attribute-sourced c14n to IdPAttributePrincipal
    
    https://issues.shibboleth.net/jira/browse/IDP-1729
---
 .../AttributeSourcedSubjectCanonicalization.java   | 116 ++++++++++++++++-----
 ...ttributeSourcedSubjectCanonicalizationTest.java | 102 ++++++++++++++++--
 .../c14n/attribute-sourced-subject-c14n-beans.xml  |   2 +
 .../resources/conf/c14n/subject-c14n.properties    |   2 +
 4 files changed, 189 insertions(+), 33 deletions(-)

diff --git a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
index ff2727d9d..7305440ab 100644
--- a/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
+++ b/idp-authn-impl/src/main/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalization.java
@@ -19,11 +19,15 @@ package net.shibboleth.idp.authn.impl;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.security.auth.Subject;
 
 import net.shibboleth.idp.attribute.IdPAttribute;
 import net.shibboleth.idp.attribute.IdPAttributeValue;
@@ -34,6 +38,7 @@ import net.shibboleth.idp.authn.AbstractSubjectCanonicalizationAction;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.SubjectCanonicalizationException;
 import net.shibboleth.idp.authn.context.SubjectCanonicalizationContext;
+import net.shibboleth.idp.authn.principal.IdPAttributePrincipal;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
@@ -70,6 +75,12 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
     /** Delimiter to use for scoped attribute serialization. */
     private char delimiter;
     
+    /** Whether to also check the original Subject for {@link IdPAttributePrincipal}s. */
+    private boolean resolveFromSubject;
+    
+    /** Indexed attributes pulled from subject. */
+    @Nonnull @NonnullElements private Map<String,IdPAttribute> subjectSourcedAttributes;
+    
     /** Ordered list of attributes to look for and read from. */
     @Nonnull @NonnullElements private List<String> attributeSourceIds;
         
@@ -83,6 +94,7 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
     public AttributeSourcedSubjectCanonicalization() {
         delimiter = '@';
         attributeSourceIds = Collections.emptyList();
+        subjectSourcedAttributes = Collections.emptyMap();
         
         attributeContextLookupStrategy =
                 new ChildContextLookup<>(AttributeContext.class).compose(
@@ -100,6 +112,20 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
         delimiter = ch;
     }
     
+    /**
+     * Whether to include any {@link IdPAttributePrincipal} objects found in the input {@link Subject}
+     * when searching for a matching attribute ID.
+     * 
+     * @param flag flag to set
+     * 
+     * @since 4.1.0
+     */
+    public void setResolveFromSubject(final boolean flag) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        resolveFromSubject = flag;
+    }
+    
     /**
      * Set the attribute IDs to read from in order of preference.
      * 
@@ -139,48 +165,52 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext, 
             @Nonnull final SubjectCanonicalizationContext c14nContext) {
 
+        if (!super.doPreExecute(profileRequestContext, c14nContext)) {
+            return false;
+        }
+        
+        if (resolveFromSubject) {
+            final Set<IdPAttributePrincipal> subjectSourced =
+                    c14nContext.getSubject().getPrincipals(IdPAttributePrincipal.class);
+            if (subjectSourced != null && !subjectSourced.isEmpty()) {
+                subjectSourcedAttributes = new HashMap<>(subjectSourced.size());
+                subjectSourced.forEach(a -> subjectSourcedAttributes.put(a.getAttribute().getId(), a.getAttribute()));
+            }
+        }
+        
         attributeCtx = attributeContextLookupStrategy.apply(profileRequestContext);
-        if (attributeCtx == null || attributeCtx.getIdPAttributes().isEmpty()) {
+        if (subjectSourcedAttributes.isEmpty() && (attributeCtx == null || attributeCtx.getIdPAttributes().isEmpty())) {
             log.warn("{} No attributes found, canonicalization not possible", getLogPrefix());
             c14nContext.setException(new SubjectCanonicalizationException("No attributes were found"));
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_SUBJECT);
             return false;
         }
         
-        return super.doPreExecute(profileRequestContext, c14nContext);
+        return true;
     }
     
     /** {@inheritDoc} */
-    // CheckStyle: ReturnCount OFF
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext, 
             @Nonnull final SubjectCanonicalizationContext c14nContext) {
         
         for (final String id : attributeSourceIds) {
-            final IdPAttribute attr = attributeCtx.getIdPAttributes().get(id);
-            if (attr == null) {
-                continue;
-            }
-            for (final IdPAttributeValue val : attr.getValues()) {
-                if (val instanceof StringAttributeValue) {
-                    final StringAttributeValue stringVal = (StringAttributeValue) val;
-                    if (stringVal.getValue() == null || stringVal.getValue().isEmpty()) {
-                        log.debug("{} Ignoring null/empty string value", getLogPrefix());
-                        continue;
-                    }
-                    log.debug("{} Using attribute {} string value {} as input to transforms", getLogPrefix(), id,
-                            stringVal.getValue());
-                    c14nContext.setPrincipalName(applyTransforms(stringVal.getValue()));
+            
+            IdPAttribute attr = subjectSourcedAttributes.get(id);
+            if (attr != null) {
+                final String result = findValue(attr);
+                if (result != null) {
+                    c14nContext.setPrincipalName(result);
                     return;
-                } else if (val instanceof ScopedStringAttributeValue) {
-                    final ScopedStringAttributeValue scoped = (ScopedStringAttributeValue) val;
-                    final String withScope = scoped.getValue() + delimiter + scoped.getScope();
-                    log.debug("{} Using attribute {} scoped value {} as input to transforms", getLogPrefix(), id,
-                            withScope);
-                    c14nContext.setPrincipalName(applyTransforms(withScope));
-                    return;
-                } else {
-                    log.warn("{} Unsupported attribute value type: {}", getLogPrefix(), val.getClass().getName());
+                }
+            } else if (attributeCtx != null) {
+                attr = attributeCtx.getIdPAttributes().get(id);
+                if (attr != null) {
+                    final String result = findValue(attr);
+                    if (result != null) {
+                        c14nContext.setPrincipalName(result);
+                        return;
+                    }
                 }
             }
         }
@@ -189,6 +219,38 @@ public class AttributeSourcedSubjectCanonicalization extends AbstractSubjectCano
         c14nContext.setException(new SubjectCanonicalizationException("No usable attribute values were found"));
         ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_SUBJECT);
     }
-    // CheckStyle: ReturnCount ON
 
+    /**
+     * Check for a compatible value in the input attribute.
+     * 
+     * @param attribute input attribute
+     * 
+     * @return value to use for result, or null
+     */
+    @Nullable private String findValue(@Nonnull final IdPAttribute attribute) {
+        
+        for (final IdPAttributeValue val : attribute.getValues()) {
+            if (val instanceof StringAttributeValue) {
+                final StringAttributeValue stringVal = (StringAttributeValue) val;
+                if (stringVal.getValue() == null || stringVal.getValue().isEmpty()) {
+                    log.debug("{} Ignoring null/empty string value", getLogPrefix());
+                    continue;
+                }
+                log.debug("{} Using attribute {} string value {} as input to transforms", getLogPrefix(),
+                        attribute.getId(), stringVal.getValue());
+                return applyTransforms(stringVal.getValue());
+            } else if (val instanceof ScopedStringAttributeValue) {
+                final ScopedStringAttributeValue scoped = (ScopedStringAttributeValue) val;
+                final String withScope = scoped.getValue() + delimiter + scoped.getScope();
+                log.debug("{} Using attribute {} scoped value {} as input to transforms", getLogPrefix(),
+                        attribute.getId(), withScope);
+                return applyTransforms(withScope);
+            } else {
+                log.warn("{} Unsupported attribute value type: {}", getLogPrefix(), val.getClass().getName());
+            }
+        }
+        
+        return null;
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
index 08a4c1891..df0663213 100644
--- a/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
+++ b/idp-authn-impl/src/test/java/net/shibboleth/idp/authn/impl/AttributeSourcedSubjectCanonicalizationTest.java
@@ -17,8 +17,8 @@
 
 package net.shibboleth.idp.authn.impl;
 
-import java.util.Arrays;
 import java.util.Collections;
+import java.util.List;
 
 import javax.security.auth.Subject;
 
@@ -33,6 +33,7 @@ import net.shibboleth.idp.attribute.context.AttributeContext;
 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.IdPAttributePrincipal;
 import net.shibboleth.idp.profile.testing.ActionTestingSupport;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
@@ -46,8 +47,7 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
         super.setUp();
         
         action = new AttributeSourcedSubjectCanonicalization();
-        action.setAttributeSourceIds(Arrays.asList("attr1", "attr2"));
-        action.initialize();
+        action.setAttributeSourceIds(List.of("attr1", "attr2"));
     }
     
     @Test(expectedExceptions=ComponentInitializationException.class)
@@ -56,13 +56,30 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
         action.initialize();
     }
     
-    @Test public void testNoContext() {
+    @Test public void testNoContext() throws ComponentInitializationException {
+        action.initialize();
+        
         final Event event = action.execute(src);
         
         ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT_C14N_CTX);
     }
 
-    @Test public void testNoAttributes() {
+    @Test public void testNoAttributes() throws ComponentInitializationException {
+        action.initialize();
+
+        Subject subject = new Subject();
+        prc.getSubcontext(SubjectCanonicalizationContext.class, true).setSubject(subject);
+        
+        final Event event = action.execute(src);
+        
+        ActionTestingSupport.assertEvent(event, AuthnEventIds.INVALID_SUBJECT);
+        Assert.assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class, false).getException());
+    }
+
+    @Test public void testNoSubjectSourcedAttributes() throws ComponentInitializationException {
+        action.setResolveFromSubject(true);
+        action.initialize();
+        
         Subject subject = new Subject();
         prc.getSubcontext(SubjectCanonicalizationContext.class, true).setSubject(subject);
         
@@ -72,11 +89,15 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
         Assert.assertNotNull(prc.getSubcontext(SubjectCanonicalizationContext.class, false).getException());
     }
 
-    @Test public void testSuccess() {
+    @Test public void testSuccess() throws ComponentInitializationException {
+        action.initialize();
+        
         final IdPAttribute inputAttribute = new IdPAttribute("attr2");
         inputAttribute.setValues(Collections.singletonList(new StringAttributeValue("foo")));
+        
         final SubjectCanonicalizationContext sc = prc.getSubcontext(SubjectCanonicalizationContext.class, true);
         sc.setSubject(new Subject());
+        
         sc.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singleton(inputAttribute));
         
         final Event event = action.execute(src);
@@ -85,4 +106,73 @@ public class AttributeSourcedSubjectCanonicalizationTest extends BaseAuthenticat
         Assert.assertEquals(sc.getPrincipalName(), "foo");
     }
 
+    @Test public void testSubjectSourcedSuccess() throws ComponentInitializationException {
+        action.setResolveFromSubject(true);
+        action.initialize();
+        
+        final IdPAttribute inputAttribute = new IdPAttribute("attr2");
+        inputAttribute.setValues(Collections.singletonList(new StringAttributeValue("foo")));
+        final SubjectCanonicalizationContext sc = prc.getSubcontext(SubjectCanonicalizationContext.class, true);
+        sc.setSubject(new Subject());
+        sc.getSubject().getPrincipals().add(new IdPAttributePrincipal(inputAttribute));
+
+        final Event event = action.execute(src);
+        
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(sc.getPrincipalName(), "foo");
+    }
+
+    @Test public void testDualSubjectSourcedSuccess() throws ComponentInitializationException {
+        action.setResolveFromSubject(true);
+        action.initialize();
+
+        final IdPAttribute attr2 = new IdPAttribute("attr2");
+        attr2.setValues(Collections.singletonList(new StringAttributeValue("foo")));
+
+        final IdPAttribute attr2bar = new IdPAttribute("attr2");
+        attr2bar.setValues(Collections.singletonList(new StringAttributeValue("bar")));
+
+        final SubjectCanonicalizationContext sc = prc.getSubcontext(SubjectCanonicalizationContext.class, true);
+        sc.setSubject(new Subject());
+        sc.getSubject().getPrincipals().add(new IdPAttributePrincipal(attr2));
+        
+        sc.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singleton(attr2bar));
+        
+        Event event = action.execute(src);
+        
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(sc.getPrincipalName(), "foo");
+
+        sc.getSubject().getPrincipals().clear();
+        sc.getSubject().getPrincipals().add(new IdPAttributePrincipal(attr2bar));
+        sc.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singleton(attr2));
+
+        event = action.execute(src);
+        
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(sc.getPrincipalName(), "bar");
+    }
+
+    @Test public void testDualSubjectSourcedSuccess2() throws ComponentInitializationException {
+        action.setResolveFromSubject(true);
+        action.initialize();
+
+        final IdPAttribute attr2 = new IdPAttribute("attr2");
+        attr2.setValues(Collections.singletonList(new StringAttributeValue("bar")));
+
+        final IdPAttribute attr1 = new IdPAttribute("attr1");
+        attr1.setValues(Collections.singletonList(new StringAttributeValue("foo")));
+
+        final SubjectCanonicalizationContext sc = prc.getSubcontext(SubjectCanonicalizationContext.class, true);
+        sc.setSubject(new Subject());
+        sc.getSubject().getPrincipals().add(new IdPAttributePrincipal(attr2));
+        
+        sc.getSubcontext(AttributeContext.class, true).setIdPAttributes(Collections.singleton(attr1));
+        
+        final Event event = action.execute(src);
+        
+        ActionTestingSupport.assertProceedEvent(event);
+        Assert.assertEquals(sc.getPrincipalName(), "foo");
+    }
+    
 }
\ No newline at end of file
diff --git a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml
index 1131af5c4..d9840ac64 100644
--- a/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml
+++ b/idp-conf-impl/src/main/resources/net/shibboleth/idp/flows/c14n/attribute-sourced-subject-c14n-beans.xml
@@ -28,6 +28,7 @@
     
     <bean id="ResolveAttributes" class="net.shibboleth.idp.profile.impl.ResolveAttributes" scope="prototype"
             c:resolverService-ref="shibboleth.AttributeResolverService"
+            p:activationCondition="#{getObject('shibboleth.c14n.attribute.ResolutionCondition') ?: getObject('shibboleth.Conditions.TRUE')}"
             p:attributesToResolve-ref="shibboleth.c14n.attribute.AttributesToResolve"
             p:maskFailures="%{idp.service.attribute.resolver.maskFailures:true}"
             p:principalNameLookupStrategy="#{getObject('shibboleth.c14n.attribute.PrincipalNameLookupStrategy')}">
@@ -41,6 +42,7 @@
     <bean id="AttributeSourcedSubjectCanonicalization"
         class="net.shibboleth.idp.authn.impl.AttributeSourcedSubjectCanonicalization" scope="prototype"
         p:attributeSourceIds-ref="shibboleth.c14n.attribute.AttributeSourceIds"
+        p:resolveFromSubject="%{idp.c14n.attribute.resolveFromSubject:false}"
         p:lowercase="#{getObject('shibboleth.c14n.attribute.Lowercase') ?: %{idp.c14n.attribute.lowercase:false}}"
         p:uppercase="#{getObject('shibboleth.c14n.attribute.Uppercase') ?: %{idp.c14n.attribute.uppercase:false}}"
         p:trim="#{getObject('shibboleth.c14n.attribute.Trim') ?: %{idp.c14n.attribute.trim:true}}"
diff --git a/idp-conf/src/main/resources/conf/c14n/subject-c14n.properties b/idp-conf/src/main/resources/conf/c14n/subject-c14n.properties
index 8a2c8f4dc..a25892b1a 100644
--- a/idp-conf/src/main/resources/conf/c14n/subject-c14n.properties
+++ b/idp-conf/src/main/resources/conf/c14n/subject-c14n.properties
@@ -12,6 +12,8 @@
 #idp.c14n.attribute.lowercase = false
 #idp.c14n.attribute.uppercase = false
 #idp.c14n.attribute.trim = true
+# Allows direct use of attributes via SAML/etc. authentication 
+#idp.c14n.attribute.resolveFromSubject = false
 # Lists of attributes to resolve...
 #idp.c14n.attribute.attributesToResolve =
 #  and then select a principal name from

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


More information about the commits mailing list