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     */
020    package org.apache.directory.server.core.authz.support;
021    
022    
023    import java.util.ArrayList;
024    import java.util.Collection;
025    import java.util.Collections;
026    import java.util.HashSet;
027    
028    import javax.naming.NamingException;
029    
030    import org.apache.directory.server.core.authn.AuthenticationInterceptor;
031    import org.apache.directory.server.core.authz.AciAuthorizationInterceptor;
032    import org.apache.directory.server.core.authz.DefaultAuthorizationInterceptor;
033    import org.apache.directory.server.core.event.Evaluator;
034    import org.apache.directory.server.core.event.EventInterceptor;
035    import org.apache.directory.server.core.event.ExpressionEvaluator;
036    import org.apache.directory.server.core.interceptor.context.OperationContext;
037    import org.apache.directory.server.core.normalization.NormalizationInterceptor;
038    import org.apache.directory.server.core.operational.OperationalAttributeInterceptor;
039    import org.apache.directory.server.core.schema.SchemaInterceptor;
040    import org.apache.directory.server.core.subtree.RefinementEvaluator;
041    import org.apache.directory.server.core.subtree.RefinementLeafEvaluator;
042    import org.apache.directory.server.core.subtree.SubentryInterceptor;
043    import org.apache.directory.server.core.subtree.SubtreeEvaluator;
044    import org.apache.directory.server.core.trigger.TriggerInterceptor;
045    import org.apache.directory.shared.ldap.aci.ACITuple;
046    import org.apache.directory.shared.ldap.aci.MicroOperation;
047    import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
048    import org.apache.directory.shared.ldap.entry.ServerEntry;
049    import org.apache.directory.shared.ldap.entry.Value;
050    import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
051    import org.apache.directory.shared.ldap.name.DN;
052    import org.apache.directory.shared.ldap.schema.SchemaManager;
053    import org.apache.directory.shared.ldap.schema.registries.OidRegistry;
054    
055    
056    /**
057     * An implementation of Access Control Decision Function (18.8, X.501).
058     * <p>
059     * This engine simply filters the collection of tuples using the following
060     * {@link ACITupleFilter}s sequentially:
061     * <ol>
062     * <li>{@link RelatedUserClassFilter}</li>
063     * <li>{@link RelatedProtectedItemFilter}</li>
064     * <li>{@link MaxValueCountFilter}</li>
065     * <li>{@link MaxImmSubFilter}</li>
066     * <li>{@link RestrictedByFilter}</li>
067     * <li>{@link MicroOperationFilter}</li>
068     * <li>{@link HighestPrecedenceFilter}</li>
069     * <li>{@link MostSpecificUserClassFilter}</li>
070     * <li>{@link MostSpecificProtectedItemFilter}</li>
071     * </ol>
072     * <p>
073     * Operation is determined to be permitted if and only if there is at least one
074     * tuple left and all of them grants the access. (18.8.4. X.501)
075     * 
076     * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
077     * @version $Rev: 927146 $, $Date: 2010-03-24 19:39:54 +0100 (Wed, 24 Mar 2010) $
078     */
079    public class ACDFEngine
080    {
081        private final ACITupleFilter[] filters;
082    
083    
084        /**
085         * Creates a new instance.
086         * 
087         * @param oidRegistry an OID registry to be used by internal components
088         * @param attrTypeRegistry an attribute type registry to be used by internal components 
089         * 
090         * @throws NamingException if failed to initialize internal components
091         */
092        public ACDFEngine( OidRegistry oidRegistry, SchemaManager schemaManager ) throws NamingException
093        {
094            Evaluator entryEvaluator = new ExpressionEvaluator( oidRegistry, schemaManager );
095            SubtreeEvaluator subtreeEvaluator = new SubtreeEvaluator( oidRegistry, schemaManager );
096            RefinementEvaluator refinementEvaluator = new RefinementEvaluator( new RefinementLeafEvaluator( oidRegistry ) );
097    
098            filters = new ACITupleFilter[] {
099                new RelatedUserClassFilter( subtreeEvaluator ),
100                new RelatedProtectedItemFilter( refinementEvaluator, entryEvaluator, oidRegistry, schemaManager ),
101                new MaxValueCountFilter(),
102                new MaxImmSubFilter(),
103                new RestrictedByFilter(),
104                new MicroOperationFilter(),
105                new HighestPrecedenceFilter(),
106                new MostSpecificUserClassFilter(),
107                new MostSpecificProtectedItemFilter() };
108        }
109    
110    
111        /**
112         * Checks the user with the specified name can access the specified resource
113         * (entry, attribute type, or attribute value) and throws {@link LdapNoPermissionException}
114         * if the user doesn't have any permission to perform the specified grants.
115         * 
116         * @param proxy the proxy to the partition nexus
117         * @param userGroupNames the collection of the group DNs the user who is trying to access the resource belongs
118         * @param username the DN of the user who is trying to access the resource
119         * @param entryName the DN of the entry the user is trying to access
120         * @param attrId the attribute type of the attribute the user is trying to access.
121         *               <tt>null</tt> if the user is not accessing a specific attribute type.
122         * @param attrValue the attribute value of the attribute the user is trying to access.
123         *                  <tt>null</tt> if the user is not accessing a specific attribute value.
124         * @param microOperations the {@link org.apache.directory.shared.ldap.aci.MicroOperation}s to perform
125         * @param aciTuples {@link org.apache.directory.shared.ldap.aci.ACITuple}s translated from {@link org.apache.directory.shared.ldap.aci.ACIItem}s in the subtree entries
126         * @param entryView in case of a Modify operation, view of the entry being modified as if the modification permitted and completed
127         * @throws NamingException if failed to evaluate ACI items
128         */
129        public void checkPermission( 
130            SchemaManager schemaManager, 
131            OperationContext opContext, 
132            Collection<DN> userGroupNames, 
133            DN username,
134            AuthenticationLevel authenticationLevel, 
135            DN entryName, 
136            String attrId, 
137            Value<?> attrValue, 
138            Collection<MicroOperation> microOperations, 
139            Collection<ACITuple> aciTuples, 
140            ServerEntry entry, 
141            ServerEntry entryView ) throws Exception
142        {
143            if ( !hasPermission( schemaManager, opContext, userGroupNames, username, authenticationLevel, entryName, 
144                attrId, attrValue, microOperations, aciTuples, entry, entryView ) )
145            {
146                throw new LdapNoPermissionException();
147            }
148        }
149    
150        public static final Collection<String> USER_LOOKUP_BYPASS;
151        static
152        {
153            Collection<String> c = new HashSet<String>();
154            c.add( NormalizationInterceptor.class.getName() );
155            c.add( AuthenticationInterceptor.class.getName() );
156    //        c.add( ReferralInterceptor.class.getName() );
157            c.add( AciAuthorizationInterceptor.class.getName() );
158            c.add( DefaultAuthorizationInterceptor.class.getName() );
159    //        c.add( ExceptionInterceptor.class.getName() );
160            c.add( OperationalAttributeInterceptor.class.getName() );
161            c.add( SchemaInterceptor.class.getName() );
162            c.add( SubentryInterceptor.class.getName() );
163    //        c.add( CollectiveAttributeInterceptor.class.getName() );
164            c.add( EventInterceptor.class.getName() );
165            c.add( TriggerInterceptor.class.getName() );
166            USER_LOOKUP_BYPASS = Collections.unmodifiableCollection( c );
167        }
168    
169    
170        /**
171         * Returns <tt>true</tt> if the user with the specified name can access the specified resource
172         * (entry, attribute type, or attribute value) and throws {@link LdapNoPermissionException}
173         * if the user doesn't have any permission to perform the specified grants.
174         * 
175         * @param proxy the proxy to the partition nexus
176         * @param userGroupNames the collection of the group DNs the user who is trying to access the resource belongs
177         * @param userName the DN of the user who is trying to access the resource
178         * @param entryName the DN of the entry the user is trying to access
179         * @param attrId the attribute type of the attribute the user is trying to access.
180         *               <tt>null</tt> if the user is not accessing a specific attribute type.
181         * @param attrValue the attribute value of the attribute the user is trying to access.
182         *                  <tt>null</tt> if the user is not accessing a specific attribute value.
183         * @param microOperations the {@link org.apache.directory.shared.ldap.aci.MicroOperation}s to perform
184         * @param aciTuples {@link org.apache.directory.shared.ldap.aci.ACITuple}s translated from {@link org.apache.directory.shared.ldap.aci.ACIItem}s in the subtree entries
185         * @param entryView in case of a Modify operation, view of the entry being modified as if the modification permitted and completed
186         */
187        public boolean hasPermission( 
188            SchemaManager schemaManager, 
189            OperationContext opContext, 
190            Collection<DN> userGroupNames, 
191            DN userName,
192            AuthenticationLevel authenticationLevel, 
193            DN entryName, 
194            String attrId, 
195            Value<?> attrValue, 
196            Collection<MicroOperation> microOperations, 
197            Collection<ACITuple> aciTuples, 
198            ServerEntry entry, 
199            ServerEntry entryView ) throws Exception
200        {
201            if ( entryName == null )
202            {
203                throw new NullPointerException( "entryName" );
204            }
205    
206            ServerEntry userEntry = opContext.lookup( userName, USER_LOOKUP_BYPASS );
207    
208            // Determine the scope of the requested operation.
209            OperationScope scope;
210            
211            if ( attrId == null )
212            {
213                scope = OperationScope.ENTRY;
214            }
215            else if ( attrValue == null )
216            {
217                scope = OperationScope.ATTRIBUTE_TYPE;
218            }
219            else
220            {
221                scope = OperationScope.ATTRIBUTE_TYPE_AND_VALUE;
222            }
223    
224            // Clone aciTuples in case it is unmodifiable.
225            aciTuples = new ArrayList<ACITuple>( aciTuples );
226    
227            // Filter unrelated and invalid tuples
228            for ( ACITupleFilter filter : filters )
229            {
230                aciTuples = filter.filter( 
231                    schemaManager, 
232                    aciTuples, 
233                    scope, 
234                    opContext, 
235                    userGroupNames, 
236                    userName, 
237                    userEntry,
238                    authenticationLevel, 
239                    entryName, 
240                    attrId, 
241                    attrValue, 
242                    entry, 
243                    microOperations, 
244                    entryView );
245            }
246    
247            // Deny access if no tuples left.
248            if ( aciTuples.size() == 0 )
249            {
250                return false;
251            }
252    
253            // Grant access if and only if one or more tuples remain and
254            // all grant access. Otherwise deny access.
255            for ( ACITuple tuple : aciTuples )
256            {
257                if ( !tuple.isGrant() )
258                {
259                    return false;
260                }
261            }
262    
263            return true;
264        }
265    }