001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.server.core.authz;
021
022
023import java.io.IOException;
024import java.text.ParseException;
025import java.util.ArrayList;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Set;
031
032import javax.naming.directory.SearchControls;
033
034import org.apache.directory.api.ldap.aci.ACIItem;
035import org.apache.directory.api.ldap.aci.ACIItemParser;
036import org.apache.directory.api.ldap.aci.ACITuple;
037import org.apache.directory.api.ldap.aci.MicroOperation;
038import org.apache.directory.api.ldap.model.constants.Loggers;
039import org.apache.directory.api.ldap.model.constants.SchemaConstants;
040import org.apache.directory.api.ldap.model.entry.Attribute;
041import org.apache.directory.api.ldap.model.entry.Entry;
042import org.apache.directory.api.ldap.model.entry.Modification;
043import org.apache.directory.api.ldap.model.entry.Value;
044import org.apache.directory.api.ldap.model.exception.LdapException;
045import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
046import org.apache.directory.api.ldap.model.exception.LdapOperationErrorException;
047import org.apache.directory.api.ldap.model.exception.LdapOperationException;
048import org.apache.directory.api.ldap.model.exception.LdapOtherException;
049import org.apache.directory.api.ldap.model.filter.EqualityNode;
050import org.apache.directory.api.ldap.model.filter.ExprNode;
051import org.apache.directory.api.ldap.model.filter.OrNode;
052import org.apache.directory.api.ldap.model.message.AliasDerefMode;
053import org.apache.directory.api.ldap.model.message.SearchScope;
054import org.apache.directory.api.ldap.model.name.Dn;
055import org.apache.directory.api.ldap.model.schema.AttributeType;
056import org.apache.directory.api.ldap.model.schema.normalizers.ConcreteNameComponentNormalizer;
057import org.apache.directory.server.constants.ServerDNConstants;
058import org.apache.directory.server.core.api.CoreSession;
059import org.apache.directory.server.core.api.DirectoryService;
060import org.apache.directory.server.core.api.InterceptorEnum;
061import org.apache.directory.server.core.api.LdapPrincipal;
062import org.apache.directory.server.core.api.entry.ClonedServerEntry;
063import org.apache.directory.server.core.api.entry.ServerEntryUtils;
064import org.apache.directory.server.core.api.filtering.EntryFilter;
065import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
066import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
067import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
068import org.apache.directory.server.core.api.interceptor.context.CompareOperationContext;
069import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
070import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
071import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
072import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
073import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
074import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
075import org.apache.directory.server.core.api.interceptor.context.OperationContext;
076import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
077import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
078import org.apache.directory.server.core.api.partition.Partition;
079import org.apache.directory.server.core.api.partition.PartitionNexus;
080import org.apache.directory.server.core.api.partition.PartitionTxn;
081import org.apache.directory.server.core.api.subtree.SubentryUtils;
082import org.apache.directory.server.core.authz.support.ACDFEngine;
083import org.apache.directory.server.core.authz.support.AciContext;
084import org.apache.directory.server.i18n.I18n;
085import org.slf4j.Logger;
086import org.slf4j.LoggerFactory;
087
088
089/**
090 * An ACI based authorization service.
091 *
092 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
093 */
094public class AciAuthorizationInterceptor extends BaseInterceptor
095{
096    /** the logger for this class */
097    private static final Logger LOG = LoggerFactory.getLogger( AciAuthorizationInterceptor.class );
098
099    /** 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        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        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            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        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        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                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        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        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        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        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        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                        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                        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                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        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        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        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        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        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        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                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        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}