View Javadoc

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