View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.authz;
21  
22  
23  import javax.naming.directory.SearchControls;
24  
25  import java.text.ParseException;
26  import java.util.ArrayList;
27  import java.util.Collection;
28  import java.util.Collections;
29  import java.util.HashSet;
30  import java.util.List;
31  import java.util.Set;
32  
33  import org.apache.directory.server.constants.ServerDNConstants;
34  import org.apache.directory.server.core.CoreSession;
35  import org.apache.directory.server.core.DefaultCoreSession;
36  import org.apache.directory.server.core.DirectoryService;
37  import org.apache.directory.server.core.authn.LdapPrincipal;
38  import org.apache.directory.server.core.authz.support.ACDFEngine;
39  import org.apache.directory.server.core.entry.ClonedServerEntry;
40  import org.apache.directory.server.core.entry.ServerAttribute;
41  import org.apache.directory.server.core.entry.ServerEntry;
42  import org.apache.directory.server.core.entry.ServerEntryUtils;
43  import org.apache.directory.server.core.filtering.EntryFilter;
44  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
45  import org.apache.directory.server.core.interceptor.BaseInterceptor;
46  import org.apache.directory.server.core.interceptor.InterceptorChain;
47  import org.apache.directory.server.core.interceptor.NextInterceptor;
48  import org.apache.directory.server.core.interceptor.context.AddOperationContext;
49  import org.apache.directory.server.core.interceptor.context.CompareOperationContext;
50  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
51  import org.apache.directory.server.core.interceptor.context.EntryOperationContext;
52  import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext;
53  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
54  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
55  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
56  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
57  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
58  import org.apache.directory.server.core.interceptor.context.OperationContext;
59  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
60  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
61  import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
62  import org.apache.directory.server.core.partition.ByPassConstants;
63  import org.apache.directory.server.core.subtree.SubentryInterceptor;
64  import org.apache.directory.server.schema.ConcreteNameComponentNormalizer;
65  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
66  import org.apache.directory.server.schema.registries.OidRegistry;
67  import org.apache.directory.server.schema.registries.Registries;
68  import org.apache.directory.shared.ldap.aci.ACIItem;
69  import org.apache.directory.shared.ldap.aci.ACIItemParser;
70  import org.apache.directory.shared.ldap.aci.ACITuple;
71  import org.apache.directory.shared.ldap.aci.MicroOperation;
72  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
73  import org.apache.directory.shared.ldap.constants.SchemaConstants;
74  import org.apache.directory.shared.ldap.entry.EntryAttribute;
75  import org.apache.directory.shared.ldap.entry.Modification;
76  import org.apache.directory.shared.ldap.entry.Value;
77  import org.apache.directory.shared.ldap.exception.LdapNamingException;
78  import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
79  import org.apache.directory.shared.ldap.message.ResultCodeEnum;
80  import org.apache.directory.shared.ldap.name.LdapDN;
81  import org.apache.directory.shared.ldap.schema.AttributeType;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  
85  
86  /**
87   * An ACI based authorization service.
88   *
89   * @org.apache.xbean.XBean
90   *
91   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
92   * @version $Rev: 686082 $
93   */
94  public class AciAuthorizationInterceptor extends BaseInterceptor
95  {
96      /** the logger for this class */
97      private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class );
98  
99      /**
100      * the multivalued op attr used to track the perscriptive access control
101      * subentries that apply to an entry.
102      */
103     private static final String AC_SUBENTRY_ATTR = "accessControlSubentries";
104 
105     private static final Collection<MicroOperation> ADD_PERMS;
106     private static final Collection<MicroOperation> READ_PERMS;
107     private static final Collection<MicroOperation> COMPARE_PERMS;
108     private static final Collection<MicroOperation> SEARCH_ENTRY_PERMS;
109     private static final Collection<MicroOperation> SEARCH_ATTRVAL_PERMS;
110     private static final Collection<MicroOperation> REMOVE_PERMS;
111     private static final Collection<MicroOperation> MATCHEDNAME_PERMS;
112     private static final Collection<MicroOperation> BROWSE_PERMS;
113     private static final Collection<MicroOperation> LOOKUP_PERMS;
114     private static final Collection<MicroOperation> REPLACE_PERMS;
115     private static final Collection<MicroOperation> RENAME_PERMS;
116     private static final Collection<MicroOperation> EXPORT_PERMS;
117     private static final Collection<MicroOperation> IMPORT_PERMS;
118     private static final Collection<MicroOperation> MOVERENAME_PERMS;
119 
120     static
121     {
122         Set<MicroOperation> set = new HashSet<MicroOperation>( 2 );
123         set.add( MicroOperation.BROWSE );
124         set.add( MicroOperation.RETURN_DN );
125         SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
126 
127         set = new HashSet<MicroOperation>( 2 );
128         set.add( MicroOperation.READ );
129         set.add( MicroOperation.BROWSE );
130         LOOKUP_PERMS = Collections.unmodifiableCollection( set );
131 
132         set = new HashSet<MicroOperation>( 2 );
133         set.add( MicroOperation.ADD );
134         set.add( MicroOperation.REMOVE );
135         REPLACE_PERMS = Collections.unmodifiableCollection( set );
136 
137         set = new HashSet<MicroOperation>( 2 );
138         set.add( MicroOperation.EXPORT );
139         set.add( MicroOperation.RENAME );
140         MOVERENAME_PERMS = Collections.unmodifiableCollection( set );
141 
142         SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
143         ADD_PERMS = Collections.singleton( MicroOperation.ADD );
144         READ_PERMS = Collections.singleton( MicroOperation.READ );
145         COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE );
146         REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE );
147         MATCHEDNAME_PERMS = Collections.singleton( MicroOperation.DISCLOSE_ON_ERROR );
148         BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE );
149         RENAME_PERMS = Collections.singleton( MicroOperation.RENAME );
150         EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT );
151         IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT );
152     }
153 
154     /** a tupleCache that responds to add, delete, and modify attempts */
155     private TupleCache tupleCache;
156     
157     /** a groupCache that responds to add, delete, and modify attempts */
158     private GroupCache groupCache;
159     
160     /** a normalizing ACIItem parser */
161     private ACIItemParser aciParser;
162     
163     /** use and instance of the ACDF engine */
164     private ACDFEngine engine;
165     
166     /** interceptor chain */
167     private InterceptorChain chain;
168     
169     /** Global registries */
170     private Registries registries;
171     
172     /** attribute type registry */
173     private AttributeTypeRegistry atRegistry;
174     
175     /** whether or not this interceptor is activated */
176     private boolean enabled;
177     
178     /** the system wide subschemaSubentryDn */
179     private String subschemaSubentryDn;
180 
181     private AttributeType objectClassType;
182     private AttributeType acSubentryType;
183 
184     private String subentryOid;
185 
186     /** A storage for the entryACI attributeType */
187     private AttributeType entryAciType;
188 
189     /** the subentry ACI attribute type */
190     private AttributeType subentryAciType;
191     
192     public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
193 
194     /**
195      * Initializes this interceptor based service by getting a handle on the nexus, setting up
196      * the tupe and group membership caches and the ACIItem parser and the ACDF engine.
197      *
198      * @param directoryService the directory service core
199      * @throws Exception if there are problems during initialization
200      */
201     public void init( DirectoryService directoryService ) throws Exception
202     {
203         super.init( directoryService );
204 
205         LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
206         adminDn.normalize( directoryService.getRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
207         CoreSession adminSession = new DefaultCoreSession( 
208             new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
209 
210         tupleCache = new TupleCache( adminSession );
211         groupCache = new GroupCache( adminSession );
212         registries = directoryService.getRegistries();
213         atRegistry = registries.getAttributeTypeRegistry();
214         OidRegistry oidRegistry = registries.getOidRegistry();
215         
216         // look up some constant information
217         String objectClassOid = oidRegistry.getOid( SchemaConstants.OBJECT_CLASS_AT );
218         subentryOid = oidRegistry.getOid( SchemaConstants.SUBENTRY_OC );
219         String acSubentryOid = oidRegistry.getOid( AC_SUBENTRY_ATTR );
220         objectClassType = atRegistry.lookup( objectClassOid );
221         acSubentryType = atRegistry.lookup( acSubentryOid );
222         entryAciType = atRegistry.lookup( SchemaConstants.ENTRY_ACI_AT_OID ); 
223         subentryAciType = atRegistry.lookup( SchemaConstants.SUBENTRY_ACI_AT_OID );
224         
225         aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( atRegistry, oidRegistry ), atRegistry.getNormalizerMapping() );
226         engine = new ACDFEngine( registries.getOidRegistry(), atRegistry );
227         chain = directoryService.getInterceptorChain();
228         enabled = directoryService.isAccessControlEnabled();
229 
230         // stuff for dealing with subentries (garbage for now)
231         Value<?> subschemaSubentry = 
232             directoryService.getPartitionNexus().getRootDSE( null ).
233                 get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get();
234         LdapDN subschemaSubentryDnName = new LdapDN( (String)(subschemaSubentry.get()) );
235         subschemaSubentryDnName.normalize( atRegistry.getNormalizerMapping() );
236         subschemaSubentryDn = subschemaSubentryDnName.toNormName();
237     }
238 
239 
240     private void protectCriticalEntries( LdapDN dn ) throws Exception
241     {
242         LdapDN principalDn = getPrincipal().getJndiName();
243 
244         if ( dn.isEmpty() )
245         {
246             String msg = "The rootDSE cannot be deleted, moved or renamed!";
247             LOG.error( msg );
248             throw new LdapNoPermissionException( msg );
249         }
250 
251         if ( isTheAdministrator( dn ) )
252         {
253             String msg = "User '" + principalDn.getUpName();
254             msg += "' does not have permission to move or rename the admin";
255             msg += " account.  No one not even the admin can del, move or";
256             msg += " rename " + dn.getUpName() + "!";
257             LOG.error( msg );
258             throw new LdapNoPermissionException( msg );
259         }
260     }
261 
262 
263     /**
264      * Adds perscriptiveACI tuples to a collection of tuples by accessing the
265      * tupleCache.  The tuple cache is accessed for each A/C subentry
266      * associated with the protected entry.  Note that subentries are handled
267      * differently: their parent, the administrative entry is accessed to
268      * determine the perscriptiveACIs effecting the AP and hence the subentry
269      * which is considered to be in the same context.
270      *
271      * @param tuples the collection of tuples to add to
272      * @param dn the normalized distinguished name of the protected entry
273      * @param entry the target entry that access to is being controled
274      * @throws Exception if there are problems accessing attribute values
275      * @param proxy the partition nexus proxy object
276      */
277     private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, LdapDN dn,
278         ServerEntry entry ) throws Exception
279     {
280         EntryAttribute oc = null;
281         
282         if ( entry instanceof ClonedServerEntry )
283         {
284             oc = ((ClonedServerEntry)entry).getOriginalEntry().get( objectClassType );
285         }
286         else
287         {
288             oc = entry.get( objectClassType );
289         }
290         
291         /*
292          * If the protected entry is a subentry, then the entry being evaluated
293          * for perscriptiveACIs is in fact the administrative entry.  By
294          * substituting the administrative entry for the actual subentry the
295          * code below this "if" statement correctly evaluates the effects of
296          * perscriptiveACI on the subentry.  Basically subentries are considered
297          * to be in the same naming context as their access point so the subentries
298          * effecting their parent entry applies to them as well.
299          */
300         if ( oc.contains( SchemaConstants.SUBENTRY_OC ) || oc.contains( subentryOid ) )
301         {
302             LdapDN parentDn = ( LdapDN ) dn.clone();
303             parentDn.remove( dn.size() - 1 );
304             entry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS );
305         }
306 
307         EntryAttribute subentries = entry.get( acSubentryType );
308         
309         if ( subentries == null )
310         {
311             return;
312         }
313         
314         for ( Value<?> value:subentries )
315         {
316             String subentryDn = ( String ) value.get();
317             tuples.addAll( tupleCache.getACITuples( subentryDn ) );
318         }
319     }
320 
321 
322     /**
323      * Adds the set of entryACI tuples to a collection of tuples.  The entryACI
324      * is parsed and tuples are generated on they fly then added to the collection.
325      *
326      * @param tuples the collection of tuples to add to
327      * @param entry the target entry that access to is being regulated
328      * @throws Exception if there are problems accessing attribute values
329      */
330     private void addEntryAciTuples( Collection<ACITuple> tuples, ServerEntry entry ) throws Exception
331     {
332         EntryAttribute entryAci = entry.get( entryAciType );
333         
334         if ( entryAci == null )
335         {
336             return;
337         }
338 
339         for ( Value<?> value:entryAci )
340         {
341             String aciString = ( String ) value.get();
342             ACIItem item;
343 
344             try
345             {
346                 item = aciParser.parse( aciString );
347             }
348             catch ( ParseException e )
349             {
350                 String msg = "failed to parse entryACI: " + aciString;
351                 LOG.error( msg, e );
352                 throw new LdapNamingException( msg, ResultCodeEnum.OPERATIONS_ERROR );
353             }
354 
355             tuples.addAll( item.toTuples() );
356         }
357     }
358 
359 
360     /**
361      * Adds the set of subentryACI tuples to a collection of tuples.  The subentryACI
362      * is parsed and tuples are generated on the fly then added to the collection.
363      *
364      * @param tuples the collection of tuples to add to
365      * @param dn the normalized distinguished name of the protected entry
366      * @param entry the target entry that access to is being regulated
367      * @throws Exception if there are problems accessing attribute values
368      * @param proxy the partition nexus proxy object
369      */
370     private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, LdapDN dn, ServerEntry entry )
371         throws Exception
372     {
373         // only perform this for subentries
374         if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) )
375         {
376             return;
377         }
378 
379         // get the parent or administrative entry for this subentry since it
380         // will contain the subentryACI attributes that effect subentries
381         LdapDN parentDn = ( LdapDN ) dn.clone();
382         parentDn.remove( dn.size() - 1 );
383         ServerEntry administrativeEntry = opContext.lookup( parentDn, ByPassConstants.LOOKUP_BYPASS ).getOriginalEntry();
384         
385         EntryAttribute subentryAci = administrativeEntry.get( subentryAciType );
386 
387         if ( subentryAci == null )
388         {
389             return;
390         }
391 
392         for ( Value<?> value:subentryAci )
393         {
394             String aciString = ( String ) value.get();
395             ACIItem item;
396 
397             try
398             {
399                 item = aciParser.parse( aciString );
400             }
401             catch ( ParseException e )
402             {
403                 String msg = "failed to parse subentryACI: " + aciString;
404                 LOG.error( msg, e );
405                 throw new LdapNamingException( msg, ResultCodeEnum.OPERATIONS_ERROR );
406             }
407 
408             tuples.addAll( item.toTuples() );
409         }
410     }
411 
412 
413     /* -------------------------------------------------------------------------------
414      * Within every access controled interceptor method we must retrieve the ACITuple
415      * set for all the perscriptiveACIs that apply to the candidate, the target entry
416      * operated upon.  This ACITuple set is gotten from the TupleCache by looking up
417      * the subentries referenced by the accessControlSubentries operational attribute
418      * within the target entry.
419      *
420      * Then the entry is inspected for an entryACI.  This is not done for the add op
421      * since it could introduce a security breech.  So for non-add ops if present a
422      * set of ACITuples are generated for all the entryACIs within the entry.  This
423      * set is combined with the ACITuples cached for the perscriptiveACI affecting
424      * the target entry.  If the entry is a subentry the ACIs are also processed for
425      * the subentry to generate more ACITuples.  This subentry TupleACI set is joined
426      * with the entry and perscriptive ACI.
427      *
428      * The union of ACITuples are fed into the engine along with other parameters
429      * to decide whether a permission is granted or rejected for the specific
430      * operation.
431      * -------------------------------------------------------------------------------
432      */
433 
434     public void add( NextInterceptor next, AddOperationContext addContext ) throws Exception
435     {
436         // Access the principal requesting the operation, and bypass checks if it is the admin
437         LdapPrincipal principal = addContext.getSession().getEffectivePrincipal();
438         LdapDN principalDn = principal.getJndiName();
439         
440         ServerEntry serverEntry = addContext.getEntry(); 
441         //Attributes entry = ServerEntryUtils.toAttributesImpl( serverEntry );
442 
443         LdapDN name = addContext.getDn();
444 
445         // bypass authz code if we are disabled
446         if ( !enabled )
447         {
448             next.add( addContext );
449             return;
450         }
451 
452         // bypass authz code but manage caches if operation is performed by the admin
453         if ( isPrincipalAnAdministrator( principalDn ) )
454         {
455             next.add( addContext );
456             tupleCache.subentryAdded( name, serverEntry );
457             groupCache.groupAdded( name, serverEntry );
458             return;
459         }
460 
461         // perform checks below here for all non-admin users
462         SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() );
463         ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( name, serverEntry );
464         
465         for ( EntryAttribute attribute:serverEntry )
466         {
467             subentryAttrs.put( attribute );
468         }
469 
470         // Assemble all the information required to make an access control decision
471         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toNormName() );
472         Collection<ACITuple> tuples = new HashSet<ACITuple>();
473 
474         // Build the total collection of tuples to be considered for add rights
475         // NOTE: entryACI are NOT considered in adds (it would be a security breech)
476         addPerscriptiveAciTuples( addContext, tuples, name, subentryAttrs );
477         addSubentryAciTuples( addContext, tuples, name, subentryAttrs );
478 
479         // check if entry scope permission is granted
480         engine.checkPermission( registries, addContext, userGroups, principalDn, principal.getAuthenticationLevel(), name, null, null,
481             ADD_PERMS, tuples, subentryAttrs, null );
482 
483         // now we must check if attribute type and value scope permission is granted
484         for ( EntryAttribute attribute:serverEntry )
485         {
486             for ( Value<?> value:attribute )
487             {
488                 engine.checkPermission( registries, addContext, userGroups, principalDn, 
489                     principal.getAuthenticationLevel(), name, attribute.getUpId(), value, 
490                     ADD_PERMS, tuples, serverEntry, null );
491             }
492         }
493 
494         // if we've gotten this far then access has been granted
495         next.add( addContext );
496 
497         // if the entry added is a subentry or a groupOf[Unique]Names we must
498         // update the ACITuple cache and the groups cache to keep them in sync
499         tupleCache.subentryAdded( name, serverEntry );
500         groupCache.groupAdded( name, serverEntry );
501     }
502 
503 
504     private boolean isTheAdministrator( LdapDN normalizedDn )
505     {
506         return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
507     }
508 
509 
510     public void delete( NextInterceptor next, DeleteOperationContext deleteContext ) throws Exception
511     {
512         LdapDN name = deleteContext.getDn();
513         
514         LdapPrincipal principal = deleteContext.getSession().getEffectivePrincipal();
515         LdapDN principalDn = principal.getJndiName();
516 
517         // bypass authz code if we are disabled
518         if ( ! enabled )
519         {
520             next.delete( deleteContext );
521             return;
522         }
523 
524         ClonedServerEntry entry = deleteContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
525 
526         protectCriticalEntries( name );
527 
528         // bypass authz code but manage caches if operation is performed by the admin
529         if ( isPrincipalAnAdministrator( principalDn ) )
530         {
531             next.delete( deleteContext );
532             tupleCache.subentryDeleted( name, entry );
533             groupCache.groupDeleted( name, entry );
534             return;
535         }
536 
537         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toString() );
538         Collection<ACITuple> tuples = new HashSet<ACITuple>();
539         addPerscriptiveAciTuples( deleteContext, tuples, name, entry.getOriginalEntry() );
540         addEntryAciTuples( tuples, entry );
541         addSubentryAciTuples( deleteContext, tuples, name, entry );
542 
543         engine.checkPermission( registries, deleteContext, userGroups, principalDn, 
544             principal.getAuthenticationLevel(), name, null, null, REMOVE_PERMS, tuples, entry, null );
545 
546         next.delete( deleteContext );
547         tupleCache.subentryDeleted( name, entry );
548         groupCache.groupDeleted( name, entry );
549     }
550 
551 
552     public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
553     {
554         LdapDN name = opContext.getDn();
555 
556         // Access the principal requesting the operation, and bypass checks if it is the admin
557         ClonedServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
558         
559         LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
560         LdapDN principalDn = principal.getJndiName();
561 
562         // bypass authz code if we are disabled
563         if ( !enabled )
564         {
565             next.modify( opContext );
566             return;
567         }
568 
569         List<Modification> mods = opContext.getModItems();
570 
571         // bypass authz code but manage caches if operation is performed by the admin
572         if ( isPrincipalAnAdministrator( principalDn ) )
573         {
574             next.modify( opContext );
575             /**
576              * @TODO: A virtual entry can be created here for not hitting the backend again.
577              */
578             ServerEntry modifiedEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
579             tupleCache.subentryModified( name, mods, modifiedEntry );
580             groupCache.groupModified( name, mods, entry, registries );
581             return;
582         }
583 
584         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toString() );
585         Collection<ACITuple> tuples = new HashSet<ACITuple>();
586         addPerscriptiveAciTuples( opContext, tuples, name, entry.getOriginalEntry() );
587         addEntryAciTuples( tuples, entry );
588         addSubentryAciTuples( opContext, tuples, name, entry );
589 
590         engine.checkPermission( registries, opContext, userGroups, principalDn, 
591             principal.getAuthenticationLevel(), name, null, null, 
592             Collections.singleton( MicroOperation.MODIFY ), tuples, entry, null );
593 
594         Collection<MicroOperation> perms = null;
595         ServerEntry entryView = ( ServerEntry ) entry.clone();
596         
597         for ( Modification mod : mods )
598         {
599             ServerAttribute attr = (ServerAttribute)mod.getAttribute();
600 
601             switch ( mod.getOperation() )
602             {
603                 case ADD_ATTRIBUTE :
604                     perms = ADD_PERMS;
605                 
606                     // If the attribute is being created with an initial value ...
607                     if ( entry.get( attr.getId() ) == null )
608                     {
609                         // ... we also need to check if adding the attribute is permitted
610                         engine.checkPermission( registries, opContext, userGroups, principalDn, principal.getAuthenticationLevel(), name,
611                                 attr.getId(), null, perms, tuples, entry, null );
612                     }
613                     
614                     break;
615 
616                 case REMOVE_ATTRIBUTE :
617                     perms = REMOVE_PERMS;
618                     EntryAttribute entryAttr = entry.get( attr.getId() );
619 
620                     if ( entryAttr != null )
621                     {
622                         // If there is only one value remaining in the attribute ...
623                         if ( entryAttr.size() == 1 )
624                         {
625                             // ... we also need to check if removing the attribute at all is permitted
626                             engine.checkPermission( registries, opContext, userGroups, principalDn, 
627                                 principal.getAuthenticationLevel(), name, attr.getId(), 
628                                 null, perms, tuples, entry, null );
629                         }
630                     }
631                     
632                     break;
633 
634                 case REPLACE_ATTRIBUTE :
635                     perms = REPLACE_PERMS;
636                     break;
637             }
638 
639             /**
640              * Update the entry view as the current modification is applied to the original entry.
641              * This is especially required for handling the MaxValueCount protected item. Number of
642              * values for an attribute after a modification should be known in advance in order to
643              * check permissions for MaxValueCount protected item. So during addition of the first
644              * value of an attribute it can be rejected if the permission denied due the the
645              * MaxValueCount protected item. This is not the perfect implementation as required by
646              * the specification because the system should reject the addition exactly on the right
647              * value of the attribute. However as we do not have that much granularity in our
648              * implementation (we consider an Attribute Addition itself a Micro Operation,
649              * not the individual Value Additions) we just handle this when the first value of an
650              * attribute is being checked for relevant permissions below. 
651              */
652             entryView = ServerEntryUtils.getTargetEntry( mod, entryView, registries );
653             
654             for ( Value<?> value:attr )
655             {                
656                 engine.checkPermission( registries, opContext, userGroups, principalDn, 
657                     principal.getAuthenticationLevel(), name, attr.getId(), value, 
658                     perms, tuples, entry, entryView );
659             }
660         }
661 
662         
663 
664         next.modify( opContext );
665         /**
666          * @TODO: A virtual entry can be created here for not hitting the backend again.
667          */
668         ServerEntry modifiedEntry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
669         tupleCache.subentryModified( name, mods, modifiedEntry );
670         groupCache.groupModified( name, mods, entry, registries );
671     }
672 
673     
674     public boolean hasEntry( NextInterceptor next, EntryOperationContext entryContext ) throws Exception
675     {
676         LdapDN name = entryContext.getDn();
677         if ( ! enabled )
678         {
679             return name.size() == 0 || next.hasEntry( entryContext );
680         }
681         
682         boolean answer = next.hasEntry( entryContext );
683 
684         // no checks on the RootDSE
685         if ( name.size() == 0 )
686         {
687             // No need to go down to the stack, if the dn is empty 
688             // It's the rootDSE, and it exists ! 
689             return answer;
690         }
691         
692         // TODO - eventually replace this with a check on session.isAnAdministrator()
693         LdapPrincipal principal = entryContext.getSession().getEffectivePrincipal();
694         LdapDN principalDn = principal.getJndiName();
695         if ( isPrincipalAnAdministrator( principalDn ) )
696         {
697             return answer;
698         }
699 
700         ClonedServerEntry entry = entryContext.lookup( name, ByPassConstants.HAS_ENTRY_BYPASS );
701         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toNormName() );
702         Collection<ACITuple> tuples = new HashSet<ACITuple>();
703         addPerscriptiveAciTuples( entryContext, tuples, name, entry.getOriginalEntry() );
704         addEntryAciTuples( tuples, entry.getOriginalEntry() );
705         addSubentryAciTuples( entryContext, tuples, name, entry.getOriginalEntry() );
706 
707         // check that we have browse access to the entry
708         engine.checkPermission( registries, entryContext, userGroups, principalDn, 
709             principal.getAuthenticationLevel(), name, null, null,
710             BROWSE_PERMS, tuples, entry.getOriginalEntry(), null );
711 
712         return next.hasEntry( entryContext );
713     }
714 
715 
716     /**
717      * Checks if the READ permissions exist to the entry and to each attribute type and
718      * value.
719      *
720      * @todo not sure if we should hide attribute types/values or throw an exception
721      * instead.  I think we're going to have to use a filter to restrict the return
722      * of attribute types and values instead of throwing an exception.  Lack of read
723      * perms to attributes and their values results in their removal when returning
724      * the entry.
725      *
726      * @param principal the user associated with the call
727      * @param dn the name of the entry being looked up
728      * @param entry the raw entry pulled from the nexus
729      * @throws Exception if undlying access to the DIT fails
730      */
731     private void checkLookupAccess( LookupOperationContext lookupContext, ServerEntry entry ) throws Exception
732     {
733         // no permissions checks on the RootDSE
734         if ( lookupContext.getDn().toString().trim().equals( "" ) )
735         {
736             return;
737         }
738 
739         LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal();
740         LdapDN userName = principal.getJndiName();
741         Set<LdapDN> userGroups = groupCache.getGroups( userName.toNormName() );
742         Collection<ACITuple> tuples = new HashSet<ACITuple>();
743         addPerscriptiveAciTuples( lookupContext, tuples, lookupContext.getDn(), entry );
744         addEntryAciTuples( tuples, entry );
745         addSubentryAciTuples( lookupContext, tuples, lookupContext.getDn(), entry );
746 
747         // check that we have read access to the entry
748         engine.checkPermission( registries, lookupContext, userGroups, userName, principal.getAuthenticationLevel(), 
749             lookupContext.getDn(), null, null,
750             LOOKUP_PERMS, tuples, entry, null );
751 
752         // check that we have read access to every attribute type and value
753         for ( EntryAttribute attribute:entry )
754         {
755             
756             for ( Value<?> value:attribute )
757             {
758                 engine.checkPermission( 
759                     registries, 
760                     lookupContext, 
761                     userGroups, 
762                     userName, 
763                     principal.getAuthenticationLevel(), 
764                     lookupContext.getDn(), 
765                     attribute.getUpId(), 
766                     value, 
767                     READ_PERMS, 
768                     tuples, 
769                     entry, 
770                     null );
771             }
772         }
773     }
774 
775 
776     public ClonedServerEntry lookup( NextInterceptor next, LookupOperationContext lookupContext ) throws Exception
777     {
778         LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal();
779         LdapDN principalDn = principal.getJndiName();
780         
781         if ( !principalDn.isNormalized() )
782         {
783             principalDn.normalize( atRegistry.getNormalizerMapping() );
784         }
785         
786         if ( isPrincipalAnAdministrator( principalDn ) || !enabled )
787         {
788             return next.lookup( lookupContext );
789         }
790 
791         lookupContext.setByPassed( ByPassConstants.LOOKUP_BYPASS );
792         ServerEntry entry = lookupContext.getSession().getDirectoryService()
793             .getOperationManager().lookup( lookupContext );
794 
795         checkLookupAccess( lookupContext, entry );
796         return next.lookup( lookupContext );
797     }
798 
799     
800     public void rename( NextInterceptor next, RenameOperationContext renameContext ) throws Exception
801     {
802         LdapDN name = renameContext.getDn();
803 
804         ClonedServerEntry entry = renameContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
805         
806         LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal();
807         LdapDN principalDn = principal.getJndiName();
808         LdapDN newName = ( LdapDN ) name.clone();
809         newName.remove( name.size() - 1 );
810 
811         newName.add( renameContext.getNewRdn() );
812 
813         // bypass authz code if we are disabled
814         if ( !enabled )
815         {
816             next.rename( renameContext );
817             return;
818         }
819 
820         protectCriticalEntries( name );
821 
822         // bypass authz code but manage caches if operation is performed by the admin
823         if ( isPrincipalAnAdministrator( principalDn ) )
824         {
825             next.rename( renameContext );
826             tupleCache.subentryRenamed( name, newName );
827             
828             // TODO : this method returns a boolean : what should we do with the result ?
829             groupCache.groupRenamed( name, newName );
830 
831             return;
832         }
833 
834         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toString() );
835         Collection<ACITuple> tuples = new HashSet<ACITuple>();
836         addPerscriptiveAciTuples( renameContext, tuples, name, entry.getOriginalEntry() );
837         addEntryAciTuples( tuples, entry );
838         addSubentryAciTuples( renameContext, tuples, name, entry );
839 
840         engine.checkPermission( registries, renameContext, userGroups, principalDn, 
841             principal.getAuthenticationLevel(), name, null, null,
842             RENAME_PERMS, tuples, entry, null );
843 
844         next.rename( renameContext );
845         tupleCache.subentryRenamed( name, newName );
846         groupCache.groupRenamed( name, newName );
847     }
848 
849 
850     public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext moveAndRenameContext )
851         throws Exception
852     {
853         LdapDN oriChildName = moveAndRenameContext.getDn();
854         LdapDN newParentName = moveAndRenameContext.getParent();
855 
856         ClonedServerEntry entry = moveAndRenameContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
857         
858         LdapPrincipal principal = moveAndRenameContext.getSession().getEffectivePrincipal();
859         LdapDN principalDn = principal.getJndiName();
860         LdapDN newName = ( LdapDN ) newParentName.clone();
861         newName.add( moveAndRenameContext.getNewRdn().getUpName() );
862 
863         // bypass authz code if we are disabled
864         if ( !enabled )
865         {
866             next.moveAndRename( moveAndRenameContext );
867             return;
868         }
869 
870         protectCriticalEntries( oriChildName );
871 
872         // bypass authz code but manage caches if operation is performed by the admin
873         if ( isPrincipalAnAdministrator( principalDn ) )
874         {
875             next.moveAndRename( moveAndRenameContext );
876             tupleCache.subentryRenamed( oriChildName, newName );
877             groupCache.groupRenamed( oriChildName, newName );
878             return;
879         }
880 
881         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toString() );
882         Collection<ACITuple> tuples = new HashSet<ACITuple>();
883         addPerscriptiveAciTuples( moveAndRenameContext, tuples, oriChildName, entry.getOriginalEntry() );
884         addEntryAciTuples( tuples, entry );
885         addSubentryAciTuples( moveAndRenameContext, tuples, oriChildName, entry );
886 
887         engine.checkPermission( registries, moveAndRenameContext, userGroups, 
888             principalDn, principal.getAuthenticationLevel(), oriChildName, null,
889             null, MOVERENAME_PERMS, tuples, entry, null );
890 
891         // Get the entry again without operational attributes
892         // because access control subentry operational attributes
893         // will not be valid at the new location.
894         // This will certainly be fixed by the SubentryInterceptor,
895         // but after this service.
896         
897         ClonedServerEntry importedEntry = moveAndRenameContext.lookup( oriChildName, 
898             ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS );
899         
900         // As the target entry does not exist yet and so
901         // its subentry operational attributes are not there,
902         // we need to construct an entry to represent it
903         // at least with minimal requirements which are object class
904         // and access control subentry operational attributes.
905         SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) chain.get( SubentryInterceptor.class.getName() );
906         ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( newName, importedEntry );
907         
908         for ( EntryAttribute attribute:importedEntry )
909         {
910             subentryAttrs.put( attribute );
911         }
912         
913         Collection<ACITuple> destTuples = new HashSet<ACITuple>();
914         // Import permission is only valid for prescriptive ACIs
915         addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newName, subentryAttrs );
916         // Evaluate the target context to see whether it
917         // allows an entry named newName to be imported as a subordinate.
918         engine.checkPermission( registries, moveAndRenameContext, userGroups, principalDn, 
919             principal.getAuthenticationLevel(), newName, null,
920             null, IMPORT_PERMS, destTuples, subentryAttrs, null );
921 
922 
923         next.moveAndRename( moveAndRenameContext );
924         tupleCache.subentryRenamed( oriChildName, newName );
925         groupCache.groupRenamed( oriChildName, newName );
926     }
927 
928 
929     public void move( NextInterceptor next, MoveOperationContext moveContext ) throws Exception
930     {
931         LdapDN oriChildName = moveContext.getDn();
932         LdapDN newParentName = moveContext.getParent();
933         
934         // Access the principal requesting the operation, and bypass checks if it is the admin
935         ClonedServerEntry entry = moveContext.lookup( oriChildName, ByPassConstants.LOOKUP_BYPASS );
936        
937         LdapDN newName = ( LdapDN ) newParentName.clone();
938         newName.add( oriChildName.get( oriChildName.size() - 1 ) );
939         LdapPrincipal principal = moveContext.getSession().getEffectivePrincipal();
940         LdapDN principalDn = principal.getJndiName();
941 
942         // bypass authz code if we are disabled
943         if ( !enabled )
944         {
945             next.move( moveContext );
946             return;
947         }
948 
949         protectCriticalEntries( oriChildName);
950 
951         // bypass authz code but manage caches if operation is performed by the admin
952         if ( isPrincipalAnAdministrator( principalDn ) )
953         {
954             next.move( moveContext );
955             tupleCache.subentryRenamed( oriChildName, newName );
956             groupCache.groupRenamed( oriChildName, newName );
957             return;
958         }
959 
960         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toString() );
961         Collection<ACITuple> tuples = new HashSet<ACITuple>();
962         addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry.getOriginalEntry() );
963         addEntryAciTuples( tuples, entry );
964         addSubentryAciTuples( moveContext, tuples, oriChildName, entry );
965 
966         engine.checkPermission( registries, moveContext, userGroups, principalDn, 
967             principal.getAuthenticationLevel(), oriChildName, null,
968             null, EXPORT_PERMS, tuples, entry, null );
969         
970         // Get the entry again without operational attributes
971         // because access control subentry operational attributes
972         // will not be valid at the new location.
973         // This will certainly be fixed by the SubentryInterceptor,
974         // but after this service.
975         ServerEntry importedEntry = moveContext.lookup( oriChildName, 
976             ByPassConstants.LOOKUP_EXCLUDING_OPR_ATTRS_BYPASS );
977             
978         // As the target entry does not exist yet and so
979         // its subentry operational attributes are not there,
980         // we need to construct an entry to represent it
981         // at least with minimal requirements which are object class
982         // and access control subentry operational attributes.
983         SubentryInterceptor subentryInterceptor = ( SubentryInterceptor ) 
984             chain.get( SubentryInterceptor.class.getName() );
985         ServerEntry subentryAttrs = subentryInterceptor.getSubentryAttributes( newName, importedEntry );
986         
987         for ( EntryAttribute attribute:importedEntry )
988         {
989             subentryAttrs.put( attribute );
990         }
991         
992         Collection<ACITuple> destTuples = new HashSet<ACITuple>();
993         // Import permission is only valid for prescriptive ACIs
994         addPerscriptiveAciTuples( moveContext, destTuples, newName, subentryAttrs );
995         // Evaluate the target context to see whether it
996         // allows an entry named newName to be imported as a subordinate.
997         engine.checkPermission( registries, moveContext, userGroups, principalDn, 
998             principal.getAuthenticationLevel(), newName, null,
999             null, IMPORT_PERMS, destTuples, subentryAttrs, null );
1000 
1001         next.move( moveContext );
1002         tupleCache.subentryRenamed( oriChildName, newName );
1003         groupCache.groupRenamed( oriChildName, newName );
1004     }
1005 
1006     
1007     public EntryFilteringCursor list( NextInterceptor next, ListOperationContext opContext ) throws Exception
1008     {
1009         LdapPrincipal user = opContext.getSession().getEffectivePrincipal();
1010         EntryFilteringCursor cursor = next.list( opContext );
1011         
1012         if ( isPrincipalAnAdministrator( user.getJndiName() ) || !enabled )
1013         {
1014             return cursor;
1015         }
1016         
1017         AuthorizationFilter authzFilter = new AuthorizationFilter();
1018         cursor.addEntryFilter( authzFilter );
1019         return cursor;
1020     }
1021 
1022 
1023     public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext opContext ) throws Exception
1024     {
1025         LdapPrincipal user = opContext.getSession().getEffectivePrincipal();
1026         LdapDN principalDn = user.getJndiName();
1027         EntryFilteringCursor cursor = next.search( opContext );
1028 
1029         boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( opContext.getDn().getNormName() );
1030         SearchControls searchCtls = opContext.getSearchControls();
1031         boolean isRootDSELookup = opContext.getDn().size() == 0 && searchCtls.getSearchScope() == SearchControls.OBJECT_SCOPE;
1032 
1033         if ( isPrincipalAnAdministrator( principalDn ) || !enabled || isRootDSELookup || isSubschemaSubentryLookup )
1034         {
1035             return cursor;
1036         }
1037         
1038         cursor.addEntryFilter( new AuthorizationFilter() );
1039         return cursor;
1040     }
1041 
1042     
1043     public final boolean isPrincipalAnAdministrator( LdapDN principalDn )
1044     {
1045         return groupCache.isPrincipalAnAdministrator( principalDn );
1046     }
1047     
1048 
1049     public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws Exception
1050     {
1051     	LdapDN name = opContext.getDn();
1052     	String oid = opContext.getOid();
1053     	Value<?> value = ( Value<?> ) opContext.getValue();
1054 
1055         ClonedServerEntry entry = opContext.lookup( name, ByPassConstants.LOOKUP_BYPASS );
1056 
1057         LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1058         LdapDN principalDn = principal.getJndiName();
1059 
1060         if ( isPrincipalAnAdministrator( principalDn ) || !enabled )
1061         {
1062             return next.compare( opContext );
1063         }
1064 
1065         Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toNormName() );
1066         Collection<ACITuple> tuples = new HashSet<ACITuple>();
1067         addPerscriptiveAciTuples( opContext, tuples, name, entry.getOriginalEntry() );
1068         addEntryAciTuples( tuples, entry );
1069         addSubentryAciTuples( opContext, tuples, name, entry );
1070 
1071         engine.checkPermission( registries, opContext, userGroups, principalDn, 
1072             principal.getAuthenticationLevel(), name, null, null,
1073             READ_PERMS, tuples, entry, null );
1074         engine.checkPermission( registries, opContext, userGroups, principalDn, 
1075             principal.getAuthenticationLevel(), name, oid, value,
1076             COMPARE_PERMS, tuples, entry, null );
1077 
1078         return next.compare( opContext );
1079     }
1080 
1081 
1082     public LdapDN getMatchedName ( NextInterceptor next, GetMatchedNameOperationContext opContext ) throws Exception
1083     {
1084         // Access the principal requesting the operation, and bypass checks if it is the admin
1085         LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1086         LdapDN principalDn = principal.getJndiName();
1087         
1088         if ( isPrincipalAnAdministrator( principalDn ) || !enabled )
1089         {
1090             return next.getMatchedName( opContext );
1091         }
1092 
1093         // get the present matched name
1094         ClonedServerEntry entry;
1095         LdapDN matched = next.getMatchedName( opContext );
1096 
1097         // check if we have disclose on error permission for the entry at the matched dn
1098         // if not remove rdn and check that until nothing is left in the name and return
1099         // that but if permission is granted then short the process and return the dn
1100         while ( matched.size() > 0 )
1101         {
1102             entry = opContext.lookup( matched, ByPassConstants.GETMATCHEDDN_BYPASS );
1103             
1104             Set<LdapDN> userGroups = groupCache.getGroups( principalDn.toString() );
1105             Collection<ACITuple> tuples = new HashSet<ACITuple>();
1106             addPerscriptiveAciTuples( opContext, tuples, matched, entry.getOriginalEntry() );
1107             addEntryAciTuples( tuples, entry );
1108             addSubentryAciTuples( opContext, tuples, matched, entry );
1109 
1110             if ( engine.hasPermission( registries, opContext, userGroups, principalDn, 
1111                 principal.getAuthenticationLevel(), matched, null,
1112                 null, MATCHEDNAME_PERMS, tuples, entry, null ) )
1113             {
1114                 return matched;
1115             }
1116 
1117             matched.remove( matched.size() - 1 );
1118         }
1119 
1120         return matched;
1121     }
1122 
1123 
1124     public void cacheNewGroup( LdapDN name, ServerEntry entry ) throws Exception
1125     {
1126         groupCache.groupAdded( name, entry );
1127     }
1128 
1129 
1130     private boolean filter( OperationContext opContext, LdapDN normName, ClonedServerEntry clonedEntry ) 
1131         throws Exception
1132     {
1133         /*
1134          * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
1135          * tests.  If we hasPermission() returns false we immediately short the
1136          * process and return false.
1137          */
1138         
1139         LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1140         LdapDN userDn = principal.getJndiName();
1141         Set<LdapDN> userGroups = groupCache.getGroups( userDn.toNormName() );
1142         Collection<ACITuple> tuples = new HashSet<ACITuple>();
1143         addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry.getOriginalEntry() );
1144         addEntryAciTuples( tuples, clonedEntry.getOriginalEntry() );
1145         addSubentryAciTuples( opContext, tuples, normName, clonedEntry.getOriginalEntry() );
1146 
1147         if ( !engine.hasPermission( 
1148                         registries, 
1149                         opContext, 
1150                         userGroups, 
1151                         userDn, 
1152                         principal.getAuthenticationLevel(), 
1153                         normName, 
1154                         null, 
1155                         null, 
1156                         SEARCH_ENTRY_PERMS, 
1157                         tuples, 
1158                         clonedEntry.getOriginalEntry(), 
1159                         null ) )
1160         {
1161             return false;
1162         }
1163 
1164         /*
1165          * For each attribute type we check if access is allowed to the type.  If not
1166          * the attribute is yanked out of the entry to be returned.  If permission is
1167          * allowed we move on to check if the values are allowed.  Values that are
1168          * not allowed are removed from the attribute.  If the attribute has no more
1169          * values remaining then the entire attribute is removed.
1170          */
1171         List<AttributeType> attributeToRemove = new ArrayList<AttributeType>();
1172         
1173         for ( AttributeType attributeType:clonedEntry.getAttributeTypes() )
1174         {
1175             // if attribute type scope access is not allowed then remove the attribute and continue
1176             String id = attributeType.getName();
1177             EntryAttribute attr = clonedEntry.get( attributeType );
1178         
1179             if ( !engine.hasPermission( 
1180                         registries, 
1181                         opContext, 
1182                         userGroups, 
1183                         userDn,
1184                         principal.getAuthenticationLevel(), 
1185                         normName, 
1186                         id, 
1187                         null, 
1188                         SEARCH_ATTRVAL_PERMS, 
1189                         tuples, 
1190                         clonedEntry, 
1191                         null ) )
1192             {
1193                 attributeToRemove.add( attributeType );
1194                 
1195                 continue;
1196             }
1197 
1198             List<Value<?>> valueToRemove = new ArrayList<Value<?>>();
1199             
1200             // attribute type scope is ok now let's determine value level scope
1201             for ( Value<?> value:attr )
1202             {
1203                 if ( !engine.hasPermission( 
1204                         registries, 
1205                         opContext, 
1206                         userGroups, 
1207                         userDn, 
1208                         principal.getAuthenticationLevel(), 
1209                         normName, 
1210                         attr.getUpId(), 
1211                         value, 
1212                         SEARCH_ATTRVAL_PERMS, 
1213                         tuples,
1214                         clonedEntry, 
1215                         null ) )
1216                 {
1217                     valueToRemove.add( value );
1218                 }
1219             }
1220             
1221             for ( Value<?> value:valueToRemove )
1222             {
1223                 attr.remove( value );
1224             }
1225             
1226             if ( attr.size() == 0 )
1227             {
1228                 attributeToRemove.add( attributeType );
1229             }
1230         }
1231         
1232         for ( AttributeType attributeType:attributeToRemove )
1233         {
1234             clonedEntry.removeAttributes( attributeType );
1235         }
1236 
1237         return true;
1238     }
1239 
1240 
1241     /**
1242      * WARNING: create one of these filters fresh every time for each new search.
1243      */
1244     class AuthorizationFilter implements EntryFilter
1245     {
1246         public boolean accept( SearchingOperationContext operationContext, ClonedServerEntry entry ) 
1247             throws Exception
1248         {
1249             LdapDN normName = entry.getDn().normalize( atRegistry.getNormalizerMapping() );
1250             return filter( operationContext, normName, entry );
1251         }
1252     }
1253 }