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.support;
021
022
023import java.util.ArrayList;
024import java.util.Collection;
025
026import org.apache.directory.api.ldap.aci.ACITuple;
027import org.apache.directory.api.ldap.model.constants.SchemaConstants;
028import org.apache.directory.api.ldap.model.entry.Entry;
029import org.apache.directory.api.ldap.model.exception.LdapException;
030import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
031import org.apache.directory.api.ldap.model.schema.SchemaManager;
032import org.apache.directory.server.core.api.CoreSession;
033import org.apache.directory.server.core.api.event.Evaluator;
034import org.apache.directory.server.core.api.event.ExpressionEvaluator;
035import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
036import org.apache.directory.server.core.api.subtree.SubtreeEvaluator;
037import org.apache.directory.server.core.api.subtree.RefinementEvaluator;
038import org.apache.directory.server.core.api.subtree.RefinementLeafEvaluator;
039
040
041/**
042 * An implementation of Access Control Decision Function (18.8, X.501).
043 * <br>
044 * This engine simply filters the collection of tuples using the following
045 * {@link ACITupleFilter}s sequentially:
046 * <ol>
047 * <li>{@link RelatedUserClassFilter}</li>
048 * <li>{@link RelatedProtectedItemFilter}</li>
049 * <li>{@link MaxValueCountFilter}</li>
050 * <li>{@link MaxImmSubFilter}</li>
051 * <li>{@link RestrictedByFilter}</li>
052 * <li>{@link MicroOperationFilter}</li>
053 * <li>{@link HighestPrecedenceFilter}</li>
054 * <li>{@link MostSpecificUserClassFilter}</li>
055 * <li>{@link MostSpecificProtectedItemFilter}</li>
056 * </ol>
057 * <br>
058 * Operation is determined to be permitted if and only if there is at least one
059 * tuple left and all of them grants the access. (18.8.4. X.501)
060 *
061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
062 */
063public class ACDFEngine
064{
065    private final ACITupleFilter[] filters;
066
067
068    /**
069     * Creates a new instance.
070     *
071     * @param schemaManager The server schemaManager
072     */
073    public ACDFEngine( SchemaManager schemaManager )
074    {
075        Evaluator entryEvaluator = new ExpressionEvaluator( schemaManager );
076        SubtreeEvaluator subtreeEvaluator = new SubtreeEvaluator( schemaManager );
077        RefinementEvaluator refinementEvaluator = new RefinementEvaluator( new RefinementLeafEvaluator( schemaManager ) );
078
079        filters = new ACITupleFilter[]
080            {
081                new RelatedUserClassFilter( subtreeEvaluator ),
082                new RelatedProtectedItemFilter( refinementEvaluator, entryEvaluator, schemaManager ),
083                new MaxValueCountFilter(),
084                new MaxImmSubFilter( schemaManager ),
085                new RestrictedByFilter(),
086                new MicroOperationFilter(),
087                new HighestPrecedenceFilter(),
088                new MostSpecificUserClassFilter(),
089                new MostSpecificProtectedItemFilter() };
090    }
091
092
093    /**
094     * Checks the user with the specified name can access the specified resource
095     * (entry, attribute type, or attribute value) and throws {@link LdapNoPermissionException}
096     * if the user doesn't have any permission to perform the specified grants.
097     *
098     * @param aciContext the container for ACI items
099     * @throws LdapException if failed to evaluate ACI items
100     */
101    public void checkPermission( AciContext aciContext ) throws LdapException
102    {
103        if ( !hasPermission( aciContext ) )
104        {
105            throw new LdapNoPermissionException();
106        }
107    }
108
109
110    /**
111     * Returns <tt>true</tt> if the user with the specified name can access the specified resource
112     * (entry, attribute type, or attribute value) and throws {@link org.apache.directory.api.ldap.model.exception.LdapNoPermissionException}
113     * if the user doesn't have any permission to perform the specified grants.
114     *
115     * @param aciContext the container for ACI items
116     * @return <tt>true</tt> if the user has permission to access the resource
117     * @throws LdapException if failed to evaluate ACI items
118     */
119    public boolean hasPermission( AciContext aciContext ) throws LdapException
120    {
121        if ( aciContext.getEntryDn() == null )
122        {
123            throw new IllegalArgumentException( "entryName" );
124        }
125
126        CoreSession session = aciContext.getOperationContext().getSession();
127        LookupOperationContext lookupContext = new LookupOperationContext( session, aciContext.getUserDn(),
128            SchemaConstants.ALL_ATTRIBUTES_ARRAY );
129        lookupContext.setPartition( aciContext.getOperationContext().getPartition() );
130        lookupContext.setTransaction( aciContext.getOperationContext().getTransaction() );
131        
132        Entry userEntry = session.getDirectoryService().getPartitionNexus().lookup( lookupContext );
133
134        // Determine the scope of the requested operation.
135        OperationScope scope;
136
137        if ( aciContext.getAttributeType() == null )
138        {
139            scope = OperationScope.ENTRY;
140        }
141        else if ( aciContext.getAttrValue() == null )
142        {
143            scope = OperationScope.ATTRIBUTE_TYPE;
144        }
145        else
146        {
147            scope = OperationScope.ATTRIBUTE_TYPE_AND_VALUE;
148        }
149
150        // Clone aciTuples in case it is unmodifiable.
151        aciContext.setAciTuples( new ArrayList<ACITuple>( aciContext.getAciTuples() ) );
152
153        // Filter unrelated and invalid tuples
154        for ( ACITupleFilter filter : filters )
155        {
156            if ( aciContext.getAciTuples().isEmpty() )
157            {
158                // No need to continue filtering
159                return false;
160            }
161
162            Collection<ACITuple> aciTuples = filter.filter( aciContext, scope, userEntry );
163            aciContext.setAciTuples( aciTuples );
164        }
165
166        // Deny access if no tuples left.
167        if ( aciContext.getAciTuples().isEmpty() )
168        {
169            return false;
170        }
171
172        // Grant access if and only if one or more tuples remain and
173        // all grant access. Otherwise deny access.
174        for ( ACITuple tuple : aciContext.getAciTuples() )
175        {
176            if ( !tuple.isGrant() )
177            {
178                return false;
179            }
180        }
181
182        return true;
183    }
184}