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 java.io.IOException;
24  import java.text.ParseException;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Set;
31  
32  import javax.naming.directory.SearchControls;
33  
34  import org.apache.directory.api.ldap.aci.ACIItem;
35  import org.apache.directory.api.ldap.aci.ACIItemParser;
36  import org.apache.directory.api.ldap.aci.ACITuple;
37  import org.apache.directory.api.ldap.aci.MicroOperation;
38  import org.apache.directory.api.ldap.model.constants.Loggers;
39  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
40  import org.apache.directory.api.ldap.model.entry.Attribute;
41  import org.apache.directory.api.ldap.model.entry.Entry;
42  import org.apache.directory.api.ldap.model.entry.Modification;
43  import org.apache.directory.api.ldap.model.entry.Value;
44  import org.apache.directory.api.ldap.model.exception.LdapException;
45  import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
46  import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
47  import org.apache.directory.api.ldap.model.exception.LdapOperationException;
48  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
49  import org.apache.directory.api.ldap.model.filter.EqualityNode;
50  import org.apache.directory.api.ldap.model.filter.ExprNode;
51  import org.apache.directory.api.ldap.model.filter.OrNode;
52  import org.apache.directory.api.ldap.model.message.AliasDerefMode;
53  import org.apache.directory.api.ldap.model.message.SearchScope;
54  import org.apache.directory.api.ldap.model.name.Dn;
55  import org.apache.directory.api.ldap.model.schema.AttributeType;
56  import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
57  import org.apache.directory.server.constants.ServerDNConstants;
58  import org.apache.directory.server.core.api.CoreSession;
59  import org.apache.directory.server.core.api.DirectoryService;
60  import org.apache.directory.server.core.api.InterceptorEnum;
61  import org.apache.directory.server.core.api.LdapPrincipal;
62  import org.apache.directory.server.core.api.entry.ClonedServerEntry;
63  import org.apache.directory.server.core.api.entry.ServerEntryUtils;
64  import org.apache.directory.server.core.api.filtering.EntryFilter;
65  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
66  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
67  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
68  import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
69  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
70  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
71  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
72  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
73  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
74  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
75  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
76  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
77  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
78  import org.apache.directory.server.core.api.partition.Partition;
79  import org.apache.directory.server.core.api.partition.PartitionNexus;
80  import org.apache.directory.server.core.api.partition.PartitionTxn;
81  import org.apache.directory.server.core.api.subtree.SubentryUtils;
82  import org.apache.directory.server.core.authz.support.ACDFEngine;
83  import org.apache.directory.server.core.authz.support.AciContext;
84  import org.apache.directory.server.i18n.I18n;
85  import org.slf4j.Logger;
86  import org.slf4j.LoggerFactory;
87  
88  
89  /**
90   * An ACI based authorization service.
91   *
92   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
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      /** the dedicated logger for ACI */
100     private static final Logger ACI_LOG = LoggerFactory.getLogger( Loggers.ACI_LOG.getName() );
101 
102     private static final Collection<MicroOperation> ADD_PERMS;
103     private static final Collection<MicroOperation> READ_PERMS;
104     private static final Collection<MicroOperation> COMPARE_PERMS;
105     private static final Collection<MicroOperation> SEARCH_ENTRY_PERMS;
106     private static final Collection<MicroOperation> SEARCH_ATTRVAL_PERMS;
107     private static final Collection<MicroOperation> REMOVE_PERMS;
108     private static final Collection<MicroOperation> BROWSE_PERMS;
109     private static final Collection<MicroOperation> LOOKUP_PERMS;
110     private static final Collection<MicroOperation> REPLACE_PERMS;
111     private static final Collection<MicroOperation> RENAME_PERMS;
112     private static final Collection<MicroOperation> EXPORT_PERMS;
113     private static final Collection<MicroOperation> IMPORT_PERMS;
114     private static final Collection<MicroOperation> MOVERENAME_PERMS;
115 
116     static
117     {
118         Set<MicroOperation> set = new HashSet<>( 2 );
119         set.add( MicroOperation.BROWSE );
120         set.add( MicroOperation.RETURN_DN );
121         SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
122 
123         set = new HashSet<>( 2 );
124         set.add( MicroOperation.READ );
125         set.add( MicroOperation.BROWSE );
126         LOOKUP_PERMS = Collections.unmodifiableCollection( set );
127 
128         set = new HashSet<>( 2 );
129         set.add( MicroOperation.ADD );
130         set.add( MicroOperation.REMOVE );
131         REPLACE_PERMS = Collections.unmodifiableCollection( set );
132 
133         set = new HashSet<>( 2 );
134         set.add( MicroOperation.EXPORT );
135         set.add( MicroOperation.RENAME );
136         MOVERENAME_PERMS = Collections.unmodifiableCollection( set );
137 
138         SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
139         ADD_PERMS = Collections.singleton( MicroOperation.ADD );
140         READ_PERMS = Collections.singleton( MicroOperation.READ );
141         COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE );
142         REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE );
143         BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE );
144         RENAME_PERMS = Collections.singleton( MicroOperation.RENAME );
145         EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT );
146         IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT );
147     }
148 
149     /** a tupleCache that responds to add, delete, and modify attempts */
150     private TupleCache tupleCache;
151 
152     /** a groupCache that responds to add, delete, and modify attempts */
153     private GroupCache groupCache;
154 
155     /** a normalizing ACIItem parser */
156     private ACIItemParser aciParser;
157 
158     /** use and instance of the ACDF engine */
159     private ACDFEngine engine;
160 
161     /** the system wide subschemaSubentryDn */
162     private Dn subschemaSubentryDn;
163 
164     /** A reference to the nexus for direct backend operations */
165     private PartitionNexus nexus;
166 
167     public static final SearchControls DEFAULT_SEARCH_CONTROLS = new SearchControls();
168 
169     /** The SubentryUtils instance */
170     private static SubentryUtils subentryUtils;
171 
172 
173     /**
174      * Create a AciAuthorizationInterceptor instance
175      */
176     public AciAuthorizationInterceptor()
177     {
178         super( InterceptorEnum.ACI_AUTHORIZATION_INTERCEPTOR );
179     }
180 
181 
182     /**
183      * Load the Tuples into the cache
184      */
185     private void initTupleCache() throws LdapException
186     {
187         // Load all the prescriptiveACI : they are stored in AccessControlSubentry entries
188         SearchControls controls = new SearchControls();
189         controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
190         controls.setReturningAttributes( new String[]
191             { SchemaConstants.PRESCRIPTIVE_ACI_AT } );
192 
193         AttributeType ocAt = directoryService.getAtProvider().getObjectClass();
194         ExprNode filter = new EqualityNode<String>( ocAt, 
195             new Value( ocAt, SchemaConstants.ACCESS_CONTROL_SUBENTRY_OC ) );
196 
197         CoreSession adminSession = directoryService.getAdminSession();
198 
199         SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter, controls );
200 
201         searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
202         Partition partition = nexus.getPartition( Dn.ROOT_DSE );
203         searchOperationContext.setPartition( partition );
204         
205         try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
206         {
207             searchOperationContext.setTransaction( partitionTxn );
208 
209             EntryFilteringCursor results = nexus.search( searchOperationContext );
210     
211             try
212             {
213                 while ( results.next() )
214                 {
215                     Entry entry = results.get();
216     
217                     tupleCache.subentryAdded( entry.getDn(), entry );
218                 }
219     
220                 results.close();
221             }
222             catch ( Exception e )
223             {
224                 throw new LdapOperationException( e.getMessage(), e );
225             }
226         }
227         catch ( IOException ioe )
228         {
229             throw new LdapOtherException( ioe.getMessage(), ioe );
230         }
231     }
232 
233 
234     /**
235      * Load the Groups into the cache
236      */
237     private void initGroupCache() throws LdapException
238     {
239         // Load all the member/uniqueMember : they are stored in groupOfNames/groupOfUniqueName
240         SearchControls controls = new SearchControls();
241         controls.setSearchScope( SearchControls.SUBTREE_SCOPE );
242         controls.setReturningAttributes( new String[]
243             { SchemaConstants.MEMBER_AT, SchemaConstants.UNIQUE_MEMBER_AT } );
244         AttributeType ocAt = directoryService.getAtProvider().getObjectClass();
245 
246         ExprNode filter =
247             new OrNode(
248                 new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.GROUP_OF_NAMES_OC ) ),
249                 new EqualityNode<String>( ocAt, new Value( ocAt, SchemaConstants.GROUP_OF_UNIQUE_NAMES_OC ) ) );
250 
251         CoreSession adminSession = directoryService.getAdminSession();
252         
253         SearchOperationContextxt/SearchOperationContext.html#SearchOperationContext">SearchOperationContext searchOperationContext = new SearchOperationContext( adminSession, Dn.ROOT_DSE, filter,
254             controls );
255 
256         searchOperationContext.setAliasDerefMode( AliasDerefMode.NEVER_DEREF_ALIASES );
257 
258         EntryFilteringCursor results = nexus.search( searchOperationContext );
259 
260         try
261         {
262             while ( results.next() )
263             {
264                 Entry entry = results.get();
265 
266                 groupCache.groupAdded( entry.getDn().getNormName(), entry );
267             }
268 
269             results.close();
270         }
271         catch ( Exception e )
272         {
273             throw new LdapOperationException( e.getMessage(), e );
274         }
275     }
276 
277 
278     /**
279      * Initializes this interceptor based service by getting a handle on the nexus, setting up
280      * the tuple and group membership caches, the ACIItem parser and the ACDF engine.
281      *
282      * @param directoryService the directory service core
283      * @throws LdapException if there are problems during initialization
284      */
285     @Override
286     public void init( DirectoryService directoryService ) throws LdapException
287     {
288         LOG.debug( "Initializing the AciAuthorizationInterceptor" );
289 
290         super.init( directoryService );
291 
292         nexus = directoryService.getPartitionNexus();
293 
294         CoreSession adminSession = directoryService.getAdminSession();
295 
296         // Create the caches
297         tupleCache = new TupleCache( adminSession );
298         groupCache = new GroupCache( directoryService );
299 
300         // Iitialize the ACI PARSER and ACDF engine
301         aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( schemaManager ), schemaManager );
302         engine = new ACDFEngine( schemaManager );
303 
304         // stuff for dealing with subentries (garbage for now)
305         Value subschemaSubentry = directoryService.getPartitionNexus().getRootDseValue(
306             directoryService.getAtProvider().getSubschemaSubentry() );
307         subschemaSubentryDn = dnFactory.create( subschemaSubentry.getString() );
308 
309         // Init the caches now
310         initTupleCache();
311         initGroupCache();
312 
313         // Init the SubentryUtils instance
314         subentryUtils = new SubentryUtils( directoryService );
315     }
316 
317 
318     private void protectCriticalEntries( OperationContext opCtx, Dn dn ) throws LdapException
319     {
320         Dn principalDn = getPrincipal( opCtx ).getDn();
321 
322         if ( dn.isEmpty() )
323         {
324             String msg = I18n.err( I18n.ERR_8 );
325             LOG.error( msg );
326             throw new LdapNoPermissionException( msg );
327         }
328 
329         if ( isTheAdministrator( dn ) )
330         {
331             String msg = I18n.err( I18n.ERR_9, principalDn.getName(), dn.getName() );
332             LOG.error( msg );
333             throw new LdapNoPermissionException( msg );
334         }
335     }
336 
337 
338     /**
339      * Adds perscriptiveACI tuples to a collection of tuples by accessing the
340      * tupleCache.  The tuple cache is accessed for each A/C subentry
341      * associated with the protected entry.  Note that subentries are handled
342      * differently: their parent, the administrative entry is accessed to
343      * determine the perscriptiveACIs effecting the AP and hence the subentry
344      * which is considered to be in the same context.
345      *
346      * @param tuples the collection of tuples to add to
347      * @param dn the normalized distinguished name of the protected entry
348      * @param entry the target entry whose access is being controlled
349      * @throws Exception if there are problems accessing attribute values
350      * @param proxy the partition nexus proxy object
351      */
352     private void addPerscriptiveAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry )
353         throws LdapException
354     {
355         Entry originalEntry;
356 
357         if ( entry instanceof ClonedServerEntry )
358         {
359             originalEntry = ( ( ClonedServerEntry ) entry ).getOriginalEntry();
360         }
361         else
362         {
363             originalEntry = entry;
364         }
365 
366         Attribute oc = originalEntry.get( directoryService.getAtProvider().getObjectClass() );
367 
368         /*
369          * If the protected entry is a subentry, then the entry being evaluated
370          * for perscriptiveACIs is in fact the administrative entry.  By
371          * substituting the administrative entry for the actual subentry the
372          * code below this "if" statement correctly evaluates the effects of
373          * perscriptiveACI on the subentry.  Basically subentries are considered
374          * to be in the same naming context as their access point so the subentries
375          * effecting their parent entry applies to them as well.
376          */
377         if ( oc.contains( SchemaConstants.SUBENTRY_OC ) )
378         {
379             Dn parentDn = dn.getParent();
380             CoreSession session = opContext.getSession();
381             LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn,
382                 SchemaConstants.ALL_ATTRIBUTES_ARRAY );
383             lookupContext.setPartition( opContext.getPartition() );
384             lookupContext.setTransaction( opContext.getTransaction() );
385 
386             originalEntry = directoryService.getPartitionNexus().lookup( lookupContext );
387         }
388 
389         Attribute subentries = originalEntry.get( directoryService.getAtProvider().getAccessControlSubentries() );
390 
391         if ( subentries == null )
392         {
393             return;
394         }
395 
396         for ( Value value : subentries )
397         {
398             String subentryDnStr = value.getString();
399             Dn subentryDn = dnFactory.create( subentryDnStr );
400             tuples.addAll( tupleCache.getACITuples( subentryDn.getNormName() ) );
401         }
402     }
403 
404 
405     /**
406      * Adds the set of entryACI tuples to a collection of tuples.  The entryACI
407      * is parsed and tuples are generated on they fly then added to the collection.
408      *
409      * @param tuples the collection of tuples to add to
410      * @param entry the target entry that access to is being regulated
411      * @throws Exception if there are problems accessing attribute values
412      */
413     private void addEntryAciTuples( Collection<ACITuple> tuples, Entry entry ) throws LdapException
414     {
415         Attribute entryAci = entry.get( directoryService.getAtProvider().getEntryACI() );
416 
417         if ( entryAci == null )
418         {
419             return;
420         }
421 
422         for ( Value value : entryAci )
423         {
424             String aciString = value.getString();
425             ACIItem item;
426 
427             try
428             {
429                 item = aciParser.parse( aciString );
430             }
431             catch ( ParseException e )
432             {
433                 String msg = I18n.err( I18n.ERR_10, aciString );
434                 LOG.error( msg, e );
435                 throw new LdapOperationErrorException( msg );
436             }
437 
438             tuples.addAll( item.toTuples() );
439         }
440     }
441 
442 
443     /**
444      * Adds the set of subentryACI tuples to a collection of tuples.  The subentryACI
445      * is parsed and tuples are generated on the fly then added to the collection.
446      *
447      * @param tuples the collection of tuples to add to
448      * @param dn the normalized distinguished name of the protected entry
449      * @param entry the target entry that access to is being regulated
450      * @throws Exception if there are problems accessing attribute values
451      * @param proxy the partition nexus proxy object
452      */
453     private void addSubentryAciTuples( OperationContext opContext, Collection<ACITuple> tuples, Dn dn, Entry entry )
454         throws LdapException
455     {
456         // only perform this for subentries
457         if ( !entry.contains( SchemaConstants.OBJECT_CLASS_AT, SchemaConstants.SUBENTRY_OC ) )
458         {
459             return;
460         }
461 
462         // get the parent or administrative entry for this subentry since it
463         // will contain the subentryACI attributes that effect subentries
464         Dn parentDn = dn.getParent();
465 
466         CoreSession session = opContext.getSession();
467         LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, parentDn,
468             SchemaConstants.ALL_ATTRIBUTES_ARRAY );
469         lookupContext.setPartition( opContext.getPartition() );
470         lookupContext.setTransaction( opContext.getTransaction() );
471 
472         Entry administrativeEntry = ( ( ClonedServerEntry ) directoryService.getPartitionNexus().lookup( lookupContext ) )
473             .getOriginalEntry();
474 
475         Attribute subentryAci = administrativeEntry.get( directoryService.getAtProvider().getSubentryACI() );
476 
477         if ( subentryAci == null )
478         {
479             return;
480         }
481 
482         for ( Value value : subentryAci )
483         {
484             String aciString = value.getString();
485             ACIItem item;
486 
487             try
488             {
489                 item = aciParser.parse( aciString );
490             }
491             catch ( ParseException e )
492             {
493                 String msg = I18n.err( I18n.ERR_11, aciString );
494                 LOG.error( msg, e );
495                 throw new LdapOperationErrorException( msg );
496             }
497 
498             tuples.addAll( item.toTuples() );
499         }
500     }
501 
502 
503     /* -------------------------------------------------------------------------------
504      * Within every access controled interceptor method we must retrieve the ACITuple
505      * set for all the perscriptiveACIs that apply to the candidate, the target entry
506      * operated upon.  This ACITuple set is gotten from the TupleCache by looking up
507      * the subentries referenced by the accessControlSubentries operational attribute
508      * within the target entry.
509      *
510      * Then the entry is inspected for an entryACI.  This is not done for the add op
511      * since it could introduce a security breech.  So for non-add ops if present a
512      * set of ACITuples are generated for all the entryACIs within the entry.  This
513      * set is combined with the ACITuples cached for the perscriptiveACI affecting
514      * the target entry.  If the entry is a subentry the ACIs are also processed for
515      * the subentry to generate more ACITuples.  This subentry TupleACI set is joined
516      * with the entry and perscriptive ACI.
517      *
518      * The union of ACITuples are fed into the engine along with other parameters
519      * to decide whether a permission is granted or rejected for the specific
520      * operation.
521      * -------------------------------------------------------------------------------
522      */
523     /**
524      * {@inheritDoc}
525      */
526     @Override
527     public void add( AddOperationContext addContext ) throws LdapException
528     {
529         // bypass authz code if it was disabled
530         if ( !directoryService.isAccessControlEnabled() )
531         {
532             ACI_LOG.debug( "ACI interceptor disabled" );
533             next( addContext );
534             return;
535         }
536 
537         ACI_LOG.debug( "Adding the entry {}", addContext.getEntry() );
538 
539         // Access the principal requesting the operation, and bypass checks if it is the admin
540         LdapPrincipal principal = addContext.getSession().getEffectivePrincipal();
541         Dn principalDn = principal.getDn();
542 
543         Entry serverEntry = addContext.getEntry();
544 
545         Dn dn = addContext.getDn();
546 
547         // bypass authz code but manage caches if operation is performed by the admin
548         if ( isPrincipalAnAdministrator( principalDn ) )
549         {
550             ACI_LOG.debug( "Addition done by the administartor : no check" );
551 
552             next( addContext );
553             tupleCache.subentryAdded( dn, serverEntry );
554             groupCache.groupAdded( dn.getNormName(), serverEntry );
555             return;
556         }
557 
558         // perform checks below here for all non-admin users
559         Entry subentry = subentryUtils.getSubentryAttributes( dn, serverEntry );
560 
561         for ( Attribute attribute : serverEntry )
562         {
563             subentry.put( attribute );
564         }
565 
566         // Assemble all the information required to make an access control decision
567         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
568         Collection<ACITuple> tuples = new HashSet<>();
569 
570         // Build the total collection of tuples to be considered for add rights
571         // NOTE: entryACI are NOT considered in adds (it would be a security breech)
572         addPerscriptiveAciTuples( addContext, tuples, dn, subentry );
573         addSubentryAciTuples( addContext, tuples, dn, subentry );
574 
575         // check if entry scope permission is granted
576         AciContexte/authz/support/AciContext.html#AciContext">AciContext entryAciCtx = new AciContext( schemaManager, addContext );
577         entryAciCtx.setUserGroupNames( userGroups );
578         entryAciCtx.setUserDn( principalDn );
579         entryAciCtx.setAuthenticationLevel( principal.getAuthenticationLevel() );
580         entryAciCtx.setEntryDn( dn );
581         entryAciCtx.setMicroOperations( ADD_PERMS );
582         entryAciCtx.setAciTuples( tuples );
583         entryAciCtx.setEntry( subentry );
584 
585         engine.checkPermission( entryAciCtx );
586 
587         // now we must check if attribute type and value scope permission is granted
588         for ( Attribute attribute : serverEntry )
589         {
590             for ( Value value : attribute )
591             {
592                 AciContextuthz/support/AciContext.html#AciContext">AciContext attrAciContext = new AciContext( schemaManager, addContext );
593                 attrAciContext.setUserGroupNames( userGroups );
594                 attrAciContext.setUserDn( principalDn );
595                 attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
596                 attrAciContext.setEntryDn( dn );
597                 attrAciContext.setAttributeType( attribute.getAttributeType() );
598                 attrAciContext.setAttrValue( value );
599                 attrAciContext.setMicroOperations( ADD_PERMS );
600                 attrAciContext.setAciTuples( tuples );
601                 attrAciContext.setEntry( serverEntry );
602 
603                 engine.checkPermission( attrAciContext );
604             }
605         }
606 
607         // if we've gotten this far then access has been granted
608         next( addContext );
609 
610         // if the entry added is a subentry or a groupOf[Unique]Names we must
611         // update the ACITuple cache and the groups cache to keep them in sync
612         tupleCache.subentryAdded( dn, serverEntry );
613         groupCache.groupAdded( dn.getNormName(), serverEntry );
614     }
615 
616 
617     /**
618      * {@inheritDoc}
619      */
620     @Override
621     public boolean compare( CompareOperationContext compareContext ) throws LdapException
622     {
623         CoreSession session = compareContext.getSession();
624         Dn dn = compareContext.getDn();
625         String oid = compareContext.getOid();
626 
627         Entry entry = compareContext.getOriginalEntry();
628 
629         LdapPrincipal principal = session.getEffectivePrincipal();
630         Dn principalDn = principal.getDn();
631 
632         if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() )
633         {
634             return next( compareContext );
635         }
636 
637         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
638         Collection<ACITuple> tuples = new HashSet<>();
639         addPerscriptiveAciTuples( compareContext, tuples, dn, entry );
640         addEntryAciTuples( tuples, entry );
641         addSubentryAciTuples( compareContext, tuples, dn, entry );
642 
643         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, compareContext );
644         aciContext.setUserGroupNames( userGroups );
645         aciContext.setUserDn( principalDn );
646         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
647         aciContext.setEntryDn( dn );
648         aciContext.setMicroOperations( READ_PERMS );
649         aciContext.setAciTuples( tuples );
650         aciContext.setEntry( entry );
651 
652         engine.checkPermission( aciContext );
653 
654         AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( oid );
655 
656         aciContext = new AciContext( schemaManager, compareContext );
657         aciContext.setUserGroupNames( userGroups );
658         aciContext.setUserDn( principalDn );
659         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
660         aciContext.setEntryDn( dn );
661         aciContext.setAttributeType( attributeType );
662         aciContext.setMicroOperations( COMPARE_PERMS );
663         aciContext.setAciTuples( tuples );
664         aciContext.setEntry( entry );
665 
666         engine.checkPermission( aciContext );
667 
668         return next( compareContext );
669     }
670 
671 
672     /**
673      * {@inheritDoc}
674      */
675     @Override
676     public void delete( DeleteOperationContext deleteContext ) throws LdapException
677     {
678         CoreSession session = deleteContext.getSession();
679 
680         // bypass authz code if we are disabled
681         if ( !directoryService.isAccessControlEnabled() )
682         {
683             next( deleteContext );
684             return;
685         }
686 
687         Dn dn = deleteContext.getDn();
688         LdapPrincipal principal = session.getEffectivePrincipal();
689         Dn principalDn = principal.getDn();
690 
691         Entry entry = deleteContext.getEntry();
692 
693         protectCriticalEntries( deleteContext, dn );
694 
695         // bypass authz code but manage caches if operation is performed by the admin
696         if ( isPrincipalAnAdministrator( principalDn ) )
697         {
698             next( deleteContext );
699 
700             tupleCache.subentryDeleted( dn, entry );
701             groupCache.groupDeleted( dn, entry );
702 
703             return;
704         }
705 
706         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
707         Collection<ACITuple> tuples = new HashSet<>();
708         addPerscriptiveAciTuples( deleteContext, tuples, dn, entry );
709         addEntryAciTuples( tuples, entry );
710         addSubentryAciTuples( deleteContext, tuples, dn, entry );
711 
712         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, deleteContext );
713         aciContext.setUserGroupNames( userGroups );
714         aciContext.setUserDn( principalDn );
715         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
716         aciContext.setEntryDn( dn );
717         aciContext.setMicroOperations( REMOVE_PERMS );
718         aciContext.setAciTuples( tuples );
719         aciContext.setEntry( entry );
720 
721         engine.checkPermission( aciContext );
722 
723         next( deleteContext );
724 
725         tupleCache.subentryDeleted( dn, entry );
726         groupCache.groupDeleted( dn, entry );
727     }
728 
729 
730     /**
731      * {@inheritDoc}
732      */
733     @Override
734     public boolean hasEntry( HasEntryOperationContext hasEntryContext ) throws LdapException
735     {
736         Dn dn = hasEntryContext.getDn();
737 
738         if ( !directoryService.isAccessControlEnabled() )
739         {
740             return dn.isRootDse() || next( hasEntryContext );
741         }
742 
743         boolean answer = next( hasEntryContext );
744 
745         // no checks on the RootDSE
746         if ( dn.isRootDse() )
747         {
748             // No need to go down to the stack, if the dn is empty
749             // It's the rootDSE, and it exists !
750             return answer;
751         }
752 
753         CoreSession session = hasEntryContext.getSession();
754 
755         // TODO - eventually replace this with a check on session.isAnAdministrator()
756         LdapPrincipal principal = session.getEffectivePrincipal();
757         Dn principalDn = principal.getDn();
758 
759         if ( isPrincipalAnAdministrator( principalDn ) )
760         {
761             return answer;
762         }
763 
764         LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, dn,
765             SchemaConstants.ALL_ATTRIBUTES_ARRAY );
766         lookupContext.setPartition( hasEntryContext.getPartition() );
767         lookupContext.setTransaction( hasEntryContext.getTransaction() );
768 
769         Entry entry = directoryService.getPartitionNexus().lookup( lookupContext );
770 
771         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
772         Collection<ACITuple> tuples = new HashSet<>();
773         addPerscriptiveAciTuples( hasEntryContext, tuples, dn, entry );
774         addEntryAciTuples( tuples, ( ( ClonedServerEntry ) entry ).getOriginalEntry() );
775         addSubentryAciTuples( hasEntryContext, tuples, dn, ( ( ClonedServerEntry ) entry ).getOriginalEntry() );
776 
777         // check that we have browse access to the entry
778         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, hasEntryContext );
779         aciContext.setUserGroupNames( userGroups );
780         aciContext.setUserDn( principalDn );
781         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
782         aciContext.setEntryDn( dn );
783         aciContext.setMicroOperations( BROWSE_PERMS );
784         aciContext.setAciTuples( tuples );
785         aciContext.setEntry( ( ( ClonedServerEntry ) entry ).getOriginalEntry() );
786 
787         engine.checkPermission( aciContext );
788 
789         return next( hasEntryContext );
790     }
791 
792 
793     /**
794      * {@inheritDoc}
795      */
796     @Override
797     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
798     {
799         CoreSession session = lookupContext.getSession();
800 
801         Entry entry = next( lookupContext );
802 
803         LdapPrincipal principal = session.getEffectivePrincipal();
804         Dn principalDn = principal.getDn();
805         
806         if ( !principalDn.isSchemaAware() )
807         {
808             principalDn = new Dn( schemaManager, principalDn );
809         }
810 
811         // Bypass this interceptor if we disabled the AC subsystem or if the principal is the admin
812         if ( isPrincipalAnAdministrator( principalDn ) || !directoryService.isAccessControlEnabled() )
813         {
814             return entry;
815         }
816 
817         checkLookupAccess( lookupContext, entry );
818 
819         return entry;
820     }
821 
822 
823     /**
824      * {@inheritDoc}
825      */
826     @Override
827     public void modify( ModifyOperationContext modifyContext ) throws LdapException
828     {
829         Dn dn = modifyContext.getDn();
830 
831         // Access the principal requesting the operation, and bypass checks if it is the admin
832         Entry entry = modifyContext.getEntry();
833 
834         LdapPrincipal principal = modifyContext.getSession().getEffectivePrincipal();
835         Dn principalDn = principal.getDn();
836 
837         // bypass authz code if we are disabled
838         if ( !directoryService.isAccessControlEnabled() )
839         {
840             next( modifyContext );
841             return;
842         }
843 
844         List<Modification> mods = modifyContext.getModItems();
845 
846         // bypass authz code but manage caches if operation is performed by the admin
847         if ( isPrincipalAnAdministrator( principalDn ) )
848         {
849             next( modifyContext );
850 
851             Entry modifiedEntry = modifyContext.getAlteredEntry();
852             tupleCache.subentryModified( dn, mods, modifiedEntry );
853             groupCache.groupModified( dn, mods, entry, schemaManager );
854 
855             return;
856         }
857 
858         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
859         Collection<ACITuple> tuples = new HashSet<>();
860         addPerscriptiveAciTuples( modifyContext, tuples, dn, entry );
861         addEntryAciTuples( tuples, entry );
862         addSubentryAciTuples( modifyContext, tuples, dn, entry );
863 
864         AciContextthz/support/AciContext.html#AciContext">AciContext entryAciContext = new AciContext( schemaManager, modifyContext );
865         entryAciContext.setUserGroupNames( userGroups );
866         entryAciContext.setUserDn( principalDn );
867         entryAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
868         entryAciContext.setEntryDn( dn );
869         entryAciContext.setMicroOperations( Collections.singleton( MicroOperation.MODIFY ) );
870         entryAciContext.setAciTuples( tuples );
871         entryAciContext.setEntry( entry );
872 
873         engine.checkPermission( entryAciContext );
874 
875         Collection<MicroOperation> perms;
876         Entry entryView = entry.clone();
877 
878         for ( Modification mod : mods )
879         {
880             Attribute attr = mod.getAttribute();
881 
882             switch ( mod.getOperation() )
883             {
884                 case ADD_ATTRIBUTE:
885                     perms = ADD_PERMS;
886 
887                     // If the attribute is being created with an initial value ...
888                     if ( entry.get( attr.getId() ) == null )
889                     {
890                         AciContextuthz/support/AciContext.html#AciContext">AciContext attrAciContext = new AciContext( schemaManager, modifyContext );
891                         attrAciContext.setUserGroupNames( userGroups );
892                         attrAciContext.setUserDn( principalDn );
893                         attrAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
894                         attrAciContext.setEntryDn( dn );
895                         attrAciContext.setAttributeType( attr.getAttributeType() );
896                         attrAciContext.setMicroOperations( perms );
897                         attrAciContext.setAciTuples( tuples );
898                         attrAciContext.setEntry( entry );
899 
900                         // ... we also need to check if adding the attribute is permitted
901                         engine.checkPermission( attrAciContext );
902                     }
903 
904                     break;
905 
906                 case REMOVE_ATTRIBUTE:
907                     perms = REMOVE_PERMS;
908                     Attribute entryAttr = entry.get( attr.getId() );
909 
910                     if ( ( entryAttr != null ) && ( entryAttr.size() == 1 ) )
911                     {
912                         // If there is only one value remaining in the attribute ...
913                         // ... we also need to check if removing the attribute at all is permitted
914                         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, modifyContext );
915                         aciContext.setUserGroupNames( userGroups );
916                         aciContext.setUserDn( principalDn );
917                         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
918                         aciContext.setEntryDn( dn );
919                         aciContext.setAttributeType( attr.getAttributeType() );
920                         aciContext.setMicroOperations( perms );
921                         aciContext.setAciTuples( tuples );
922                         aciContext.setEntry( entry );
923 
924                         engine.checkPermission( aciContext );
925                     }
926 
927                     break;
928 
929                 case REPLACE_ATTRIBUTE:
930                     perms = REPLACE_PERMS;
931                     break;
932 
933                 default:
934                     throw new IllegalArgumentException( "Unexpected modify operation " + mod.getOperation() );
935             }
936 
937             /**
938              * Update the entry view as the current modification is applied to the original entry.
939              * This is especially required for handling the MaxValueCount protected item. Number of
940              * values for an attribute after a modification should be known in advance in order to
941              * check permissions for MaxValueCount protected item. So during addition of the first
942              * value of an attribute it can be rejected if the permission denied due the the
943              * MaxValueCount protected item. This is not the perfect implementation as required by
944              * the specification because the system should reject the addition exactly on the right
945              * value of the attribute. However as we do not have that much granularity in our
946              * implementation (we consider an Attribute Addition itself a Micro Operation,
947              * not the individual Value Additions) we just handle this when the first value of an
948              * attribute is being checked for relevant permissions below.
949              */
950             entryView = ServerEntryUtils.getTargetEntry( mod, entryView, schemaManager );
951 
952             for ( Value value : attr )
953             {
954                 AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, modifyContext );
955                 aciContext.setUserGroupNames( userGroups );
956                 aciContext.setUserDn( principalDn );
957                 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
958                 aciContext.setEntryDn( dn );
959                 aciContext.setAttributeType( attr.getAttributeType() );
960                 aciContext.setAttrValue( value );
961                 aciContext.setMicroOperations( perms );
962                 aciContext.setAciTuples( tuples );
963                 aciContext.setEntry( entry );
964                 aciContext.setEntryView( entryView );
965 
966                 engine.checkPermission( aciContext );
967             }
968         }
969 
970         next( modifyContext );
971 
972         Entry modifiedEntry = modifyContext.getAlteredEntry();
973         tupleCache.subentryModified( dn, mods, modifiedEntry );
974         groupCache.groupModified( dn, mods, entry, schemaManager );
975     }
976 
977 
978     /**
979      * {@inheritDoc}
980      */
981     @Override
982     public void move( MoveOperationContext moveContext ) throws LdapException
983     {
984         Dn oriChildName = moveContext.getDn();
985 
986         // Access the principal requesting the operation, and bypass checks if it is the admin
987         Entry entry = moveContext.getOriginalEntry();
988         CoreSession session = moveContext.getSession();
989 
990         Dn newDn = moveContext.getNewDn();
991 
992         LdapPrincipal principal = session.getEffectivePrincipal();
993         Dn principalDn = principal.getDn();
994 
995         // bypass authz code if we are disabled
996         if ( !directoryService.isAccessControlEnabled() )
997         {
998             next( moveContext );
999             return;
1000         }
1001 
1002         protectCriticalEntries( moveContext, oriChildName );
1003 
1004         // bypass authz code but manage caches if operation is performed by the admin
1005         if ( isPrincipalAnAdministrator( principalDn ) )
1006         {
1007             next( moveContext );
1008             tupleCache.subentryRenamed( oriChildName, newDn );
1009             groupCache.groupRenamed( oriChildName, newDn );
1010             return;
1011         }
1012 
1013         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
1014         Collection<ACITuple> tuples = new HashSet<>();
1015         addPerscriptiveAciTuples( moveContext, tuples, oriChildName, entry );
1016         addEntryAciTuples( tuples, entry );
1017         addSubentryAciTuples( moveContext, tuples, oriChildName, entry );
1018 
1019         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, moveContext );
1020         aciContext.setUserGroupNames( userGroups );
1021         aciContext.setUserDn( principalDn );
1022         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1023         aciContext.setEntryDn( oriChildName );
1024         aciContext.setMicroOperations( EXPORT_PERMS );
1025         aciContext.setAciTuples( tuples );
1026         aciContext.setEntry( entry );
1027 
1028         engine.checkPermission( aciContext );
1029 
1030         // Get the entry again without operational attributes
1031         // because access control subentry operational attributes
1032         // will not be valid at the new location.
1033         // This will certainly be fixed by the SubentryInterceptor,
1034         // but after this service.
1035         LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, oriChildName,
1036             SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
1037         lookupContext.setPartition( moveContext.getPartition() );
1038         lookupContext.setTransaction( moveContext.getTransaction() );
1039 
1040         Entry importedEntry = directoryService.getPartitionNexus().lookup( lookupContext );
1041 
1042         // As the target entry does not exist yet and so
1043         // its subentry operational attributes are not there,
1044         // we need to construct an entry to represent it
1045         // at least with minimal requirements which are object class
1046         // and access control subentry operational attributes.
1047         Entry subentryAttrs = subentryUtils.getSubentryAttributes( newDn, importedEntry );
1048 
1049         for ( Attribute attribute : importedEntry )
1050         {
1051             subentryAttrs.put( attribute );
1052         }
1053 
1054         Collection<ACITuple> destTuples = new HashSet<>();
1055         // Import permission is only valid for prescriptive ACIs
1056         addPerscriptiveAciTuples( moveContext, destTuples, newDn, subentryAttrs );
1057 
1058         // Evaluate the target context to see whether it
1059         // allows an entry named newName to be imported as a subordinate.
1060         aciContext = new AciContext( schemaManager, moveContext );
1061         aciContext.setUserGroupNames( userGroups );
1062         aciContext.setUserDn( principalDn );
1063         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1064         aciContext.setEntryDn( newDn );
1065         aciContext.setMicroOperations( IMPORT_PERMS );
1066         aciContext.setAciTuples( destTuples );
1067         aciContext.setEntry( subentryAttrs );
1068 
1069         engine.checkPermission( aciContext );
1070 
1071         next( moveContext );
1072         tupleCache.subentryRenamed( oriChildName, newDn );
1073         groupCache.groupRenamed( oriChildName, newDn );
1074     }
1075 
1076 
1077     /**
1078      * {@inheritDoc}
1079      */
1080     @Override
1081     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
1082     {
1083         Dn oldDn = moveAndRenameContext.getDn();
1084         CoreSession session = moveAndRenameContext.getSession();
1085 
1086         Entry entry = moveAndRenameContext.getOriginalEntry();
1087 
1088         LdapPrincipal principal = session.getEffectivePrincipal();
1089         Dn principalDn = principal.getDn();
1090         Dn newDn = moveAndRenameContext.getNewDn();
1091 
1092         // bypass authz code if we are disabled
1093         if ( !directoryService.isAccessControlEnabled() )
1094         {
1095             next( moveAndRenameContext );
1096 
1097             return;
1098         }
1099 
1100         protectCriticalEntries( moveAndRenameContext, oldDn );
1101 
1102         // bypass authz code but manage caches if operation is performed by the admin
1103         if ( isPrincipalAnAdministrator( principalDn ) )
1104         {
1105             next( moveAndRenameContext );
1106             tupleCache.subentryRenamed( oldDn, newDn );
1107             groupCache.groupRenamed( oldDn, newDn );
1108 
1109             return;
1110         }
1111 
1112         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
1113         Collection<ACITuple> tuples = new HashSet<>();
1114         addPerscriptiveAciTuples( moveAndRenameContext, tuples, oldDn, entry );
1115         addEntryAciTuples( tuples, entry );
1116         addSubentryAciTuples( moveAndRenameContext, tuples, oldDn, entry );
1117 
1118         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, moveAndRenameContext );
1119         aciContext.setUserGroupNames( userGroups );
1120         aciContext.setUserDn( principalDn );
1121         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1122         aciContext.setEntryDn( oldDn );
1123         aciContext.setMicroOperations( MOVERENAME_PERMS );
1124         aciContext.setAciTuples( tuples );
1125         aciContext.setEntry( entry );
1126 
1127         engine.checkPermission( aciContext );
1128 
1129         // Get the entry again without operational attributes
1130         // because access control subentry operational attributes
1131         // will not be valid at the new location.
1132         // This will certainly be fixed by the SubentryInterceptor,
1133         // but after this service.
1134 
1135         LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( session, oldDn,
1136             SchemaConstants.ALL_USER_ATTRIBUTES_ARRAY );
1137         lookupContext.setPartition( moveAndRenameContext.getPartition() );
1138         lookupContext.setTransaction( moveAndRenameContext.getTransaction() );
1139 
1140         Entry importedEntry = directoryService.getPartitionNexus().lookup( lookupContext );
1141 
1142         // As the target entry does not exist yet and so
1143         // its subentry operational attributes are not there,
1144         // we need to construct an entry to represent it
1145         // at least with minimal requirements which are object class
1146         // and access control subentry operational attributes.
1147         Entry subentryAttrs = subentryUtils.getSubentryAttributes( newDn, importedEntry );
1148 
1149         for ( Attribute attribute : importedEntry )
1150         {
1151             subentryAttrs.put( attribute );
1152         }
1153 
1154         Collection<ACITuple> destTuples = new HashSet<>();
1155         // Import permission is only valid for prescriptive ACIs
1156         addPerscriptiveAciTuples( moveAndRenameContext, destTuples, newDn, subentryAttrs );
1157 
1158         // Evaluate the target context to see whether it
1159         // allows an entry named newName to be imported as a subordinate.
1160         aciContext = new AciContext( schemaManager, moveAndRenameContext );
1161         aciContext.setUserGroupNames( userGroups );
1162         aciContext.setUserDn( principalDn );
1163         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1164         aciContext.setEntryDn( newDn );
1165         aciContext.setMicroOperations( IMPORT_PERMS );
1166         aciContext.setAciTuples( destTuples );
1167         aciContext.setEntry( subentryAttrs );
1168 
1169         engine.checkPermission( aciContext );
1170 
1171         next( moveAndRenameContext );
1172         tupleCache.subentryRenamed( oldDn, newDn );
1173         groupCache.groupRenamed( oldDn, newDn );
1174     }
1175 
1176 
1177     /**
1178      * {@inheritDoc}
1179      */
1180     @Override
1181     public void rename( RenameOperationContext renameContext ) throws LdapException
1182     {
1183         Dn oldName = renameContext.getDn();
1184         Entry originalEntry = renameContext.getOriginalEntry();
1185 
1186         if ( renameContext.getEntry() != null )
1187         {
1188             originalEntry = ( ( ClonedServerEntry ) renameContext.getEntry() ).getOriginalEntry();
1189         }
1190 
1191         LdapPrincipal principal = renameContext.getSession().getEffectivePrincipal();
1192         Dn principalDn = principal.getDn();
1193         Dn newName = renameContext.getNewDn();
1194 
1195         // bypass authz code if we are disabled
1196         if ( !directoryService.isAccessControlEnabled() )
1197         {
1198             next( renameContext );
1199             return;
1200         }
1201 
1202         protectCriticalEntries( renameContext, oldName );
1203 
1204         // bypass authz code but manage caches if operation is performed by the admin
1205         if ( isPrincipalAnAdministrator( principalDn ) )
1206         {
1207             next( renameContext );
1208             tupleCache.subentryRenamed( oldName, newName );
1209 
1210             // TODO : this method returns a boolean : what should we do with the result ?
1211             groupCache.groupRenamed( oldName, newName );
1212 
1213             return;
1214         }
1215 
1216         Set<String> userGroups = groupCache.getGroups( principalDn.getNormName() );
1217         Collection<ACITuple> tuples = new HashSet<>();
1218         addPerscriptiveAciTuples( renameContext, tuples, oldName, originalEntry );
1219         addEntryAciTuples( tuples, originalEntry );
1220         addSubentryAciTuples( renameContext, tuples, oldName, originalEntry );
1221 
1222         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, renameContext );
1223         aciContext.setUserGroupNames( userGroups );
1224         aciContext.setUserDn( principalDn );
1225         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1226         aciContext.setEntryDn( oldName );
1227         aciContext.setMicroOperations( RENAME_PERMS );
1228         aciContext.setAciTuples( tuples );
1229         aciContext.setEntry( originalEntry );
1230 
1231         engine.checkPermission( aciContext );
1232 
1233         next( renameContext );
1234         tupleCache.subentryRenamed( oldName, newName );
1235         groupCache.groupRenamed( oldName, newName );
1236     }
1237 
1238 
1239     /**
1240      * {@inheritDoc}
1241      */
1242     @Override
1243     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
1244     {
1245         LdapPrincipal user = searchContext.getSession().getEffectivePrincipal();
1246         Dn principalDn = user.getDn();
1247         EntryFilteringCursor cursor = next( searchContext );
1248 
1249         boolean isSubschemaSubentryLookup = subschemaSubentryDn.equals( searchContext.getDn() );
1250 
1251         boolean isRootDseLookup = ( searchContext.getDn().size() == 0 )
1252             && ( searchContext.getScope() == SearchScope.OBJECT );
1253 
1254         if ( isPrincipalAnAdministrator( principalDn )
1255             || !directoryService.isAccessControlEnabled() || isRootDseLookup
1256             || isSubschemaSubentryLookup )
1257         {
1258             return cursor;
1259         }
1260 
1261         cursor.addEntryFilter( new AuthorizationFilter() );
1262         return cursor;
1263     }
1264 
1265 
1266     /**
1267      * Checks if the READ permissions exist to the entry and to each attribute type and
1268      * value.
1269      *
1270      * @todo not sure if we should hide attribute types/values or throw an exception
1271      * instead.  I think we're going to have to use a filter to restrict the return
1272      * of attribute types and values instead of throwing an exception.  Lack of read
1273      * perms to attributes and their values results in their removal when returning
1274      * the entry.
1275      *
1276      * @param principal the user associated with the call
1277      * @param dn the name of the entry being looked up
1278      * @param entry the raw entry pulled from the nexus
1279      * @throws Exception if undlying access to the DIT fails
1280      */
1281     private void checkLookupAccess( LookupOperationContext lookupContext, Entry entry ) throws LdapException
1282     {
1283         Dn dn = lookupContext.getDn();
1284 
1285         // no permissions checks on the RootDSE
1286         if ( dn.isRootDse() )
1287         {
1288             return;
1289         }
1290 
1291         LdapPrincipal principal = lookupContext.getSession().getEffectivePrincipal();
1292         Dn userName = principal.getDn();
1293         Set<String> userGroups = groupCache.getGroups( userName.getNormName() );
1294         Collection<ACITuple> tuples = new HashSet<>();
1295         addPerscriptiveAciTuples( lookupContext, tuples, dn, entry );
1296         addEntryAciTuples( tuples, entry );
1297         addSubentryAciTuples( lookupContext, tuples, dn, entry );
1298 
1299         // check that we have read access to the entry
1300         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, lookupContext );
1301         aciContext.setUserGroupNames( userGroups );
1302         aciContext.setUserDn( userName );
1303         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1304         aciContext.setEntryDn( dn );
1305         aciContext.setMicroOperations( LOOKUP_PERMS );
1306         aciContext.setAciTuples( tuples );
1307         aciContext.setEntry( entry );
1308 
1309         engine.checkPermission( aciContext );
1310 
1311         // check that we have read access to every attribute type and value
1312         for ( Attribute attribute : entry )
1313         {
1314 
1315             for ( Value value : attribute )
1316             {
1317                 AciContextthz/support/AciContext.html#AciContext">AciContext valueAciContext = new AciContext( schemaManager, lookupContext );
1318                 valueAciContext.setUserGroupNames( userGroups );
1319                 valueAciContext.setUserDn( userName );
1320                 valueAciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1321                 valueAciContext.setEntryDn( dn );
1322                 valueAciContext.setAttributeType( attribute.getAttributeType() );
1323                 valueAciContext.setAttrValue( value );
1324                 valueAciContext.setMicroOperations( READ_PERMS );
1325                 valueAciContext.setAciTuples( tuples );
1326                 valueAciContext.setEntry( entry );
1327 
1328                 engine.checkPermission( valueAciContext );
1329             }
1330         }
1331     }
1332 
1333 
1334     public final boolean isPrincipalAnAdministrator( Dn principalDn )
1335     {
1336         return groupCache.isPrincipalAnAdministrator( principalDn.getNormName() );
1337     }
1338 
1339 
1340     public void cacheNewGroup( String name, Entry entry ) throws LdapException
1341     {
1342         groupCache.groupAdded( name, entry );
1343     }
1344 
1345 
1346     private boolean filter( OperationContext opContext, Dn normName, Entry clonedEntry ) throws LdapException
1347     {
1348         /*
1349          * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
1350          * tests.  If we hasPermission() returns false we immediately short the
1351          * process and return false.
1352          */
1353 
1354         LdapPrincipal principal = opContext.getSession().getEffectivePrincipal();
1355         Dn userDn = principal.getDn();
1356         Set<String> userGroups = groupCache.getGroups( userDn.getNormName() );
1357         Collection<ACITuple> tuples = new HashSet<>();
1358         addPerscriptiveAciTuples( opContext, tuples, normName, clonedEntry );
1359         addEntryAciTuples( tuples, ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() );
1360         addSubentryAciTuples( opContext, tuples, normName, ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() );
1361 
1362         AciContextre/authz/support/AciContext.html#AciContext">AciContext aciContext = new AciContext( schemaManager, opContext );
1363         aciContext.setUserGroupNames( userGroups );
1364         aciContext.setUserDn( userDn );
1365         aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1366         aciContext.setEntryDn( normName );
1367         aciContext.setMicroOperations( SEARCH_ENTRY_PERMS );
1368         aciContext.setAciTuples( tuples );
1369         aciContext.setEntry( ( ( ClonedServerEntry ) clonedEntry ).getOriginalEntry() );
1370 
1371         if ( !engine.hasPermission( aciContext ) )
1372         {
1373             return false;
1374         }
1375 
1376         /*
1377          * For each attribute type we check if access is allowed to the type.  If not
1378          * the attribute is yanked out of the entry to be returned.  If permission is
1379          * allowed we move on to check if the values are allowed.  Values that are
1380          * not allowed are removed from the attribute.  If the attribute has no more
1381          * values remaining then the entire attribute is removed.
1382          */
1383         List<AttributeType> attributeToRemove = new ArrayList<>();
1384 
1385         for ( Attribute attribute : clonedEntry.getAttributes() )
1386         {
1387             // if attribute type scope access is not allowed then remove the attribute and continue
1388             AttributeType attributeType = attribute.getAttributeType();
1389             Attribute attr = clonedEntry.get( attributeType );
1390 
1391             aciContext = new AciContext( schemaManager, opContext );
1392             aciContext.setUserGroupNames( userGroups );
1393             aciContext.setUserDn( userDn );
1394             aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1395             aciContext.setEntryDn( normName );
1396             aciContext.setAttributeType( attributeType );
1397             aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS );
1398             aciContext.setAciTuples( tuples );
1399             aciContext.setEntry( clonedEntry );
1400 
1401             if ( !engine.hasPermission( aciContext ) )
1402             {
1403                 attributeToRemove.add( attributeType );
1404 
1405                 continue;
1406             }
1407 
1408             List<Value> valueToRemove = new ArrayList<>();
1409 
1410             // attribute type scope is ok now let's determine value level scope
1411             for ( Value value : attr )
1412             {
1413                 aciContext = new AciContext( schemaManager, opContext );
1414                 aciContext.setUserGroupNames( userGroups );
1415                 aciContext.setUserDn( userDn );
1416                 aciContext.setAuthenticationLevel( principal.getAuthenticationLevel() );
1417                 aciContext.setEntryDn( normName );
1418                 aciContext.setAttributeType( attr.getAttributeType() );
1419                 aciContext.setAttrValue( value );
1420                 aciContext.setMicroOperations( SEARCH_ATTRVAL_PERMS );
1421                 aciContext.setAciTuples( tuples );
1422                 aciContext.setEntry( clonedEntry );
1423 
1424                 if ( !engine.hasPermission( aciContext ) )
1425                 {
1426                     valueToRemove.add( value );
1427                 }
1428             }
1429 
1430             for ( Value value : valueToRemove )
1431             {
1432                 attr.remove( value );
1433             }
1434 
1435             if ( attr.size() == 0 )
1436             {
1437                 attributeToRemove.add( attributeType );
1438             }
1439         }
1440 
1441         for ( AttributeType attributeType : attributeToRemove )
1442         {
1443             clonedEntry.removeAttributes( attributeType );
1444         }
1445 
1446         return true;
1447     }
1448 
1449     /**
1450      * WARNING: create one of these filters fresh every time for each new search.
1451      */
1452     private class AuthorizationFilter implements EntryFilter
1453     {
1454         /**
1455          * {@inheritDoc}
1456          */
1457         @Override
1458         public boolean accept( SearchOperationContext searchContext, Entry entry ) throws LdapException
1459         {
1460             if ( !entry.getDn().isSchemaAware() )
1461             {
1462                 entry.setDn(  new Dn( schemaManager, entry.getDn() ) );
1463             }
1464 
1465             return filter( searchContext, entry.getDn(), entry );
1466         }
1467 
1468 
1469         /**
1470          * {@inheritDoc}
1471          */
1472         @Override
1473         public String toString( String tabs )
1474         {
1475             return tabs + "AuthorizationFilter";
1476         }
1477     }
1478 
1479 
1480     private boolean isTheAdministrator( Dn normalizedDn )
1481     {
1482         return normalizedDn.equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
1483     }
1484 }