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