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