View Javadoc

1   /*
2    * Licensed to the University Corporation for Advanced Internet Development, 
3    * Inc. (UCAID) under one or more contributor license agreements.  See the 
4    * NOTICE file distributed with this work for additional information regarding
5    * copyright ownership. The UCAID licenses this file to You under the Apache 
6    * License, Version 2.0 (the "License"); you may not use this file except in 
7    * compliance with the License.  You may obtain a copy of the License at
8    *
9    *    http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package edu.internet2.middleware.shibboleth.common.attribute.provider;
19  
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.opensaml.Configuration;
28  import org.opensaml.common.SAMLObjectBuilder;
29  import org.opensaml.common.xml.SAMLConstants;
30  import org.opensaml.saml2.core.Attribute;
31  import org.opensaml.saml2.core.AttributeQuery;
32  import org.opensaml.saml2.core.AttributeStatement;
33  import org.opensaml.saml2.core.NameID;
34  import org.opensaml.saml2.core.RequestAbstractType;
35  import org.opensaml.saml2.core.StatusResponseType;
36  import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
37  import org.opensaml.saml2.metadata.EntityDescriptor;
38  import org.opensaml.xml.XMLObjectBuilderFactory;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  import org.springframework.context.ApplicationContext;
42  
43  import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
44  import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
45  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
46  import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
47  import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2AttributeEncoder;
48  import edu.internet2.middleware.shibboleth.common.attribute.filtering.provider.ShibbolethAttributeFilteringEngine;
49  import edu.internet2.middleware.shibboleth.common.attribute.resolver.provider.ShibbolethAttributeResolver;
50  import edu.internet2.middleware.shibboleth.common.config.BaseService;
51  import edu.internet2.middleware.shibboleth.common.profile.provider.SAMLProfileRequestContext;
52  import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
53  import edu.internet2.middleware.shibboleth.common.service.ServiceException;
54  
55  /**
56   * SAML 2.0 Attribute Authority.
57   */
58  public class ShibbolethSAML2AttributeAuthority extends BaseService implements SAML2AttributeAuthority {
59  
60      /** Class logger. */
61      private final Logger log = LoggerFactory.getLogger(ShibbolethSAML2AttributeAuthority.class);
62  
63      /** For building attribute statements. */
64      private SAMLObjectBuilder<AttributeStatement> statementBuilder;
65  
66      /** Attribute resolver. */
67      private ShibbolethAttributeResolver attributeResolver;
68  
69      /** To determine releasable attributes. */
70      private ShibbolethAttributeFilteringEngine filteringEngine;
71  
72      /**
73       * This creates a new attribute authority.
74       * 
75       * @param resolver The attribute resolver to set
76       */
77      @SuppressWarnings("unchecked")
78      public ShibbolethSAML2AttributeAuthority(ShibbolethAttributeResolver resolver) {
79  
80          XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
81          statementBuilder = (SAMLObjectBuilder<AttributeStatement>) builderFactory
82                  .getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME);
83  
84          attributeResolver = resolver;
85      }
86  
87      /**
88       * Gets the attribute resolver.
89       * 
90       * @return Returns the attributeResolver.
91       */
92      public ShibbolethAttributeResolver getAttributeResolver() {
93          return attributeResolver;
94      }
95  
96      /**
97       * Gets the filtering engine.
98       * 
99       * @return Returns the filteringEngine.
100      */
101     public ShibbolethAttributeFilteringEngine getFilteringEngine() {
102         return filteringEngine;
103     }
104 
105     /**
106      * Sets the attribute filtering engine.
107      * 
108      * @param engine attribute filtering engine
109      */
110     public void setFilteringEngine(ShibbolethAttributeFilteringEngine engine) {
111         filteringEngine = engine;
112     }
113 
114     /** {@inheritDoc} */
115     public AttributeStatement buildAttributeStatement(AttributeQuery query, Collection<BaseAttribute> attributes)
116             throws AttributeEncodingException {
117 
118         Collection<Attribute> encodedAttributes = encodeAttributes(attributes);
119 
120         filterAttributesByValue(query, encodedAttributes);
121 
122         if (!encodedAttributes.isEmpty()) {
123             AttributeStatement statement = statementBuilder.buildObject();
124             List<org.opensaml.saml2.core.Attribute> samlAttributes = statement.getAttributes();
125             samlAttributes.addAll(encodedAttributes);
126             return statement;
127         } else {
128             log.debug("No attributes remained after encoding and filtering by value, no attribute statement built");
129             return null;
130         }
131     }
132 
133     /** {@inheritDoc} */
134     public String getAttributeIDBySAMLAttribute(Attribute attribute) {
135         // TODO Auto-generated method stub
136         return null;
137     }
138 
139     /** {@inheritDoc} */
140     public Attribute getSAMLAttributeByAttributeID(String id) {
141         // TODO Auto-generated method stub
142         return null;
143     }
144 
145     /** {@inheritDoc} */
146     public String getPrincipal(
147             SAMLProfileRequestContext<? extends RequestAbstractType, ? extends StatusResponseType, NameID, ? extends AbstractSAML2ProfileConfiguration> requestContext)
148             throws AttributeRequestException {
149         if (requestContext.getInboundMessageIssuer() == null || requestContext.getSubjectNameIdentifier() == null) {
150             throw new AttributeRequestException(
151                     "Unable to resolve principal, attribute request ID and subject name identifier may not be null");
152         }
153 
154         return attributeResolver.resolvePrincipalName(requestContext);
155     }
156 
157     /** {@inheritDoc} */
158     public Map<String, BaseAttribute> getAttributes(
159             SAMLProfileRequestContext<? extends RequestAbstractType, ? extends StatusResponseType, NameID, ? extends AbstractSAML2ProfileConfiguration> requestContext)
160             throws AttributeRequestException {
161         HashSet<String> requestedAttributes = new HashSet<String>();
162 
163         // get attributes from the message
164         Set<String> queryAttributeIds = getAttributeIds(requestContext.getInboundSAMLMessage());
165         requestedAttributes.addAll(queryAttributeIds);
166 
167         // get attributes from metadata
168         Set<String> metadataAttributeIds = getAttribtueIds(requestContext.getPeerEntityMetadata());
169         requestedAttributes.addAll(metadataAttributeIds);
170 
171         requestContext.setRequestedAttributes(requestedAttributes);
172 
173         // Resolve attributes
174         Map<String, BaseAttribute> attributes = attributeResolver.resolveAttributes(requestContext);
175 
176         // Filter resulting attributes
177         if (filteringEngine != null) {
178             attributes = filteringEngine.filterAttributes(attributes, requestContext);
179         }
180 
181         return attributes;
182     }
183 
184     /**
185      * This encodes the supplied attributes with that attribute's SAML2 encoder.
186      * 
187      * @param attributes the attributes to encode
188      * 
189      * @return the encoded attributes
190      * 
191      * @throws AttributeEncodingException thrown if an attribute could not be encoded
192      */
193     @SuppressWarnings("unchecked")
194     protected Collection<Attribute> encodeAttributes(Collection<BaseAttribute> attributes)
195             throws AttributeEncodingException {
196         Collection<Attribute> encodedAttributes = new ArrayList<Attribute>();
197 
198         boolean attributeEncoded;
199 
200         for (BaseAttribute<?> shibbolethAttribute : attributes) {
201             attributeEncoded = false;
202 
203             if (shibbolethAttribute.getValues() == null || shibbolethAttribute.getValues().size() == 0) {
204                 continue;
205             }
206 
207             Attribute attribute;
208             for (AttributeEncoder encoder : shibbolethAttribute.getEncoders()) {
209                 if (encoder instanceof SAML2AttributeEncoder) {
210                     try {
211                         attribute = (Attribute) encoder.encode(shibbolethAttribute);
212                         if (attribute != null) {
213                             encodedAttributes.add(attribute);
214                             attributeEncoded = true;
215                             log.debug("Encoded attribute {} with encoder of type {}", shibbolethAttribute.getId(),
216                                     encoder.getClass().getName());
217                         }
218                     } catch (AttributeEncodingException e) {
219                         log.warn("unable to encode attribute: " + shibbolethAttribute.getId(), e);
220                     }
221                 }
222             }
223 
224             // if it couldn't be encoded log it
225             if (!attributeEncoded) {
226                 log.debug("Attribute {} was not encoded because no SAML2AttributeEncoder was attached to it.",
227                         shibbolethAttribute.getId());
228             }
229         }
230 
231         return encodedAttributes;
232     }
233 
234     /**
235      * Filters out all but the values, for an attribute, provided in the query, if and only if, the query specifies at
236      * least one value for the attribute. That is to say, if the attribute query does not specify any attribute values
237      * then all values for that attribute are accepted and remain. Because this comparison acts on the marshalled form
238      * the provided attributes will be encoded prior to filtering.
239      * 
240      * @param query the attribute query
241      * @param attributes the attributes to filter
242      */
243     protected void filterAttributesByValue(AttributeQuery query, Collection<Attribute> attributes) {
244         if (query == null) {
245             return;
246         }
247 
248         // TODO not implemented yet
249     }
250 
251     /**
252      * Gets the attribute IDs for those attributes requested in the attribute query.
253      * 
254      * @param samlRequest incomming SAML request
255      * 
256      * @return attribute IDs for those attributes requested in the attribute query
257      */
258     protected Set<String> getAttributeIds(RequestAbstractType samlRequest) {
259         Set<String> queryAttributeIds = new HashSet<String>();
260         if (!(samlRequest instanceof AttributeQuery)) {
261             return queryAttributeIds;
262         }
263 
264         AttributeQuery query = (AttributeQuery) samlRequest;
265         if (query != null) {
266             List<org.opensaml.saml2.core.Attribute> queryAttributes = query.getAttributes();
267             queryAttributeIds = getAttributeIds(queryAttributes);
268             log.debug("query message contains the following attributes: {}", queryAttributeIds);
269         }
270 
271         return queryAttributeIds;
272     }
273 
274     /**
275      * Gets the attribute IDs for those attributes requested in the entity metadata.
276      * 
277      * @param metadata the entity metadata
278      * 
279      * @return attribute IDs for those attributes requested in the entity metadata
280      */
281     protected Set<String> getAttribtueIds(EntityDescriptor metadata) {
282         Set<String> metadataAttributeIds = new HashSet<String>();
283         AttributeAuthorityDescriptor aaDescriptor;
284         if (metadata != null) {
285             aaDescriptor = metadata.getAttributeAuthorityDescriptor(SAMLConstants.SAML20P_NS);
286             if (aaDescriptor != null) {
287                 List<org.opensaml.saml2.core.Attribute> metadataAttributes = aaDescriptor.getAttributes();
288                 metadataAttributeIds = getAttributeIds(metadataAttributes);
289                 log.debug("metadata contains the following attributes: {}", metadataAttributeIds);
290             }
291         }
292 
293         return metadataAttributeIds;
294     }
295 
296     /**
297      * This parses the attribute ids from the supplied list of attributes.
298      * 
299      * @param attributes <code>List</code>
300      * @return <code>Set</code> of attribute ids
301      */
302     protected Set<String> getAttributeIds(List<org.opensaml.saml2.core.Attribute> attributes) {
303         final Set<String> attributeIds = new HashSet<String>();
304         for (org.opensaml.saml2.core.Attribute a : attributes) {
305             String attrId = getAttributeIDBySAMLAttribute(a);
306             if (attrId != null) {
307                 attributeIds.add(attrId);
308             }
309         }
310         return attributeIds;
311     }
312 
313     /** {@inheritDoc} */
314     protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException {
315 
316     }
317 }