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.filtering.provider;
18  
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.concurrent.locks.Lock;
27  
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.context.ApplicationContext;
31  
32  import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
33  import edu.internet2.middleware.shibboleth.common.attribute.filtering.AttributeFilteringEngine;
34  import edu.internet2.middleware.shibboleth.common.attribute.filtering.AttributeFilteringException;
35  import edu.internet2.middleware.shibboleth.common.config.BaseReloadableService;
36  import edu.internet2.middleware.shibboleth.common.profile.provider.SAMLProfileRequestContext;
37  import edu.internet2.middleware.shibboleth.common.service.ServiceException;
38  
39  /**
40   * Implementation of {@link AttributeFilteringEngine}.
41   */
42  public class ShibbolethAttributeFilteringEngine extends BaseReloadableService implements
43          AttributeFilteringEngine<SAMLProfileRequestContext> {
44  
45      /** Class logger. */
46      private final Logger log = LoggerFactory.getLogger(ShibbolethAttributeFilteringEngine.class);
47  
48      /** List of unmodifiable loaded filter policies. */
49      private List<AttributeFilterPolicy> filterPolicies;
50  
51      /** Constructor. */
52      public ShibbolethAttributeFilteringEngine() {
53          super();
54          filterPolicies = new ArrayList<AttributeFilterPolicy>();
55      }
56  
57      /**
58       * Gets the filter policies active for this engine.
59       * 
60       * @return filter policies active for this engine
61       */
62      public List<AttributeFilterPolicy> getFilterPolicies() {
63          return filterPolicies;
64      }
65  
66      /** {@inheritDoc} */
67      public Map<String, BaseAttribute> filterAttributes(Map<String, BaseAttribute> attributes,
68              SAMLProfileRequestContext context) throws AttributeFilteringException {
69  
70          log.debug(getId() + " filtering {} attributes for principal {}", attributes.size(), context.getPrincipalName());
71  
72          if (attributes.size() == 0) {
73              return new HashMap<String, BaseAttribute>();
74          }
75  
76          if (getFilterPolicies() == null) {
77              log.debug("No filter policies were loaded in {}, filtering out all attributes for {}", getId(), context
78                      .getPrincipalName());
79              return new HashMap<String, BaseAttribute>();
80          }
81  
82          ShibbolethFilteringContext filterContext = new ShibbolethFilteringContext(attributes, context);
83          Lock readLock = getReadWriteLock().readLock();
84          readLock.lock();
85          for (AttributeFilterPolicy filterPolicy : filterPolicies) {
86              filterAttributes(filterContext, filterPolicy);
87              runDenyRules(filterContext);
88          }
89          readLock.unlock();
90  
91          Iterator<Entry<String, BaseAttribute>> attributeEntryItr = attributes.entrySet().iterator();
92          Entry<String, BaseAttribute> attributeEntry;
93          BaseAttribute attribute;
94          Collection retainedValues;
95          while (attributeEntryItr.hasNext()) {
96              attributeEntry = attributeEntryItr.next();
97              attribute = attributeEntry.getValue();
98              retainedValues = filterContext.getRetainedValues(attribute.getId(), false);
99              attribute.getValues().retainAll(retainedValues);
100             if (attribute.getValues().size() == 0) {
101                 log.debug("Removing attribute from return set, no more values: {}", attribute.getId());
102                 attributeEntryItr.remove();
103             }
104         }
105 
106         log.debug("Filtered attributes for principal {}.  The following attributes remain: {}", context
107                 .getPrincipalName(), attributes.keySet());
108         return attributes;
109     }
110 
111     /**
112      * Evaluates the given policy's requirement and, if the requirement is met, filters the attributes according to the
113      * policy.
114      * 
115      * @param filterContext current filtering context
116      * @param filterPolicy current filter policy
117      * 
118      * @throws FilterProcessingException thrown if the given policy can be evaluated
119      */
120     protected void filterAttributes(ShibbolethFilteringContext filterContext, AttributeFilterPolicy filterPolicy)
121             throws FilterProcessingException {
122         log.debug("Evaluating if filter policy {} is active for principal {}", filterPolicy.getPolicyId(),
123                 filterContext.getAttributeRequestContext().getPrincipalName());
124         MatchFunctor policyRequirement = filterPolicy.getPolicyRequirementRule();
125         if (policyRequirement == null || !policyRequirement.evaluatePolicyRequirement(filterContext)) {
126             log.debug("Filter policy {} is not active for principal {}", filterPolicy.getPolicyId(), filterContext
127                     .getAttributeRequestContext().getPrincipalName());
128             return;
129         }
130 
131         log.debug("Filter policy {} is active for principal {}", filterPolicy.getPolicyId(), filterContext
132                 .getAttributeRequestContext().getPrincipalName());
133         for (AttributeRule attributeRule : filterPolicy.getAttributeRules()) {
134             filterAttributes(filterContext, attributeRule);
135         }
136     }
137 
138     /**
139      * Evaluates the given attribute rule. If the attribute rule contains a permit value rule then that rule is
140      * evaluated against the unfiltered attributes and those values that meet the rule are moved into the filter
141      * contexts retained value set. If the attribute rule contains a deny value rule that rule is registered with the
142      * filter context so that it may be evaluated after all the permit value rules have run.
143      * 
144      * @param filterContext current filtering context
145      * @param attributeRule current attribute rule
146      * 
147      * @throws FilterProcessingException thrown if the given attribute rule can be evaluated
148      */
149     protected void filterAttributes(ShibbolethFilteringContext filterContext, AttributeRule attributeRule)
150             throws FilterProcessingException {
151         String attributeId = attributeRule.getAttributeId();
152         Collection attributeValues = filterContext.getRetainedValues(attributeId, false);
153 
154         MatchFunctor permitRule = attributeRule.getPermitValueRule();
155         if (permitRule != null) {
156             log.debug("Processing permit value rule for attribute {} for principal {}", attributeId, filterContext
157                     .getAttributeRequestContext().getPrincipalName());
158             BaseAttribute attribute = filterContext.getUnfilteredAttributes().get(attributeId);
159             if(attribute == null){
160                 return;
161             }
162             
163             Collection unfilteredValues = attribute.getValues();
164             for (Object attributeValue : unfilteredValues) {
165                 if (permitRule.evaluatePermitValue(filterContext, attributeId, attributeValue)) {
166                     attributeValues.add(attributeValue);
167                 } else {
168                     log.trace("The following value for attribute {} does not meet permit value rule: {}", attributeId,
169                             attributeValue.toString());
170                 }
171             }
172         }
173 
174         MatchFunctor denyRule = attributeRule.getDenyValueRule();
175         if (denyRule != null) {
176             log.debug("Registering deny value rule for attribute {} for principal {}", attributeId, filterContext
177                     .getAttributeRequestContext().getPrincipalName());
178             List<MatchFunctor> denyRules = filterContext.getDenyValueRules().get(attributeId);
179 
180             if (denyRules == null) {
181                 denyRules = new ArrayList<MatchFunctor>();
182                 filterContext.getDenyValueRules().put(attributeId, denyRules);
183             }
184 
185             denyRules.add(denyRule);
186         }
187     }
188 
189     /**
190      * Runs the deny rules registered with the filter context upon the retained value set.
191      * 
192      * @param filterContext current filtering context
193      * 
194      * @throws FilterProcessingException thrown if there is a problem evaluating a deny value rule
195      */
196     protected void runDenyRules(ShibbolethFilteringContext filterContext) throws FilterProcessingException {
197         Map<String, List<MatchFunctor>> denyRuleEntries = filterContext.getDenyValueRules();
198         if (denyRuleEntries.isEmpty()) {
199             return;
200         }
201 
202         List<MatchFunctor> denyRules;
203         Collection attributeValues;
204         Object attributeValue;
205         for (Entry<String, List<MatchFunctor>> denyRuleEntry : denyRuleEntries.entrySet()) {
206             denyRules = denyRuleEntry.getValue();
207             attributeValues = filterContext.getRetainedValues(denyRuleEntry.getKey(), false);
208             if (denyRules.isEmpty() || attributeValues.isEmpty()) {
209                 continue;
210             }
211 
212             Iterator<?> attributeValueItr = attributeValues.iterator();
213             for (MatchFunctor denyRule : denyRules) {
214                 while (attributeValueItr.hasNext()) {
215                     attributeValue = attributeValueItr.next();
216                     if (denyRule.evaluateDenyRule(filterContext, denyRuleEntry.getKey(), attributeValue)) {
217                         log.trace("Removing the following value of attribute {} per deny rule: {}", denyRuleEntry
218                                 .getKey(), attributeValue);
219                         attributeValueItr.remove();
220                     }
221                 }
222             }
223         }
224     }
225 
226     /** {@inheritDoc} */
227     protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException {
228         List<AttributeFilterPolicy> oldFilterPolicies = filterPolicies;
229 
230         try {
231             List<AttributeFilterPolicy> newFilterPolicies = new ArrayList<AttributeFilterPolicy>();
232             String[] beanNames = newServiceContext.getBeanNamesForType(AttributeFilterPolicy.class);
233             for (String beanName : beanNames) {
234                 newFilterPolicies.add((AttributeFilterPolicy) newServiceContext.getBean(beanName));
235             }
236             filterPolicies = newFilterPolicies;
237         } catch (Exception e) {
238             filterPolicies = oldFilterPolicies;
239             throw new ServiceException(getId() + " configuration is not valid, retaining old configuration", e);
240         }
241     }
242 }