[java-identity-provider] branch master updated: IDP-1446 - Create CAS transcoders

Scott Cantor cantor.2 at osu.edu
Tue May 14 20:45:15 EDT 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=e56cc890ce4c83b9410e3d6fa77903ee559b9ed5

The following commit(s) were added to refs/heads/master by this push:
       new  e56cc89   IDP-1446 - Create CAS transcoders
e56cc89 is described below

commit e56cc890ce4c83b9410e3d6fa77903ee559b9ed5
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue May 14 20:45:09 2019 -0400

    IDP-1446 - Create CAS transcoders
    
    https://issues.shibboleth.net/jira/browse/IDP-1446
    
    Replace CAS attribute logic with new classes.
---
 .../idp/cas/protocol/TicketValidationResponse.java |  45 +++---
 .../BuildSamlValidationSuccessMessageAction.java   |   6 +-
 .../PrepareTicketValidationResponseAction.java     | 154 ++++++++++++++++++---
 .../system/flows/cas/validate-abstract-beans.xml   |   3 +-
 .../resources/system/views/cas/validateSuccess.vm  |   8 +-
 5 files changed, 165 insertions(+), 51 deletions(-)

diff --git a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/protocol/TicketValidationResponse.java b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/protocol/TicketValidationResponse.java
index a42a72b..a812392 100644
--- a/idp-cas-api/src/main/java/net/shibboleth/idp/cas/protocol/TicketValidationResponse.java
+++ b/idp-cas-api/src/main/java/net/shibboleth/idp/cas/protocol/TicketValidationResponse.java
@@ -17,14 +17,18 @@
 
 package net.shibboleth.idp.cas.protocol;
 
+import net.shibboleth.idp.cas.attribute.Attribute;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotLive;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -40,21 +44,26 @@ public class TicketValidationResponse extends AbstractProtocolResponse {
     @Nullable private String userName;
 
     /** User attributes. */
-    @Nonnull private Map<String, List<String>> attributes = new HashMap<>();
+    @Nonnull @NonnullElements private final List<Attribute> attributes;
 
     /** Proxy granting ticket IOU. */
     @Nullable private String pgtIou;
 
     /** Proxies traversed. */
-    @Nonnull private List<String> proxies = new ArrayList<>();
+    @Nonnull @NonnullElements private final List<String> proxies;
 
+    /** Constructor. */
+    public TicketValidationResponse() {
+        attributes = new ArrayList<>();
+        proxies = new ArrayList<>();
+    }
 
     /**
      * Get the non-null subject principal on ticket validation success.
      * 
      * @return non-null subject principal on ticket validation success
      */
-    @Nullable public String getUserName() {
+    @Nullable @NotEmpty public String getUserName() {
         return userName;
     }
 
@@ -63,32 +72,26 @@ public class TicketValidationResponse extends AbstractProtocolResponse {
      * 
      * @param user non-null subject principal on ticket validation success
      */
-    public void setUserName(@Nonnull final String user) {
+    public void setUserName(@Nonnull @NotEmpty final String user) {
         userName = Constraint.isNotNull(user, "Username cannot be null");
     }
 
     /**
-     * Get the Immutable map of user attributes.
+     * Get the immutable collection of user attributes.
      * 
-     * @return immutable map of user attributes
+     * @return immutable collection of user attributes
      */
-    @Nonnull public Map<String, List<String>> getAttributes() {
-        return Collections.unmodifiableMap(attributes);
+    @Nonnull @NonnullElements @NotLive @Unmodifiable public Collection<Attribute> getAttributes() {
+        return Collections.unmodifiableList(attributes);
     }
 
     /**
-     * Add an attribute to the attribute mapping.
+     * Add an attribute to the attribute collection.
      * 
-     * @param name attribute name
-     * @param value attribute value
+     * @param attribute the attribute
      */
-    public void addAttribute(@Nonnull final String name, @Nonnull final String value) {
-        List<String> values = attributes.get(name);
-        if (values == null) {
-            values = new ArrayList<>();
-            attributes.put(name, values);
-        }
-        values.add(value);
+    public void addAttribute(@Nonnull final Attribute attribute) {
+        attributes.add(attribute);
     }
 
     /**
@@ -114,7 +117,7 @@ public class TicketValidationResponse extends AbstractProtocolResponse {
      * 
      * @return immutable list of proxies traversed in order of most recent to last recent
      */
-    @Nonnull public List<String> getProxies() {
+    @Nonnull @NonnullElements @NotLive @Unmodifiable public List<String> getProxies() {
         return Collections.unmodifiableList(proxies);
     }
 
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildSamlValidationSuccessMessageAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildSamlValidationSuccessMessageAction.java
index 4f6aabd..cf02a88 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildSamlValidationSuccessMessageAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/BuildSamlValidationSuccessMessageAction.java
@@ -144,11 +144,11 @@ public class BuildSamlValidationSuccessMessageAction extends AbstractOutgoingSam
         final AttributeStatement attrStatement = newSAMLObject(
                 AttributeStatement.class, AttributeStatement.DEFAULT_ELEMENT_NAME);
         attrStatement.setSubject(newSubject(state.getPrincipalName()));
-        for (final String attrName : ticketResponse.getAttributes().keySet()) {
+        for (final net.shibboleth.idp.cas.attribute.Attribute casAttr : ticketResponse.getAttributes()) {
             final Attribute attribute = newSAMLObject(Attribute.class, Attribute.DEFAULT_ELEMENT_NAME);
-            attribute.setAttributeName(attrName);
+            attribute.setAttributeName(casAttr.getName());
             attribute.setAttributeNamespace(NAMESPACE);
-            for (final String value : ticketResponse.getAttributes().get(attrName)) {
+            for (final String value : casAttr.getValues()) {
                 attribute.getAttributeValues().add(newAttributeValue(value));
             }
             attrStatement.getAttributes().add(attribute);
diff --git a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java
index 339cfd8..a82acf2 100644
--- a/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java
+++ b/idp-cas-impl/src/main/java/net/shibboleth/idp/cas/flow/impl/PrepareTicketValidationResponseAction.java
@@ -17,26 +17,47 @@
 
 package net.shibboleth.idp.cas.flow.impl;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
+import net.shibboleth.idp.attribute.AttributeEncodingException;
 import net.shibboleth.idp.attribute.IdPAttribute;
-import net.shibboleth.idp.attribute.IdPAttributeValue;
 import net.shibboleth.idp.attribute.context.AttributeContext;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoder;
+import net.shibboleth.idp.attribute.transcoding.AttributeTranscoderRegistry;
+import net.shibboleth.idp.attribute.transcoding.TranscoderSupport;
+import net.shibboleth.idp.attribute.transcoding.TranscodingRule;
+import net.shibboleth.idp.cas.attribute.Attribute;
+import net.shibboleth.idp.cas.attribute.transcoding.impl.CASStringAttributeTranscoder;
 import net.shibboleth.idp.cas.config.impl.ConfigLookupFunction;
 import net.shibboleth.idp.cas.config.impl.ValidateConfiguration;
 import net.shibboleth.idp.cas.protocol.ProtocolError;
 import net.shibboleth.idp.cas.protocol.TicketValidationRequest;
 import net.shibboleth.idp.cas.protocol.TicketValidationResponse;
 import net.shibboleth.idp.cas.ticket.TicketPrincipalLookupFunction;
+import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.idp.profile.context.RelyingPartyContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.Live;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+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;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+import net.shibboleth.utilities.java.support.service.ReloadableService;
+import net.shibboleth.utilities.java.support.service.ServiceableComponent;
+
 import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.springframework.webflow.execution.Event;
-import org.springframework.webflow.execution.RequestContext;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Collections2;
 
 /**
  * Prepares {@link TicketValidationResponse} for use in CAS protocol response views. Possible outcomes:
@@ -54,25 +75,58 @@ public class PrepareTicketValidationResponseAction extends
     @Nonnull private final Logger log = LoggerFactory.getLogger(PrepareTicketValidationResponseAction.class);
 
     /** Function used to retrieve AttributeContext. */
-    @Nonnull
-    private Function<ProfileRequestContext,AttributeContext> attributeContextFunction =
-            new ChildContextLookup<>(AttributeContext.class, true).compose(
-                    new ChildContextLookup<>(RelyingPartyContext.class));
+    @Nonnull private Function<ProfileRequestContext,AttributeContext> attributeContextFunction;
 
     /** Function used to retrieve subject principal. */
-    @Nonnull private Function<ProfileRequestContext,String> principalLookupFunction =
-            new TicketPrincipalLookupFunction();
+    @Nonnull private Function<ProfileRequestContext,String> principalLookupFunction;
 
     /** Profile configuration lookup function. */
-    @Nonnull private final ConfigLookupFunction<ValidateConfiguration> configLookupFunction =
-            new ConfigLookupFunction<>(ValidateConfiguration.class);
-
+    @Nonnull private final ConfigLookupFunction<ValidateConfiguration> configLookupFunction;
+    
+    /** Transcoder registry service object. */
+    @NonnullAfterInit private ReloadableService<AttributeTranscoderRegistry> transcoderRegistry;
+    
+    /** Fallback rule that does a simple/default encode. */
+    @NonnullAfterInit private TranscodingRule defaultTranscodingRule;
+
+    /** Constructor. */
+    public PrepareTicketValidationResponseAction() {
+        attributeContextFunction =
+                new ChildContextLookup<>(AttributeContext.class, true).compose(
+                        new ChildContextLookup<>(RelyingPartyContext.class));
+        principalLookupFunction = new TicketPrincipalLookupFunction();
+        configLookupFunction = new ConfigLookupFunction<>(ValidateConfiguration.class);
+    }
 
-    @Nonnull
+    /**
+     * Sets the registry of transcoding rules to apply to encode attributes.
+     * 
+     * @param registry registry service interface
+     */
+    public void setTranscoderRegistry(@Nonnull final ReloadableService<AttributeTranscoderRegistry> registry) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        
+        transcoderRegistry = Constraint.isNotNull(registry, "AttributeTranscoderRegistry cannot be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (transcoderRegistry == null) {
+            throw new ComponentInitializationException("AttributeTranscoderRegistry cannot be null");
+        }
+        
+        final AttributeTranscoder transcoder = new CASStringAttributeTranscoder();
+        transcoder.initialize();
+        defaultTranscodingRule = new TranscodingRule(
+                Collections.singletonMap(AttributeTranscoderRegistry.PROP_TRANSCODER, transcoder));
+    }
+    
+    /** {@inheritDoc} */
     @Override
-    protected Event doExecute(
-            final @Nonnull RequestContext springRequestContext,
-            final @Nonnull ProfileRequestContext profileRequestContext) {
+    protected void doExecute(final @Nonnull ProfileRequestContext profileRequestContext) {
 
         final AttributeContext ac = attributeContextFunction.apply(profileRequestContext);
         if (ac == null) {
@@ -105,12 +159,68 @@ public class PrepareTicketValidationResponseAction extends
 
         final TicketValidationResponse response = getCASResponse(profileRequestContext);
         response.setUserName(principal);
-        for (final IdPAttribute attribute : ac.getIdPAttributes().values()) {
-            log.debug("Processing {}", attribute);
-            for (final IdPAttributeValue<?> value : attribute.getValues()) {
-                response.addAttribute(attribute.getId(), value.getValue().toString());
+        
+        final Collection<IdPAttribute> inputAttributes = ac.getIdPAttributes().values();
+        final ArrayList<Attribute> encodedAttributes = new ArrayList<>(inputAttributes.size());
+        
+        ServiceableComponent<AttributeTranscoderRegistry> component = null;
+        try {
+            component = transcoderRegistry.getServiceableComponent();
+            if (component == null) {
+                log.error("{} Attribute transoding service unavailable", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, IdPEventIds.UNABLE_ENCODE_ATTRIBUTE);
+                return;
+            }
+            for (final IdPAttribute attribute : Collections2.filter(inputAttributes, Predicates.notNull())) {
+                encodeAttribute(component.getComponent(), profileRequestContext, attribute, encodedAttributes);
+            }
+        } finally {
+            if (null != component) {
+                component.unpinComponent();
+            }
+        }
+        
+        encodedAttributes.forEach(a -> response.addAttribute(a));
+    }
+
+    /**
+     * Access the registry of transcoding rules to transform the input attribute into a target type.
+     * 
+     * @param registry  registry of transcoding rules
+     * @param profileRequestContext current profile request context
+     * @param attribute input attribute
+     * @param results collection to add results to
+     * 
+     * @return number of results added
+     */
+    protected int encodeAttribute(@Nonnull final AttributeTranscoderRegistry registry,
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull final IdPAttribute attribute,
+            @Nonnull @NonnullElements @Live final Collection<Attribute> results) {
+        
+        Collection<TranscodingRule> transcodingRules = registry.getTranscodingRules(attribute, Attribute.class);
+        if (transcodingRules.isEmpty()) {
+            log.debug("{} Attribute {} does not have any transcoding rules, applying default", getLogPrefix(),
+                    attribute.getId());
+            transcodingRules = Collections.singletonList(defaultTranscodingRule);
+        }
+        
+        int count = 0;
+        
+        for (final TranscodingRule rules : transcodingRules) {
+            try {
+                final AttributeTranscoder<Attribute> transcoder = TranscoderSupport.<Attribute>getTranscoder(rules);
+                final Attribute encodedAttribute =
+                        transcoder.encode(profileRequestContext, attribute, Attribute.class, rules);
+                if (encodedAttribute != null) {
+                    results.add(encodedAttribute);
+                    count++;
+                }
+            } catch (final AttributeEncodingException e) {
+                log.debug("{} Unable to encode attribute {}", getLogPrefix(), attribute.getId(), e);
             }
         }
-        return null;
+        
+        return count;
     }
-}
+    
+}
\ No newline at end of file
diff --git a/idp-conf/src/main/resources/system/flows/cas/validate-abstract-beans.xml b/idp-conf/src/main/resources/system/flows/cas/validate-abstract-beans.xml
index 53f4d17..a4a7c62 100644
--- a/idp-conf/src/main/resources/system/flows/cas/validate-abstract-beans.xml
+++ b/idp-conf/src/main/resources/system/flows/cas/validate-abstract-beans.xml
@@ -38,7 +38,8 @@
           c:ticketService="#{getObject('shibboleth.CASTicketService') ?: getObject('shibboleth.DefaultCASTicketService')}" />
 
     <bean id="PrepareTicketValidationResponse"
-          class="net.shibboleth.idp.cas.flow.impl.PrepareTicketValidationResponseAction" />
+          class="net.shibboleth.idp.cas.flow.impl.PrepareTicketValidationResponseAction"
+          p:transcoderRegistry-ref="shibboleth.AttributeRegistryService" />
 
     <bean id="UpdateIdPSessionWithSPSession"
           class="net.shibboleth.idp.cas.flow.impl.UpdateIdPSessionWithSPSessionAction"
diff --git a/idp-conf/src/main/resources/system/views/cas/validateSuccess.vm b/idp-conf/src/main/resources/system/views/cas/validateSuccess.vm
index 0877b2c..5a9b0a4 100644
--- a/idp-conf/src/main/resources/system/views/cas/validateSuccess.vm
+++ b/idp-conf/src/main/resources/system/views/cas/validateSuccess.vm
@@ -4,11 +4,11 @@
     #if($ticketValidationResponse.pgtIou)
       <cas:proxyGrantingTicket>$escapeUtils.escapeXml($ticketValidationResponse.pgtIou)</cas:proxyGrantingTicket>
     #end
-    #if($ticketValidationResponse.attributes.size() > 0)
+    #if(!$ticketValidationResponse.attributes.isEmpty())
       <cas:attributes>
-        #foreach($attr in $ticketValidationResponse.attributes.keySet())
-          #foreach($value in $ticketValidationResponse.attributes.get($attr))
-            <cas:$escapeUtils.escapeXml($attr)>$escapeUtils.escapeXml($value)</cas:$escapeUtils.escapeXml($attr)>
+        #foreach($attr in $ticketValidationResponse.attributes)
+          #foreach($value in $$attr.values)
+            <cas:$escapeUtils.escapeXml($attr.name)>$escapeUtils.escapeXml($value)</cas:$escapeUtils.escapeXml($attr.name)>
           #end
         #end
       </cas:attributes>

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


More information about the commits mailing list