Package org.apache.ldap.server.authz

Source Code of org.apache.ldap.server.authz.AuthorizationService$AuthorizationFilter

/*
*   Copyright 2004 The Apache Software Foundation
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*
*/
package org.apache.ldap.server.authz;


import org.apache.ldap.server.DirectoryServiceConfiguration;
import org.apache.ldap.server.enumeration.SearchResultFilter;
import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
import org.apache.ldap.server.interceptor.BaseInterceptor;
import org.apache.ldap.server.interceptor.NextInterceptor;
import org.apache.ldap.server.interceptor.InterceptorChain;
import org.apache.ldap.server.jndi.ServerContext;
import org.apache.ldap.server.jndi.ServerLdapContext;
import org.apache.ldap.server.configuration.InterceptorConfiguration;
import org.apache.ldap.server.partition.DirectoryPartitionNexus;
import org.apache.ldap.server.partition.DirectoryPartitionNexusProxy;
import org.apache.ldap.server.authz.support.ACDFEngine;
import org.apache.ldap.server.invocation.InvocationStack;
import org.apache.ldap.server.invocation.Invocation;
import org.apache.ldap.server.authn.LdapPrincipal;
import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
import org.apache.ldap.server.schema.AttributeTypeRegistry;
import org.apache.ldap.server.subtree.SubentryService;
import org.apache.ldap.common.filter.ExprNode;
import org.apache.ldap.common.aci.MicroOperation;
import org.apache.ldap.common.aci.ACIItemParser;
import org.apache.ldap.common.aci.ACIItem;
import org.apache.ldap.common.exception.LdapNamingException;
import org.apache.ldap.common.message.ResultCodeEnum;
import org.apache.ldap.common.name.DnParser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.NamingEnumeration;
import javax.naming.directory.*;
import java.util.*;
import java.text.ParseException;


/**
* An ACI based authorization service.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
* @version $Rev: 327983 $
*/
public class AuthorizationService extends BaseInterceptor
{
    /** the logger for this class */
    private static final Logger log = LoggerFactory.getLogger( AuthorizationService.class );
    /** the entry ACI attribute string: entryACI */
    private static final String ENTRYACI_ATTR = "entryACI";
    /** the subentry ACI attribute string: subentryACI */
    private static final String SUBENTRYACI_ATTR = "subentryACI";
    /**
     * the multivalued op attr used to track the perscriptive access control
     * subentries that apply to an entry.
     */
    private static final String AC_SUBENTRY_ATTR = "accessControlSubentries";

    private static final Collection ADD_PERMS;
    private static final Collection READ_PERMS;
    private static final Collection COMPARE_PERMS;
    private static final Collection SEARCH_ENTRY_PERMS;
    private static final Collection SEARCH_ATTRVAL_PERMS;
    private static final Collection REMOVE_PERMS;
    private static final Collection MATCHEDNAME_PERMS;
    private static final Collection BROWSE_PERMS;
    private static final Collection LOOKUP_PERMS;
    private static final Collection REPLACE_PERMS;
    private static final Collection RENAME_PERMS;
    private static final Collection EXPORT_PERMS;
    private static final Collection IMPORT_PERMS;
    private static final Collection MOVERENAME_PERMS;


    static
    {
        HashSet set = new HashSet( 2 );
        set.add( MicroOperation.BROWSE );
        set.add( MicroOperation.RETURN_DN );
        SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );

        set = new HashSet( 2 );
        set.add( MicroOperation.READ );
        set.add( MicroOperation.BROWSE );
        LOOKUP_PERMS = Collections.unmodifiableCollection( set );

        set = new HashSet( 2 );
        set.add( MicroOperation.ADD );
        set.add( MicroOperation.REMOVE );
        REPLACE_PERMS = Collections.unmodifiableCollection( set );

        set = new HashSet( 3 );
        set.add( MicroOperation.IMPORT );
        set.add( MicroOperation.EXPORT );
        set.add( MicroOperation.RENAME );
        MOVERENAME_PERMS = Collections.unmodifiableCollection( set );

        SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
        ADD_PERMS = Collections.singleton( MicroOperation.ADD );
        READ_PERMS = Collections.singleton( MicroOperation.READ );
        COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE );
        REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE );
        MATCHEDNAME_PERMS = Collections.singleton( MicroOperation.DISCLOSE_ON_ERROR );
        BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE );
        RENAME_PERMS = Collections.singleton( MicroOperation.RENAME );
        EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT );
        IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT );
    }

    /** a tupleCache that responds to add, delete, and modify attempts */
    private TupleCache tupleCache;
    /** a groupCache that responds to add, delete, and modify attempts */
    private GroupCache groupCache;
    /** a normalizing ACIItem parser */
    private ACIItemParser aciParser;
    /** use and instance of the ACDF engine */
    private ACDFEngine engine;
    /** interceptor chain */
    private InterceptorChain chain;
    /** attribute type registry */
    private AttributeTypeRegistry attrRegistry;
    /** whether or not this interceptor is activated */
    private boolean enabled = false;


    /**
     * Initializes this interceptor based service by getting a handle on the nexus, setting up
     * the tupe and group membership caches and the ACIItem parser and the ACDF engine.
     *
     * @param factoryCfg the ContextFactory configuration for the server
     * @param cfg the interceptor configuration
     * @throws NamingException if there are problems during initialization
     */
    public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
    {
        super.init( factoryCfg, cfg );
        tupleCache = new TupleCache( factoryCfg );
        groupCache = new GroupCache( factoryCfg );
        attrRegistry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
        aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( attrRegistry ) );
        engine = new ACDFEngine( factoryCfg.getGlobalRegistries().getOidRegistry(), attrRegistry );
        chain = factoryCfg.getInterceptorChain();
        enabled = factoryCfg.getStartupConfiguration().isAccessControlEnabled();
    }


    /**
     * Adds perscriptiveACI tuples to a collection of tuples by accessing the
     * tupleCache.  The tuple cache is accessed for each A/C subentry
     * associated with the protected entry.  Note that subentries are handled
     * differently: their parent, the administrative entry is accessed to
     * determine the perscriptiveACIs effecting the AP and hence the subentry
     * which is considered to be in the same context.
     *
     * @param tuples the collection of tuples to add to
     * @param dn the normalized distinguished name of the protected entry
     * @param entry the target entry that access to is being controled
     * @throws NamingException if there are problems accessing attribute values
     */
    private void addPerscriptiveAciTuples( DirectoryPartitionNexusProxy proxy, Collection tuples,
                                           Name dn, Attributes entry )
            throws NamingException
    {
        /*
         * If the protected entry is a subentry, then the entry being evaluated
         * for perscriptiveACIs is in fact the administrative entry.  By
         * substituting the administrative entry for the actual subentry the
         * code below this "if" statement correctly evaluates the effects of
         * perscriptiveACI on the subentry.  Basically subentries are considered
         * to be in the same naming context as their access point so the subentries
         * effecting their parent entry applies to them as well.
         */
        if ( entry.get( "objectClass" ).contains( "subentry" ) )
        {
            Name parentDn = ( Name ) dn.clone();
            parentDn.remove( dn.size() - 1 );
            entry = proxy.lookup( parentDn, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        }

        Attribute subentries = entry.get( AC_SUBENTRY_ATTR );
        if ( subentries == null )
        {
            return;
        }
        for ( int ii = 0; ii < subentries.size(); ii++ )
        {
            String subentryDn = ( String ) subentries.get( ii );
            tuples.addAll( tupleCache.getACITuples( subentryDn ) );
        }
    }


    /**
     * Adds the set of entryACI tuples to a collection of tuples.  The entryACI
     * is parsed and tuples are generated on they fly then added to the collection.
     *
     * @param tuples the collection of tuples to add to
     * @param entry the target entry that access to is being regulated
     * @throws NamingException if there are problems accessing attribute values
     */
    private void addEntryAciTuples( Collection tuples, Attributes entry ) throws NamingException
    {
        Attribute entryAci = entry.get( ENTRYACI_ATTR );
        if ( entryAci == null )
        {
            return;
        }

        for ( int ii = 0; ii < entryAci.size(); ii++ )
        {
            String aciString = ( String ) entryAci.get( ii );
            ACIItem item;

            try
            {
                item = aciParser.parse( aciString );
            }
            catch ( ParseException e )
            {
                String msg = "failed to parse entryACI: " + aciString ;
                log.error( msg, e );
                throw new LdapNamingException( msg, ResultCodeEnum.OPERATIONSERROR );
            }

            tuples.addAll( item.toTuples() );
        }
    }


    /**
     * Adds the set of subentryACI tuples to a collection of tuples.  The subentryACI
     * is parsed and tuples are generated on the fly then added to the collection.
     *
     * @param tuples the collection of tuples to add to
     * @param dn the normalized distinguished name of the protected entry
     * @param entry the target entry that access to is being regulated
     * @throws NamingException if there are problems accessing attribute values
     */
    private void addSubentryAciTuples( DirectoryPartitionNexusProxy proxy, Collection tuples,
                                       Name dn, Attributes entry ) throws NamingException
    {
        // only perform this for subentries
        if ( ! entry.get("objectClass").contains("subentry") )
        {
            return;
        }

        // get the parent or administrative entry for this subentry since it
        // will contain the subentryACI attributes that effect subentries
        Name parentDn = ( Name ) dn.clone();
        parentDn.remove( dn.size() - 1 );
        Attributes administrativeEntry = proxy.lookup( parentDn, new String[] { SUBENTRYACI_ATTR },
                DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        Attribute subentryAci = administrativeEntry.get( SUBENTRYACI_ATTR );

        if ( subentryAci == null )
        {
            return;
        }

        for ( int ii = 0; ii < subentryAci.size(); ii++ )
        {
            String aciString = ( String ) subentryAci.get( ii );
            ACIItem item;

            try
            {
                item = aciParser.parse( aciString );
            }
            catch ( ParseException e )
            {
                String msg = "failed to parse subentryACI: " + aciString ;
                log.error( msg, e );
                throw new LdapNamingException( msg, ResultCodeEnum.OPERATIONSERROR );
            }

            tuples.addAll( item.toTuples() );
        }
    }


    /* -------------------------------------------------------------------------------
     * Within every access controled interceptor method we must retrieve the ACITuple
     * set for all the perscriptiveACIs that apply to the candidate, the target entry
     * operated upon.  This ACITuple set is gotten from the TupleCache by looking up
     * the subentries referenced by the accessControlSubentries operational attribute
     * within the target entry.
     *
     * Then the entry is inspected for an entryACI.  This is not done for the add op
     * since it could introduce a security breech.  So for non-add ops if present a
     * set of ACITuples are generated for all the entryACIs within the entry.  This
     * set is combined with the ACITuples cached for the perscriptiveACI affecting
     * the target entry.  If the entry is a subentry the ACIs are also processed for
     * the subentry to generate more ACITuples.  This subentry TupleACI set is joined
     * with the entry and perscriptive ACI.
     *
     * The union of ACITuples are fed into the engine along with other parameters
     * to decide whether a permission is granted or rejected for the specific
     * operation.
     * -------------------------------------------------------------------------------
     */

    public void add( NextInterceptor next, String upName, Name normName, Attributes entry ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.add( upName, normName, entry );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.add( upName, normName, entry );
            tupleCache.subentryAdded( upName, normName, entry );
            groupCache.groupAdded( upName, normName, entry );
            return;
        }

        // perform checks below here for all non-admin users
        SubentryService subentryService = ( SubentryService ) chain.get( "subentryService" );
        Attributes subentryAttrs = subentryService.getSubentryAttributes( normName, entry );
        NamingEnumeration attrList = entry.getAll();
        while( attrList.hasMore() )
        {
            subentryAttrs.put( ( Attribute ) attrList.next() );
        }

        // Assemble all the information required to make an access control decision
        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();

        // Build the total collection of tuples to be considered for add rights
        // NOTE: entryACI are NOT considered in adds (it would be a security breech)
        addPerscriptiveAciTuples( invocation.getProxy(), tuples, normName, subentryAttrs );
        addSubentryAciTuples( invocation.getProxy(), tuples, normName, subentryAttrs );

        // check if entry scope permission is granted
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                normName, null, null, ADD_PERMS, tuples, subentryAttrs );

        // now we must check if attribute type and value scope permission is granted
        NamingEnumeration attributeList = entry.getAll();
        while ( attributeList.hasMore() )
        {
            Attribute attr = ( Attribute ) attributeList.next();
            for ( int ii = 0; ii < attr.size(); ii++ )
            {
                engine.checkPermission( proxy, userGroups, user.getJndiName(),
                        user.getAuthenticationLevel(), normName, attr.getID(),
                        attr.get( ii ), ADD_PERMS, tuples, entry );
            }
        }

        // if we've gotten this far then access has been granted
        next.add( upName, normName, entry );

        // if the entry added is a subentry or a groupOf[Unique]Names we must
        // update the ACITuple cache and the groups cache to keep them in sync
        tupleCache.subentryAdded( upName, normName, entry );
        groupCache.groupAdded( upName, normName, entry );
    }


    public void delete( NextInterceptor next, Name name ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.delete( name );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.delete( name );
            tupleCache.subentryDeleted( name, entry );
            groupCache.groupDeleted( name, entry );
            return;
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, name, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, name, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
                null, REMOVE_PERMS, tuples, entry );

        next.delete( name );
        tupleCache.subentryDeleted( name, entry );
        groupCache.groupDeleted( name, entry );
    }


    public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.modify( name, modOp, mods );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.modify( name, modOp, mods );
            tupleCache.subentryModified( name, modOp, mods, entry );
            groupCache.groupModified( name, modOp, mods, entry );
            return;
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, name, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, name, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
                null, Collections.singleton( MicroOperation.MODIFY ), tuples, entry );

        NamingEnumeration attrList = mods.getAll();
        Collection perms = null;
        switch( modOp )
        {
            case( DirContext.ADD_ATTRIBUTE ):
                perms = ADD_PERMS;
                break;
            case( DirContext.REMOVE_ATTRIBUTE ):
                perms = REMOVE_PERMS;
                break;
            case( DirContext.REPLACE_ATTRIBUTE ):
                perms = REPLACE_PERMS;
                break;
        }

        while( attrList.hasMore() )
        {
            Attribute attr = ( Attribute ) attrList.next();
            for ( int ii = 0; ii < attr.size(); ii++ )
            {
                engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                        name, attr.getID(), attr.get( ii ), perms, tuples, entry );
            }
        }

        next.modify( name, modOp, mods );
        tupleCache.subentryModified( name, modOp, mods, entry );
        groupCache.groupModified( name, modOp, mods, entry );
    }


    public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.modify( name, mods );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.modify( name, mods );
            tupleCache.subentryModified( name, mods, entry );
            groupCache.groupModified( name, mods, entry );
            return;
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, name, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, name, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
                null, Collections.singleton( MicroOperation.MODIFY ), tuples, entry );

        Collection perms = null;
        for ( int ii = 0; ii < mods.length; ii++ )
        {
            switch( mods[ii].getModificationOp() )
            {
                case( DirContext.ADD_ATTRIBUTE ):
                    perms = ADD_PERMS;
                    break;
                case( DirContext.REMOVE_ATTRIBUTE ):
                    perms = REMOVE_PERMS;
                    break;
                case( DirContext.REPLACE_ATTRIBUTE ):
                    perms = REPLACE_PERMS;
                    break;
            }

            Attribute attr = mods[ii].getAttribute();
            for ( int jj = 0; jj < attr.size(); jj++ )
            {
                engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                        name, attr.getID(), attr.get( jj ), perms, tuples, entry );
            }
        }

        next.modify( name, mods );
        tupleCache.subentryModified( name, mods, entry );
        groupCache.groupModified( name, mods, entry );
    }


    public boolean hasEntry( NextInterceptor next, Name name ) throws NamingException
    {
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return next.hasEntry( name );
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, name, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, name, entry );

        // check that we have browse access to the entry
        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
                null, BROWSE_PERMS, tuples, entry );

        return next.hasEntry( name );
    }


    /**
     * Checks if the READ permissions exist to the entry and to each attribute type and
     * value.
     *
     * @todo not sure if we should hide attribute types/values or throw an exception
     * instead.  I think we're going to have to use a filter to restrict the return
     * of attribute types and values instead of throwing an exception.  Lack of read
     * perms to attributes and their values results in their removal when returning
     * the entry.
     *
     * @param user the user associated with the call
     * @param dn the name of the entry being looked up
     * @param entry the raw entry pulled from the nexus
     * @throws NamingException
     */
    private void checkLookupAccess( LdapPrincipal user, Name dn, Attributes entry )
            throws NamingException
    {
        DirectoryPartitionNexusProxy proxy = InvocationStack.getInstance().peek().getProxy();
        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, dn, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, dn, entry );

        // check that we have read access to the entry
        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn, null,
                null, LOOKUP_PERMS, tuples, entry );

        // check that we have read access to every attribute type and value
        NamingEnumeration attributeList = entry.getAll();
        while ( attributeList.hasMore() )
        {
            Attribute attr = ( Attribute ) attributeList.next();
            for ( int ii = 0; ii < attr.size(); ii++ )
            {
                engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn,
                        attr.getID(), attr.get( ii ), READ_PERMS, tuples, entry );
            }
        }
    }


    public Attributes lookup( NextInterceptor next, Name dn, String[] attrIds ) throws NamingException
    {
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( dn, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return next.lookup( dn, attrIds );
        }

        checkLookupAccess( user, dn, entry );

        return next.lookup( dn, attrIds );
    }


    public Attributes lookup( NextInterceptor next, Name name ) throws NamingException
    {
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return next.lookup( name );
        }

        checkLookupAccess( user, name, entry );

        return next.lookup( name );
    }


    public void modifyRn( NextInterceptor next, Name name, String newRn, boolean deleteOldRn ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
        Name newName = ( Name ) name.clone();
        newName.remove( name.size() - 1 );
        newName.add( newRn );


        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.modifyRn( name, newRn, deleteOldRn );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.modifyRn( name, newRn, deleteOldRn );
            tupleCache.subentryRenamed( name, newName );
            groupCache.groupRenamed( name, newName );
            return;
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, name, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, name, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
                null, RENAME_PERMS, tuples, entry );

//        if ( deleteOldRn )
//        {
//            String oldRn = name.get( name.size() - 1 );
//            if ( NamespaceTools.hasCompositeComponents( oldRn ) )
//            {
//                String[] comps = NamespaceTools.getCompositeComponents( oldRn );
//                for ( int ii = 0; ii < comps.length; ii++ )
//                {
//                    String id = NamespaceTools.getRdnAttribute( comps[ii] );
//                    String value = NamespaceTools.getRdnValue( comps[ii] );
//                    engine.checkPermission( next, userGroups, user.getJndiName(),
//                            user.getAuthenticationLevel(), name, id,
//                            value, Collections.singleton( MicroOperation.REMOVE ),
//                            tuples, entry );
//                }
//            }
//            else
//            {
//                String id = NamespaceTools.getRdnAttribute( oldRn );
//                String value = NamespaceTools.getRdnValue( oldRn );
//                engine.checkPermission( next, userGroups, user.getJndiName(),
//                        user.getAuthenticationLevel(), name, id,
//                        value, Collections.singleton( MicroOperation.REMOVE ),
//                        tuples, entry );
//            }
//        }

        next.modifyRn( name, newRn, deleteOldRn );
        tupleCache.subentryRenamed( name, newName );
        groupCache.groupRenamed( name, newName );
    }


    public void move( NextInterceptor next, Name oriChildName, Name newParentName, String newRn, boolean deleteOldRn )
            throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( oriChildName, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
        Name newName = ( Name ) newParentName.clone();
        newName.add( newRn );

        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.move( oriChildName, newParentName, newRn, deleteOldRn );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.move( oriChildName, newParentName, newRn, deleteOldRn );
            tupleCache.subentryRenamed( oriChildName, newName );
            groupCache.groupRenamed( oriChildName, newName );
            return;
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, oriChildName, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, oriChildName, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                oriChildName, null, null, MOVERENAME_PERMS, tuples, entry );

        Collection destTuples = new HashSet();
        addPerscriptiveAciTuples( proxy, destTuples, oriChildName, entry );
        addEntryAciTuples( destTuples, entry );
        addSubentryAciTuples( proxy, destTuples, oriChildName, entry );
        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                oriChildName, null, null, IMPORT_PERMS, tuples, entry );

//        if ( deleteOldRn )
//        {
//            String oldRn = oriChildName.get( oriChildName.size() - 1 );
//            if ( NamespaceTools.hasCompositeComponents( oldRn ) )
//            {
//                String[] comps = NamespaceTools.getCompositeComponents( oldRn );
//                for ( int ii = 0; ii < comps.length; ii++ )
//                {
//                    String id = NamespaceTools.getRdnAttribute( comps[ii] );
//                    String value = NamespaceTools.getRdnValue( comps[ii] );
//                    engine.checkPermission( next, userGroups, user.getJndiName(),
//                            user.getAuthenticationLevel(), oriChildName, id,
//                            value, Collections.singleton( MicroOperation.REMOVE ),
//                            tuples, entry );
//                }
//            }
//            else
//            {
//                String id = NamespaceTools.getRdnAttribute( oldRn );
//                String value = NamespaceTools.getRdnValue( oldRn );
//                engine.checkPermission( next, userGroups, user.getJndiName(),
//                        user.getAuthenticationLevel(), oriChildName, id,
//                        value, Collections.singleton( MicroOperation.REMOVE ),
//                        tuples, entry );
//            }
//        }

        next.move( oriChildName, newParentName, newRn, deleteOldRn );
        tupleCache.subentryRenamed( oriChildName, newName );
        groupCache.groupRenamed( oriChildName, newName );
    }


    public void move( NextInterceptor next, Name oriChildName, Name newParentName ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( oriChildName, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        Name newName = ( Name ) newParentName.clone();
        newName.add( oriChildName.get( oriChildName.size() - 1 ) );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();

        // bypass authz code if we are disabled
        if ( ! enabled )
        {
            next.move( oriChildName, newParentName );
            return;
        }

        // bypass authz code but manage caches if operation is performed by the admin
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
        {
            next.move( oriChildName, newParentName );
            tupleCache.subentryRenamed( oriChildName, newName );
            groupCache.groupRenamed( oriChildName, newName );
            return;
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, oriChildName, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, oriChildName, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                oriChildName, null, null, EXPORT_PERMS, tuples, entry );

        Collection destTuples = new HashSet();
        addPerscriptiveAciTuples( proxy, destTuples, oriChildName, entry );
        addEntryAciTuples( destTuples, entry );
        addSubentryAciTuples( proxy, destTuples, oriChildName, entry );
        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
                oriChildName, null, null, IMPORT_PERMS, tuples, entry );

        next.move( oriChildName, newParentName );
        tupleCache.subentryRenamed( oriChildName, newName );
        groupCache.groupRenamed( oriChildName, newName );
    }


    public static final SearchControls DEFUALT_SEARCH_CONTROLS = new SearchControls();

    public NamingEnumeration list( NextInterceptor next, Name base ) throws NamingException
    {
        Invocation invocation = InvocationStack.getInstance().peek();
        ServerLdapContext ctx = ( ServerLdapContext ) invocation.getCaller();
        LdapPrincipal user = ctx.getPrincipal();
        NamingEnumeration e = next.list( base );
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return e;
        }
        AuthorizationFilter authzFilter = new AuthorizationFilter();
        return new SearchResultFilteringEnumeration( e, DEFUALT_SEARCH_CONTROLS, invocation, authzFilter );
    }


    public NamingEnumeration search( NextInterceptor next, Name base, Map env, ExprNode filter,
                                     SearchControls searchCtls ) throws NamingException
    {
        Invocation invocation = InvocationStack.getInstance().peek();
        ServerLdapContext ctx = ( ServerLdapContext ) invocation.getCaller();
        LdapPrincipal user = ctx.getPrincipal();
        NamingEnumeration e = next.search( base, env, filter, searchCtls );
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return e;
        }
        AuthorizationFilter authzFilter = new AuthorizationFilter();
        return new SearchResultFilteringEnumeration( e, searchCtls, invocation, authzFilter );
    }


    public boolean compare( NextInterceptor next, Name name, String oid, Object value ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return next.compare( name, oid, value );
        }

        Set userGroups = groupCache.getGroups( user.getName() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( proxy, tuples, name, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( proxy, tuples, name, entry );

        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
                null, READ_PERMS, tuples, entry );
        engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, oid,
                value, COMPARE_PERMS, tuples, entry );

        return next.compare( name, oid, value );
    }


    public Name getMatchedName( NextInterceptor next, Name dn, boolean normalized ) throws NamingException
    {
        // Access the principal requesting the operation, and bypass checks if it is the admin
        Invocation invocation = InvocationStack.getInstance().peek();
        DirectoryPartitionNexusProxy proxy = invocation.getProxy();
        LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
        if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
        {
            return next.getMatchedName( dn, normalized );
        }

        // get the present matched name
        Attributes entry;
        Name matched = next.getMatchedName( dn, normalized );

        // check if we have disclose on error permission for the entry at the matched dn
        // if not remove rdn and check that until nothing is left in the name and return
        // that but if permission is granted then short the process and return the dn
        while ( matched.size() > 0 )
        {
            if ( normalized )
            {
                entry = proxy.lookup( matched, DirectoryPartitionNexusProxy.GETMATCHEDDN_BYPASS );
            }
            else
            {
                entry = proxy.lookup( matched, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
            }

            Set userGroups = groupCache.getGroups( user.getName() );
            Collection tuples = new HashSet();
            addPerscriptiveAciTuples( proxy, tuples, matched, entry );
            addEntryAciTuples( tuples, entry );
            addSubentryAciTuples( proxy, tuples, matched, entry );

            if ( engine.hasPermission( proxy, userGroups, user.getJndiName(),
                    user.getAuthenticationLevel(), matched, null, null,
                    MATCHEDNAME_PERMS, tuples, entry ) )
            {
                return matched;
            }

            matched.remove( matched.size() - 1 );
        }

        return matched;
    }


    public void cacheNewGroup( String upName, Name normName, Attributes entry ) throws NamingException
    {
        this.groupCache.groupAdded( upName, normName, entry );
    }


    private boolean filter( Invocation invocation, Name normName, SearchResult result ) throws NamingException
    {
       /*
        * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
        * tests.  If we hasPermission() returns false we immediately short the
        * process and return false.
        */
        Attributes entry = invocation.getProxy().lookup( normName, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
        ServerLdapContext ctx = ( ServerLdapContext ) invocation.getCaller();
        Name userDn = ctx.getPrincipal().getJndiName();
        Set userGroups = groupCache.getGroups( userDn.toString() );
        Collection tuples = new HashSet();
        addPerscriptiveAciTuples( invocation.getProxy(), tuples, normName, entry );
        addEntryAciTuples( tuples, entry );
        addSubentryAciTuples( invocation.getProxy(), tuples, normName, entry );

        if ( ! engine.hasPermission( invocation.getProxy(), userGroups, userDn,
                ctx.getPrincipal().getAuthenticationLevel(),
                normName, null, null, SEARCH_ENTRY_PERMS, tuples, entry ) )
        {
            return false;
        }

        /*
         * For each attribute type we check if access is allowed to the type.  If not
         * the attribute is yanked out of the entry to be returned.  If permission is
         * allowed we move on to check if the values are allowed.  Values that are
         * not allowed are removed from the attribute.  If the attribute has no more
         * values remaining then the entire attribute is removed.
         */
        NamingEnumeration idList = result.getAttributes().getIDs();
        while ( idList.hasMore() )
        {
            // if attribute type scope access is not allowed then remove the attribute and continue
            String id = ( String ) idList.next();
            Attribute attr = result.getAttributes().get( id );
            if ( ! engine.hasPermission( invocation.getProxy(), userGroups, userDn,
                    ctx.getPrincipal().getAuthenticationLevel(),
                    normName, attr.getID(), null, SEARCH_ATTRVAL_PERMS, tuples, entry ) )
            {
                result.getAttributes().remove( attr.getID() );

                if ( attr.size() == 0 )
                {
                    result.getAttributes().remove( attr.getID() );
                }
                continue;
            }

            // attribute type scope is ok now let's determine value level scope
            for ( int ii = 0; ii < attr.size(); ii++ )
            {
                if ( ! engine.hasPermission( invocation.getProxy(), userGroups, userDn,
                        ctx.getPrincipal().getAuthenticationLevel(), normName,
                        attr.getID(), attr.get( ii ), SEARCH_ATTRVAL_PERMS, tuples, entry ) )
                {
                    attr.remove( ii );

                    if ( ii > 0 )
                    {
                        ii--;
                    }
                }
            }
        }

        return true;
    }


    /**
     * WARNING: create one of these filters fresh every time for each new search.
     */
    class AuthorizationFilter implements SearchResultFilter
    {
        /** dedicated normalizing parser for this search - cheaper than synchronization */
        final DnParser parser;

        public AuthorizationFilter() throws NamingException
        {
            parser = new DnParser( new ConcreteNameComponentNormalizer( attrRegistry ) );
        }


        public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
                throws NamingException
        {
            Name normName = parser.parse( result.getName() );

// looks like isRelative returns true even when the names for results are absolute!!!!
// @todo this is a big bug in JNDI provider

//            if ( result.isRelative() )
//            {
//                Name base = parser.parse( ctx.getNameInNamespace() );
//                normName = base.addAll( normName );
//            }

            return filter( invocation, normName, result );
        }
    }
}
TOP

Related Classes of org.apache.ldap.server.authz.AuthorizationService$AuthorizationFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.