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