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          try{
86              for (AttributeFilterPolicy filterPolicy : filterPolicies) {
87                  filterAttributes(filterContext, filterPolicy);
88                  runDenyRules(filterContext);
89              }
90          }finally{
91              readLock.unlock();
92          }
93  
94          Iterator<Entry<String, BaseAttribute>> attributeEntryItr = attributes.entrySet().iterator();
95          Entry<String, BaseAttribute> attributeEntry;
96          BaseAttribute attribute;
97          Collection retainedValues;
98          while (attributeEntryItr.hasNext()) {
99              attributeEntry = attributeEntryItr.next();
100             attribute = attributeEntry.getValue();
101             retainedValues = filterContext.getRetainedValues(attribute.getId(), false);
102             attribute.getValues().retainAll(retainedValues);
103             if (attribute.getValues().size() == 0) {
104                 log.debug("Removing attribute from return set, no more values: {}", attribute.getId());
105                 attributeEntryItr.remove();
106             }
107         }
108 
109         log.debug("Filtered attributes for principal {}.  The following attributes remain: {}", context
110                 .getPrincipalName(), attributes.keySet());
111         return attributes;
112     }
113 
114     /**
115      * Evaluates the given policy's requirement and, if the requirement is met, filters the attributes according to the
116      * policy.
117      * 
118      * @param filterContext current filtering context
119      * @param filterPolicy current filter policy
120      * 
121      * @throws FilterProcessingException thrown if the given policy can be evaluated
122      */
123     protected void filterAttributes(ShibbolethFilteringContext filterContext, AttributeFilterPolicy filterPolicy)
124             throws FilterProcessingException {
125         log.debug("Evaluating if filter policy {} is active for principal {}", filterPolicy.getPolicyId(),
126                 filterContext.getAttributeRequestContext().getPrincipalName());
127         MatchFunctor policyRequirement = filterPolicy.getPolicyRequirementRule();
128         if (policyRequirement == null || !policyRequirement.evaluatePolicyRequirement(filterContext)) {
129             log.debug("Filter policy {} is not active for principal {}", filterPolicy.getPolicyId(), filterContext
130                     .getAttributeRequestContext().getPrincipalName());
131             return;
132         }
133 
134         log.debug("Filter policy {} is active for principal {}", filterPolicy.getPolicyId(), filterContext
135                 .getAttributeRequestContext().getPrincipalName());
136         for (AttributeRule attributeRule : filterPolicy.getAttributeRules()) {
137             filterAttributes(filterContext, attributeRule);
138         }
139     }
140 
141     /**
142      * Evaluates the given attribute rule. If the attribute rule contains a permit value rule then that rule is
143      * evaluated against the unfiltered attributes and those values that meet the rule are moved into the filter
144      * contexts retained value set. If the attribute rule contains a deny value rule that rule is registered with the
145      * filter context so that it may be evaluated after all the permit value rules have run.
146      * 
147      * @param filterContext current filtering context
148      * @param attributeRule current attribute rule
149      * 
150      * @throws FilterProcessingException thrown if the given attribute rule can be evaluated
151      */
152     protected void filterAttributes(ShibbolethFilteringContext filterContext, AttributeRule attributeRule)
153             throws FilterProcessingException {
154         String attributeId = attributeRule.getAttributeId();
155         Collection attributeValues = filterContext.getRetainedValues(attributeId, false);
156 
157         MatchFunctor permitRule = attributeRule.getPermitValueRule();
158         if (permitRule != null) {
159             log.debug("Processing permit value rule for attribute {} for principal {}", attributeId, filterContext
160                     .getAttributeRequestContext().getPrincipalName());
161             BaseAttribute attribute = filterContext.getUnfilteredAttributes().get(attributeId);
162             if(attribute == null){
163                 return;
164             }
165             
166             Collection unfilteredValues = attribute.getValues();
167             for (Object attributeValue : unfilteredValues) {
168                 if (permitRule.evaluatePermitValue(filterContext, attributeId, attributeValue)) {
169                     attributeValues.add(attributeValue);
170                 } else {
171                     log.trace("The following value for attribute {} does not meet permit value rule: {}", attributeId,
172                             attributeValue.toString());
173                 }
174             }
175         }
176 
177         MatchFunctor denyRule = attributeRule.getDenyValueRule();
178         if (denyRule != null) {
179             log.debug("Registering deny value rule for attribute {} for principal {}", attributeId, filterContext
180                     .getAttributeRequestContext().getPrincipalName());
181             List<MatchFunctor> denyRules = filterContext.getDenyValueRules().get(attributeId);
182 
183             if (denyRules == null) {
184                 denyRules = new ArrayList<MatchFunctor>();
185                 filterContext.getDenyValueRules().put(attributeId, denyRules);
186             }
187 
188             denyRules.add(denyRule);
189         }
190     }
191 
192     /**
193      * Runs the deny rules registered with the filter context upon the retained value set.
194      * 
195      * @param filterContext current filtering context
196      * 
197      * @throws FilterProcessingException thrown if there is a problem evaluating a deny value rule
198      */
199     protected void runDenyRules(ShibbolethFilteringContext filterContext) throws FilterProcessingException {
200         Map<String, List<MatchFunctor>> denyRuleEntries = filterContext.getDenyValueRules();
201         if (denyRuleEntries.isEmpty()) {
202             return;
203         }
204 
205         List<MatchFunctor> denyRules;
206         Collection attributeValues;
207         Object attributeValue;
208         for (Entry<String, List<MatchFunctor>> denyRuleEntry : denyRuleEntries.entrySet()) {
209             denyRules = denyRuleEntry.getValue();
210             attributeValues = filterContext.getRetainedValues(denyRuleEntry.getKey(), false);
211             if (denyRules.isEmpty() || attributeValues.isEmpty()) {
212                 continue;
213             }
214 
215             Iterator<?> attributeValueItr = attributeValues.iterator();
216             for (MatchFunctor denyRule : denyRules) {
217                 while (attributeValueItr.hasNext()) {
218                     attributeValue = attributeValueItr.next();
219                     if (denyRule.evaluateDenyRule(filterContext, denyRuleEntry.getKey(), attributeValue)) {
220                         log.trace("Removing the following value of attribute {} per deny rule: {}", denyRuleEntry
221                                 .getKey(), attributeValue);
222                         attributeValueItr.remove();
223                     }
224                 }
225             }
226         }
227     }
228 
229     /** {@inheritDoc} */
230     protected void onNewContextCreated(ApplicationContext newServiceContext) throws ServiceException {
231         List<AttributeFilterPolicy> oldFilterPolicies = filterPolicies;
232 
233         try {
234             List<AttributeFilterPolicy> newFilterPolicies = new ArrayList<AttributeFilterPolicy>();
235             String[] beanNames = newServiceContext.getBeanNamesForType(AttributeFilterPolicy.class);
236             for (String beanName : beanNames) {
237                 newFilterPolicies.add((AttributeFilterPolicy) newServiceContext.getBean(beanName));
238             }
239             filterPolicies = newFilterPolicies;
240         } catch (Exception e) {
241             filterPolicies = oldFilterPolicies;
242             throw new ServiceException(getId() + " configuration is not valid, retaining old configuration", e);
243         }
244     }
245 }