View Javadoc

1   /*
2    *   Copyright 2004 The Apache Software Foundation
3    *
4    *   Licensed under the Apache License, Version 2.0 (the "License");
5    *   you may not use this file except in compliance with the License.
6    *   You may obtain a copy of the License at
7    *
8    *       http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *   Unless required by applicable law or agreed to in writing, software
11   *   distributed under the License is distributed on an "AS IS" BASIS,
12   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *   See the License for the specific language governing permissions and
14   *   limitations under the License.
15   *
16   */
17  package org.apache.ldap.server.authz;
18  
19  
20  import org.apache.ldap.server.DirectoryServiceConfiguration;
21  import org.apache.ldap.server.enumeration.SearchResultFilter;
22  import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
23  import org.apache.ldap.server.interceptor.BaseInterceptor;
24  import org.apache.ldap.server.interceptor.NextInterceptor;
25  import org.apache.ldap.server.interceptor.InterceptorChain;
26  import org.apache.ldap.server.jndi.ServerContext;
27  import org.apache.ldap.server.jndi.ServerLdapContext;
28  import org.apache.ldap.server.configuration.InterceptorConfiguration;
29  import org.apache.ldap.server.partition.DirectoryPartitionNexus;
30  import org.apache.ldap.server.partition.DirectoryPartitionNexusProxy;
31  import org.apache.ldap.server.authz.support.ACDFEngine;
32  import org.apache.ldap.server.invocation.InvocationStack;
33  import org.apache.ldap.server.invocation.Invocation;
34  import org.apache.ldap.server.authn.LdapPrincipal;
35  import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
36  import org.apache.ldap.server.schema.AttributeTypeRegistry;
37  import org.apache.ldap.server.subtree.SubentryService;
38  import org.apache.ldap.common.filter.ExprNode;
39  import org.apache.ldap.common.aci.MicroOperation;
40  import org.apache.ldap.common.aci.ACIItemParser;
41  import org.apache.ldap.common.aci.ACIItem;
42  import org.apache.ldap.common.exception.LdapNamingException;
43  import org.apache.ldap.common.message.ResultCodeEnum;
44  import org.apache.ldap.common.name.DnParser;
45  
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import javax.naming.Name;
50  import javax.naming.NamingException;
51  import javax.naming.NamingEnumeration;
52  import javax.naming.directory.*;
53  import java.util.*;
54  import java.text.ParseException;
55  
56  
57  /***
58   * An ACI based authorization service.
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   * @version $Rev: 327983 $
62   */
63  public class AuthorizationService extends BaseInterceptor
64  {
65      /*** the logger for this class */
66      private static final Logger log = LoggerFactory.getLogger( AuthorizationService.class );
67      /*** the entry ACI attribute string: entryACI */
68      private static final String ENTRYACI_ATTR = "entryACI";
69      /*** the subentry ACI attribute string: subentryACI */
70      private static final String SUBENTRYACI_ATTR = "subentryACI";
71      /***
72       * the multivalued op attr used to track the perscriptive access control
73       * subentries that apply to an entry.
74       */
75      private static final String AC_SUBENTRY_ATTR = "accessControlSubentries";
76  
77      private static final Collection ADD_PERMS;
78      private static final Collection READ_PERMS;
79      private static final Collection COMPARE_PERMS;
80      private static final Collection SEARCH_ENTRY_PERMS;
81      private static final Collection SEARCH_ATTRVAL_PERMS;
82      private static final Collection REMOVE_PERMS;
83      private static final Collection MATCHEDNAME_PERMS;
84      private static final Collection BROWSE_PERMS;
85      private static final Collection LOOKUP_PERMS;
86      private static final Collection REPLACE_PERMS;
87      private static final Collection RENAME_PERMS;
88      private static final Collection EXPORT_PERMS;
89      private static final Collection IMPORT_PERMS;
90      private static final Collection MOVERENAME_PERMS;
91  
92  
93      static
94      {
95          HashSet set = new HashSet( 2 );
96          set.add( MicroOperation.BROWSE );
97          set.add( MicroOperation.RETURN_DN );
98          SEARCH_ENTRY_PERMS = Collections.unmodifiableCollection( set );
99  
100         set = new HashSet( 2 );
101         set.add( MicroOperation.READ );
102         set.add( MicroOperation.BROWSE );
103         LOOKUP_PERMS = Collections.unmodifiableCollection( set );
104 
105         set = new HashSet( 2 );
106         set.add( MicroOperation.ADD );
107         set.add( MicroOperation.REMOVE );
108         REPLACE_PERMS = Collections.unmodifiableCollection( set );
109 
110         set = new HashSet( 3 );
111         set.add( MicroOperation.IMPORT );
112         set.add( MicroOperation.EXPORT );
113         set.add( MicroOperation.RENAME );
114         MOVERENAME_PERMS = Collections.unmodifiableCollection( set );
115 
116         SEARCH_ATTRVAL_PERMS = Collections.singleton( MicroOperation.READ );
117         ADD_PERMS = Collections.singleton( MicroOperation.ADD );
118         READ_PERMS = Collections.singleton( MicroOperation.READ );
119         COMPARE_PERMS = Collections.singleton( MicroOperation.COMPARE );
120         REMOVE_PERMS = Collections.singleton( MicroOperation.REMOVE );
121         MATCHEDNAME_PERMS = Collections.singleton( MicroOperation.DISCLOSE_ON_ERROR );
122         BROWSE_PERMS = Collections.singleton( MicroOperation.BROWSE );
123         RENAME_PERMS = Collections.singleton( MicroOperation.RENAME );
124         EXPORT_PERMS = Collections.singleton( MicroOperation.EXPORT );
125         IMPORT_PERMS = Collections.singleton( MicroOperation.IMPORT );
126     }
127 
128     /*** a tupleCache that responds to add, delete, and modify attempts */
129     private TupleCache tupleCache;
130     /*** a groupCache that responds to add, delete, and modify attempts */
131     private GroupCache groupCache;
132     /*** a normalizing ACIItem parser */
133     private ACIItemParser aciParser;
134     /*** use and instance of the ACDF engine */
135     private ACDFEngine engine;
136     /*** interceptor chain */
137     private InterceptorChain chain;
138     /*** attribute type registry */
139     private AttributeTypeRegistry attrRegistry;
140     /*** whether or not this interceptor is activated */
141     private boolean enabled = false;
142 
143 
144     /***
145      * Initializes this interceptor based service by getting a handle on the nexus, setting up
146      * the tupe and group membership caches and the ACIItem parser and the ACDF engine.
147      *
148      * @param factoryCfg the ContextFactory configuration for the server
149      * @param cfg the interceptor configuration
150      * @throws NamingException if there are problems during initialization
151      */
152     public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
153     {
154         super.init( factoryCfg, cfg );
155         tupleCache = new TupleCache( factoryCfg );
156         groupCache = new GroupCache( factoryCfg );
157         attrRegistry = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
158         aciParser = new ACIItemParser( new ConcreteNameComponentNormalizer( attrRegistry ) );
159         engine = new ACDFEngine( factoryCfg.getGlobalRegistries().getOidRegistry(), attrRegistry );
160         chain = factoryCfg.getInterceptorChain();
161         enabled = factoryCfg.getStartupConfiguration().isAccessControlEnabled();
162     }
163 
164 
165     /***
166      * Adds perscriptiveACI tuples to a collection of tuples by accessing the
167      * tupleCache.  The tuple cache is accessed for each A/C subentry
168      * associated with the protected entry.  Note that subentries are handled
169      * differently: their parent, the administrative entry is accessed to
170      * determine the perscriptiveACIs effecting the AP and hence the subentry
171      * which is considered to be in the same context.
172      *
173      * @param tuples the collection of tuples to add to
174      * @param dn the normalized distinguished name of the protected entry
175      * @param entry the target entry that access to is being controled
176      * @throws NamingException if there are problems accessing attribute values
177      */
178     private void addPerscriptiveAciTuples( DirectoryPartitionNexusProxy proxy, Collection tuples,
179                                            Name dn, Attributes entry )
180             throws NamingException
181     {
182         /*
183          * If the protected entry is a subentry, then the entry being evaluated
184          * for perscriptiveACIs is in fact the administrative entry.  By
185          * substituting the administrative entry for the actual subentry the
186          * code below this "if" statement correctly evaluates the effects of
187          * perscriptiveACI on the subentry.  Basically subentries are considered
188          * to be in the same naming context as their access point so the subentries
189          * effecting their parent entry applies to them as well.
190          */
191         if ( entry.get( "objectClass" ).contains( "subentry" ) )
192         {
193             Name parentDn = ( Name ) dn.clone();
194             parentDn.remove( dn.size() - 1 );
195             entry = proxy.lookup( parentDn, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
196         }
197 
198         Attribute subentries = entry.get( AC_SUBENTRY_ATTR );
199         if ( subentries == null )
200         {
201             return;
202         }
203         for ( int ii = 0; ii < subentries.size(); ii++ )
204         {
205             String subentryDn = ( String ) subentries.get( ii );
206             tuples.addAll( tupleCache.getACITuples( subentryDn ) );
207         }
208     }
209 
210 
211     /***
212      * Adds the set of entryACI tuples to a collection of tuples.  The entryACI
213      * is parsed and tuples are generated on they fly then added to the collection.
214      *
215      * @param tuples the collection of tuples to add to
216      * @param entry the target entry that access to is being regulated
217      * @throws NamingException if there are problems accessing attribute values
218      */
219     private void addEntryAciTuples( Collection tuples, Attributes entry ) throws NamingException
220     {
221         Attribute entryAci = entry.get( ENTRYACI_ATTR );
222         if ( entryAci == null )
223         {
224             return;
225         }
226 
227         for ( int ii = 0; ii < entryAci.size(); ii++ )
228         {
229             String aciString = ( String ) entryAci.get( ii );
230             ACIItem item;
231 
232             try
233             {
234                 item = aciParser.parse( aciString );
235             }
236             catch ( ParseException e )
237             {
238                 String msg = "failed to parse entryACI: " + aciString ;
239                 log.error( msg, e );
240                 throw new LdapNamingException( msg, ResultCodeEnum.OPERATIONSERROR );
241             }
242 
243             tuples.addAll( item.toTuples() );
244         }
245     }
246 
247 
248     /***
249      * Adds the set of subentryACI tuples to a collection of tuples.  The subentryACI
250      * is parsed and tuples are generated on the fly then added to the collection.
251      *
252      * @param tuples the collection of tuples to add to
253      * @param dn the normalized distinguished name of the protected entry
254      * @param entry the target entry that access to is being regulated
255      * @throws NamingException if there are problems accessing attribute values
256      */
257     private void addSubentryAciTuples( DirectoryPartitionNexusProxy proxy, Collection tuples,
258                                        Name dn, Attributes entry ) throws NamingException
259     {
260         // only perform this for subentries
261         if ( ! entry.get("objectClass").contains("subentry") )
262         {
263             return;
264         }
265 
266         // get the parent or administrative entry for this subentry since it
267         // will contain the subentryACI attributes that effect subentries
268         Name parentDn = ( Name ) dn.clone();
269         parentDn.remove( dn.size() - 1 );
270         Attributes administrativeEntry = proxy.lookup( parentDn, new String[] { SUBENTRYACI_ATTR },
271                 DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
272         Attribute subentryAci = administrativeEntry.get( SUBENTRYACI_ATTR );
273 
274         if ( subentryAci == null )
275         {
276             return;
277         }
278 
279         for ( int ii = 0; ii < subentryAci.size(); ii++ )
280         {
281             String aciString = ( String ) subentryAci.get( ii );
282             ACIItem item;
283 
284             try
285             {
286                 item = aciParser.parse( aciString );
287             }
288             catch ( ParseException e )
289             {
290                 String msg = "failed to parse subentryACI: " + aciString ;
291                 log.error( msg, e );
292                 throw new LdapNamingException( msg, ResultCodeEnum.OPERATIONSERROR );
293             }
294 
295             tuples.addAll( item.toTuples() );
296         }
297     }
298 
299 
300     /* -------------------------------------------------------------------------------
301      * Within every access controled interceptor method we must retrieve the ACITuple
302      * set for all the perscriptiveACIs that apply to the candidate, the target entry
303      * operated upon.  This ACITuple set is gotten from the TupleCache by looking up
304      * the subentries referenced by the accessControlSubentries operational attribute
305      * within the target entry.
306      *
307      * Then the entry is inspected for an entryACI.  This is not done for the add op
308      * since it could introduce a security breech.  So for non-add ops if present a
309      * set of ACITuples are generated for all the entryACIs within the entry.  This
310      * set is combined with the ACITuples cached for the perscriptiveACI affecting
311      * the target entry.  If the entry is a subentry the ACIs are also processed for
312      * the subentry to generate more ACITuples.  This subentry TupleACI set is joined
313      * with the entry and perscriptive ACI.
314      *
315      * The union of ACITuples are fed into the engine along with other parameters
316      * to decide whether a permission is granted or rejected for the specific
317      * operation.
318      * -------------------------------------------------------------------------------
319      */
320 
321     public void add( NextInterceptor next, String upName, Name normName, Attributes entry ) throws NamingException
322     {
323         // Access the principal requesting the operation, and bypass checks if it is the admin
324         Invocation invocation = InvocationStack.getInstance().peek();
325         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
326 
327         // bypass authz code if we are disabled
328         if ( ! enabled )
329         {
330             next.add( upName, normName, entry );
331             return;
332         }
333 
334         // bypass authz code but manage caches if operation is performed by the admin
335         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
336         {
337             next.add( upName, normName, entry );
338             tupleCache.subentryAdded( upName, normName, entry );
339             groupCache.groupAdded( upName, normName, entry );
340             return;
341         }
342 
343         // perform checks below here for all non-admin users
344         SubentryService subentryService = ( SubentryService ) chain.get( "subentryService" );
345         Attributes subentryAttrs = subentryService.getSubentryAttributes( normName, entry );
346         NamingEnumeration attrList = entry.getAll();
347         while( attrList.hasMore() )
348         {
349             subentryAttrs.put( ( Attribute ) attrList.next() );
350         }
351 
352         // Assemble all the information required to make an access control decision
353         Set userGroups = groupCache.getGroups( user.getName() );
354         Collection tuples = new HashSet();
355 
356         // Build the total collection of tuples to be considered for add rights
357         // NOTE: entryACI are NOT considered in adds (it would be a security breech)
358         addPerscriptiveAciTuples( invocation.getProxy(), tuples, normName, subentryAttrs );
359         addSubentryAciTuples( invocation.getProxy(), tuples, normName, subentryAttrs );
360 
361         // check if entry scope permission is granted
362         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
363         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
364                 normName, null, null, ADD_PERMS, tuples, subentryAttrs );
365 
366         // now we must check if attribute type and value scope permission is granted
367         NamingEnumeration attributeList = entry.getAll();
368         while ( attributeList.hasMore() )
369         {
370             Attribute attr = ( Attribute ) attributeList.next();
371             for ( int ii = 0; ii < attr.size(); ii++ )
372             {
373                 engine.checkPermission( proxy, userGroups, user.getJndiName(),
374                         user.getAuthenticationLevel(), normName, attr.getID(),
375                         attr.get( ii ), ADD_PERMS, tuples, entry );
376             }
377         }
378 
379         // if we've gotten this far then access has been granted
380         next.add( upName, normName, entry );
381 
382         // if the entry added is a subentry or a groupOf[Unique]Names we must
383         // update the ACITuple cache and the groups cache to keep them in sync
384         tupleCache.subentryAdded( upName, normName, entry );
385         groupCache.groupAdded( upName, normName, entry );
386     }
387 
388 
389     public void delete( NextInterceptor next, Name name ) throws NamingException
390     {
391         // Access the principal requesting the operation, and bypass checks if it is the admin
392         Invocation invocation = InvocationStack.getInstance().peek();
393         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
394         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
395         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
396 
397         // bypass authz code if we are disabled
398         if ( ! enabled )
399         {
400             next.delete( name );
401             return;
402         }
403 
404         // bypass authz code but manage caches if operation is performed by the admin
405         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
406         {
407             next.delete( name );
408             tupleCache.subentryDeleted( name, entry );
409             groupCache.groupDeleted( name, entry );
410             return;
411         }
412 
413         Set userGroups = groupCache.getGroups( user.getName() );
414         Collection tuples = new HashSet();
415         addPerscriptiveAciTuples( proxy, tuples, name, entry );
416         addEntryAciTuples( tuples, entry );
417         addSubentryAciTuples( proxy, tuples, name, entry );
418 
419         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
420                 null, REMOVE_PERMS, tuples, entry );
421 
422         next.delete( name );
423         tupleCache.subentryDeleted( name, entry );
424         groupCache.groupDeleted( name, entry );
425     }
426 
427 
428     public void modify( NextInterceptor next, Name name, int modOp, Attributes mods ) throws NamingException
429     {
430         // Access the principal requesting the operation, and bypass checks if it is the admin
431         Invocation invocation = InvocationStack.getInstance().peek();
432         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
433         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
434         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
435 
436         // bypass authz code if we are disabled
437         if ( ! enabled )
438         {
439             next.modify( name, modOp, mods );
440             return;
441         }
442 
443         // bypass authz code but manage caches if operation is performed by the admin
444         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
445         {
446             next.modify( name, modOp, mods );
447             tupleCache.subentryModified( name, modOp, mods, entry );
448             groupCache.groupModified( name, modOp, mods, entry );
449             return;
450         }
451 
452         Set userGroups = groupCache.getGroups( user.getName() );
453         Collection tuples = new HashSet();
454         addPerscriptiveAciTuples( proxy, tuples, name, entry );
455         addEntryAciTuples( tuples, entry );
456         addSubentryAciTuples( proxy, tuples, name, entry );
457 
458         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
459                 null, Collections.singleton( MicroOperation.MODIFY ), tuples, entry );
460 
461         NamingEnumeration attrList = mods.getAll();
462         Collection perms = null;
463         switch( modOp )
464         {
465             case( DirContext.ADD_ATTRIBUTE ):
466                 perms = ADD_PERMS;
467                 break;
468             case( DirContext.REMOVE_ATTRIBUTE ):
469                 perms = REMOVE_PERMS;
470                 break;
471             case( DirContext.REPLACE_ATTRIBUTE ):
472                 perms = REPLACE_PERMS;
473                 break;
474         }
475 
476         while( attrList.hasMore() )
477         {
478             Attribute attr = ( Attribute ) attrList.next();
479             for ( int ii = 0; ii < attr.size(); ii++ )
480             {
481                 engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
482                         name, attr.getID(), attr.get( ii ), perms, tuples, entry );
483             }
484         }
485 
486         next.modify( name, modOp, mods );
487         tupleCache.subentryModified( name, modOp, mods, entry );
488         groupCache.groupModified( name, modOp, mods, entry );
489     }
490 
491 
492     public void modify( NextInterceptor next, Name name, ModificationItem[] mods ) throws NamingException
493     {
494         // Access the principal requesting the operation, and bypass checks if it is the admin
495         Invocation invocation = InvocationStack.getInstance().peek();
496         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
497         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
498         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
499 
500         // bypass authz code if we are disabled
501         if ( ! enabled )
502         {
503             next.modify( name, mods );
504             return;
505         }
506 
507         // bypass authz code but manage caches if operation is performed by the admin
508         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
509         {
510             next.modify( name, mods );
511             tupleCache.subentryModified( name, mods, entry );
512             groupCache.groupModified( name, mods, entry );
513             return;
514         }
515 
516         Set userGroups = groupCache.getGroups( user.getName() );
517         Collection tuples = new HashSet();
518         addPerscriptiveAciTuples( proxy, tuples, name, entry );
519         addEntryAciTuples( tuples, entry );
520         addSubentryAciTuples( proxy, tuples, name, entry );
521 
522         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
523                 null, Collections.singleton( MicroOperation.MODIFY ), tuples, entry );
524 
525         Collection perms = null;
526         for ( int ii = 0; ii < mods.length; ii++ )
527         {
528             switch( mods[ii].getModificationOp() )
529             {
530                 case( DirContext.ADD_ATTRIBUTE ):
531                     perms = ADD_PERMS;
532                     break;
533                 case( DirContext.REMOVE_ATTRIBUTE ):
534                     perms = REMOVE_PERMS;
535                     break;
536                 case( DirContext.REPLACE_ATTRIBUTE ):
537                     perms = REPLACE_PERMS;
538                     break;
539             }
540 
541             Attribute attr = mods[ii].getAttribute();
542             for ( int jj = 0; jj < attr.size(); jj++ )
543             {
544                 engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
545                         name, attr.getID(), attr.get( jj ), perms, tuples, entry );
546             }
547         }
548 
549         next.modify( name, mods );
550         tupleCache.subentryModified( name, mods, entry );
551         groupCache.groupModified( name, mods, entry );
552     }
553 
554 
555     public boolean hasEntry( NextInterceptor next, Name name ) throws NamingException
556     {
557         Invocation invocation = InvocationStack.getInstance().peek();
558         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
559         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
560         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
561 
562         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
563         {
564             return next.hasEntry( name );
565         }
566 
567         Set userGroups = groupCache.getGroups( user.getName() );
568         Collection tuples = new HashSet();
569         addPerscriptiveAciTuples( proxy, tuples, name, entry );
570         addEntryAciTuples( tuples, entry );
571         addSubentryAciTuples( proxy, tuples, name, entry );
572 
573         // check that we have browse access to the entry
574         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
575                 null, BROWSE_PERMS, tuples, entry );
576 
577         return next.hasEntry( name );
578     }
579 
580 
581     /***
582      * Checks if the READ permissions exist to the entry and to each attribute type and
583      * value.
584      *
585      * @todo not sure if we should hide attribute types/values or throw an exception
586      * instead.  I think we're going to have to use a filter to restrict the return
587      * of attribute types and values instead of throwing an exception.  Lack of read
588      * perms to attributes and their values results in their removal when returning
589      * the entry.
590      *
591      * @param user the user associated with the call
592      * @param dn the name of the entry being looked up
593      * @param entry the raw entry pulled from the nexus
594      * @throws NamingException
595      */
596     private void checkLookupAccess( LdapPrincipal user, Name dn, Attributes entry )
597             throws NamingException
598     {
599         DirectoryPartitionNexusProxy proxy = InvocationStack.getInstance().peek().getProxy();
600         Set userGroups = groupCache.getGroups( user.getName() );
601         Collection tuples = new HashSet();
602         addPerscriptiveAciTuples( proxy, tuples, dn, entry );
603         addEntryAciTuples( tuples, entry );
604         addSubentryAciTuples( proxy, tuples, dn, entry );
605 
606         // check that we have read access to the entry
607         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn, null,
608                 null, LOOKUP_PERMS, tuples, entry );
609 
610         // check that we have read access to every attribute type and value
611         NamingEnumeration attributeList = entry.getAll();
612         while ( attributeList.hasMore() )
613         {
614             Attribute attr = ( Attribute ) attributeList.next();
615             for ( int ii = 0; ii < attr.size(); ii++ )
616             {
617                 engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), dn,
618                         attr.getID(), attr.get( ii ), READ_PERMS, tuples, entry );
619             }
620         }
621     }
622 
623 
624     public Attributes lookup( NextInterceptor next, Name dn, String[] attrIds ) throws NamingException
625     {
626         Invocation invocation = InvocationStack.getInstance().peek();
627         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
628         Attributes entry = proxy.lookup( dn, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
629         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
630 
631         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
632         {
633             return next.lookup( dn, attrIds );
634         }
635 
636         checkLookupAccess( user, dn, entry );
637 
638         return next.lookup( dn, attrIds );
639     }
640 
641 
642     public Attributes lookup( NextInterceptor next, Name name ) throws NamingException
643     {
644         Invocation invocation = InvocationStack.getInstance().peek();
645         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
646         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
647         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
648 
649         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
650         {
651             return next.lookup( name );
652         }
653 
654         checkLookupAccess( user, name, entry );
655 
656         return next.lookup( name );
657     }
658 
659 
660     public void modifyRn( NextInterceptor next, Name name, String newRn, boolean deleteOldRn ) throws NamingException
661     {
662         // Access the principal requesting the operation, and bypass checks if it is the admin
663         Invocation invocation = InvocationStack.getInstance().peek();
664         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
665         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
666         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
667         Name newName = ( Name ) name.clone();
668         newName.remove( name.size() - 1 );
669         newName.add( newRn );
670 
671 
672         // bypass authz code if we are disabled
673         if ( ! enabled )
674         {
675             next.modifyRn( name, newRn, deleteOldRn );
676             return;
677         }
678 
679         // bypass authz code but manage caches if operation is performed by the admin
680         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
681         {
682             next.modifyRn( name, newRn, deleteOldRn );
683             tupleCache.subentryRenamed( name, newName );
684             groupCache.groupRenamed( name, newName );
685             return;
686         }
687 
688         Set userGroups = groupCache.getGroups( user.getName() );
689         Collection tuples = new HashSet();
690         addPerscriptiveAciTuples( proxy, tuples, name, entry );
691         addEntryAciTuples( tuples, entry );
692         addSubentryAciTuples( proxy, tuples, name, entry );
693 
694         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
695                 null, RENAME_PERMS, tuples, entry );
696 
697 //        if ( deleteOldRn )
698 //        {
699 //            String oldRn = name.get( name.size() - 1 );
700 //            if ( NamespaceTools.hasCompositeComponents( oldRn ) )
701 //            {
702 //                String[] comps = NamespaceTools.getCompositeComponents( oldRn );
703 //                for ( int ii = 0; ii < comps.length; ii++ )
704 //                {
705 //                    String id = NamespaceTools.getRdnAttribute( comps[ii] );
706 //                    String value = NamespaceTools.getRdnValue( comps[ii] );
707 //                    engine.checkPermission( next, userGroups, user.getJndiName(),
708 //                            user.getAuthenticationLevel(), name, id,
709 //                            value, Collections.singleton( MicroOperation.REMOVE ),
710 //                            tuples, entry );
711 //                }
712 //            }
713 //            else
714 //            {
715 //                String id = NamespaceTools.getRdnAttribute( oldRn );
716 //                String value = NamespaceTools.getRdnValue( oldRn );
717 //                engine.checkPermission( next, userGroups, user.getJndiName(),
718 //                        user.getAuthenticationLevel(), name, id,
719 //                        value, Collections.singleton( MicroOperation.REMOVE ),
720 //                        tuples, entry );
721 //            }
722 //        }
723 
724         next.modifyRn( name, newRn, deleteOldRn );
725         tupleCache.subentryRenamed( name, newName );
726         groupCache.groupRenamed( name, newName );
727     }
728 
729 
730     public void move( NextInterceptor next, Name oriChildName, Name newParentName, String newRn, boolean deleteOldRn )
731             throws NamingException
732     {
733         // Access the principal requesting the operation, and bypass checks if it is the admin
734         Invocation invocation = InvocationStack.getInstance().peek();
735         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
736         Attributes entry = proxy.lookup( oriChildName, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
737         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
738         Name newName = ( Name ) newParentName.clone();
739         newName.add( newRn );
740 
741         // bypass authz code if we are disabled
742         if ( ! enabled )
743         {
744             next.move( oriChildName, newParentName, newRn, deleteOldRn );
745             return;
746         }
747 
748         // bypass authz code but manage caches if operation is performed by the admin
749         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
750         {
751             next.move( oriChildName, newParentName, newRn, deleteOldRn );
752             tupleCache.subentryRenamed( oriChildName, newName );
753             groupCache.groupRenamed( oriChildName, newName );
754             return;
755         }
756 
757         Set userGroups = groupCache.getGroups( user.getName() );
758         Collection tuples = new HashSet();
759         addPerscriptiveAciTuples( proxy, tuples, oriChildName, entry );
760         addEntryAciTuples( tuples, entry );
761         addSubentryAciTuples( proxy, tuples, oriChildName, entry );
762 
763         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
764                 oriChildName, null, null, MOVERENAME_PERMS, tuples, entry );
765 
766         Collection destTuples = new HashSet();
767         addPerscriptiveAciTuples( proxy, destTuples, oriChildName, entry );
768         addEntryAciTuples( destTuples, entry );
769         addSubentryAciTuples( proxy, destTuples, oriChildName, entry );
770         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
771                 oriChildName, null, null, IMPORT_PERMS, tuples, entry );
772 
773 //        if ( deleteOldRn )
774 //        {
775 //            String oldRn = oriChildName.get( oriChildName.size() - 1 );
776 //            if ( NamespaceTools.hasCompositeComponents( oldRn ) )
777 //            {
778 //                String[] comps = NamespaceTools.getCompositeComponents( oldRn );
779 //                for ( int ii = 0; ii < comps.length; ii++ )
780 //                {
781 //                    String id = NamespaceTools.getRdnAttribute( comps[ii] );
782 //                    String value = NamespaceTools.getRdnValue( comps[ii] );
783 //                    engine.checkPermission( next, userGroups, user.getJndiName(),
784 //                            user.getAuthenticationLevel(), oriChildName, id,
785 //                            value, Collections.singleton( MicroOperation.REMOVE ),
786 //                            tuples, entry );
787 //                }
788 //            }
789 //            else
790 //            {
791 //                String id = NamespaceTools.getRdnAttribute( oldRn );
792 //                String value = NamespaceTools.getRdnValue( oldRn );
793 //                engine.checkPermission( next, userGroups, user.getJndiName(),
794 //                        user.getAuthenticationLevel(), oriChildName, id,
795 //                        value, Collections.singleton( MicroOperation.REMOVE ),
796 //                        tuples, entry );
797 //            }
798 //        }
799 
800         next.move( oriChildName, newParentName, newRn, deleteOldRn );
801         tupleCache.subentryRenamed( oriChildName, newName );
802         groupCache.groupRenamed( oriChildName, newName );
803     }
804 
805 
806     public void move( NextInterceptor next, Name oriChildName, Name newParentName ) throws NamingException
807     {
808         // Access the principal requesting the operation, and bypass checks if it is the admin
809         Invocation invocation = InvocationStack.getInstance().peek();
810         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
811         Attributes entry = proxy.lookup( oriChildName, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
812         Name newName = ( Name ) newParentName.clone();
813         newName.add( oriChildName.get( oriChildName.size() - 1 ) );
814         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
815 
816         // bypass authz code if we are disabled
817         if ( ! enabled )
818         {
819             next.move( oriChildName, newParentName );
820             return;
821         }
822 
823         // bypass authz code but manage caches if operation is performed by the admin
824         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
825         {
826             next.move( oriChildName, newParentName );
827             tupleCache.subentryRenamed( oriChildName, newName );
828             groupCache.groupRenamed( oriChildName, newName );
829             return;
830         }
831 
832         Set userGroups = groupCache.getGroups( user.getName() );
833         Collection tuples = new HashSet();
834         addPerscriptiveAciTuples( proxy, tuples, oriChildName, entry );
835         addEntryAciTuples( tuples, entry );
836         addSubentryAciTuples( proxy, tuples, oriChildName, entry );
837 
838         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
839                 oriChildName, null, null, EXPORT_PERMS, tuples, entry );
840 
841         Collection destTuples = new HashSet();
842         addPerscriptiveAciTuples( proxy, destTuples, oriChildName, entry );
843         addEntryAciTuples( destTuples, entry );
844         addSubentryAciTuples( proxy, destTuples, oriChildName, entry );
845         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(),
846                 oriChildName, null, null, IMPORT_PERMS, tuples, entry );
847 
848         next.move( oriChildName, newParentName );
849         tupleCache.subentryRenamed( oriChildName, newName );
850         groupCache.groupRenamed( oriChildName, newName );
851     }
852 
853 
854     public static final SearchControls DEFUALT_SEARCH_CONTROLS = new SearchControls();
855 
856     public NamingEnumeration list( NextInterceptor next, Name base ) throws NamingException
857     {
858         Invocation invocation = InvocationStack.getInstance().peek();
859         ServerLdapContext ctx = ( ServerLdapContext ) invocation.getCaller();
860         LdapPrincipal user = ctx.getPrincipal();
861         NamingEnumeration e = next.list( base );
862         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
863         {
864             return e;
865         }
866         AuthorizationFilter authzFilter = new AuthorizationFilter();
867         return new SearchResultFilteringEnumeration( e, DEFUALT_SEARCH_CONTROLS, invocation, authzFilter );
868     }
869 
870 
871     public NamingEnumeration search( NextInterceptor next, Name base, Map env, ExprNode filter,
872                                      SearchControls searchCtls ) throws NamingException
873     {
874         Invocation invocation = InvocationStack.getInstance().peek();
875         ServerLdapContext ctx = ( ServerLdapContext ) invocation.getCaller();
876         LdapPrincipal user = ctx.getPrincipal();
877         NamingEnumeration e = next.search( base, env, filter, searchCtls );
878         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
879         {
880             return e;
881         }
882         AuthorizationFilter authzFilter = new AuthorizationFilter();
883         return new SearchResultFilteringEnumeration( e, searchCtls, invocation, authzFilter );
884     }
885 
886 
887     public boolean compare( NextInterceptor next, Name name, String oid, Object value ) throws NamingException
888     {
889         // Access the principal requesting the operation, and bypass checks if it is the admin
890         Invocation invocation = InvocationStack.getInstance().peek();
891         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
892         Attributes entry = proxy.lookup( name, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
893         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
894         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
895         {
896             return next.compare( name, oid, value );
897         }
898 
899         Set userGroups = groupCache.getGroups( user.getName() );
900         Collection tuples = new HashSet();
901         addPerscriptiveAciTuples( proxy, tuples, name, entry );
902         addEntryAciTuples( tuples, entry );
903         addSubentryAciTuples( proxy, tuples, name, entry );
904 
905         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, null,
906                 null, READ_PERMS, tuples, entry );
907         engine.checkPermission( proxy, userGroups, user.getJndiName(), user.getAuthenticationLevel(), name, oid,
908                 value, COMPARE_PERMS, tuples, entry );
909 
910         return next.compare( name, oid, value );
911     }
912 
913 
914     public Name getMatchedName( NextInterceptor next, Name dn, boolean normalized ) throws NamingException
915     {
916         // Access the principal requesting the operation, and bypass checks if it is the admin
917         Invocation invocation = InvocationStack.getInstance().peek();
918         DirectoryPartitionNexusProxy proxy = invocation.getProxy();
919         LdapPrincipal user = ( ( ServerContext ) invocation.getCaller() ).getPrincipal();
920         if ( user.getName().equalsIgnoreCase( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) || ! enabled )
921         {
922             return next.getMatchedName( dn, normalized );
923         }
924 
925         // get the present matched name
926         Attributes entry;
927         Name matched = next.getMatchedName( dn, normalized );
928 
929         // check if we have disclose on error permission for the entry at the matched dn
930         // if not remove rdn and check that until nothing is left in the name and return
931         // that but if permission is granted then short the process and return the dn
932         while ( matched.size() > 0 )
933         {
934             if ( normalized )
935             {
936                 entry = proxy.lookup( matched, DirectoryPartitionNexusProxy.GETMATCHEDDN_BYPASS );
937             }
938             else
939             {
940                 entry = proxy.lookup( matched, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
941             }
942 
943             Set userGroups = groupCache.getGroups( user.getName() );
944             Collection tuples = new HashSet();
945             addPerscriptiveAciTuples( proxy, tuples, matched, entry );
946             addEntryAciTuples( tuples, entry );
947             addSubentryAciTuples( proxy, tuples, matched, entry );
948 
949             if ( engine.hasPermission( proxy, userGroups, user.getJndiName(),
950                     user.getAuthenticationLevel(), matched, null, null,
951                     MATCHEDNAME_PERMS, tuples, entry ) )
952             {
953                 return matched;
954             }
955 
956             matched.remove( matched.size() - 1 );
957         }
958 
959         return matched;
960     }
961 
962 
963     public void cacheNewGroup( String upName, Name normName, Attributes entry ) throws NamingException
964     {
965         this.groupCache.groupAdded( upName, normName, entry );
966     }
967 
968 
969     private boolean filter( Invocation invocation, Name normName, SearchResult result ) throws NamingException
970     {
971        /*
972         * First call hasPermission() for entry level "Browse" and "ReturnDN" perm
973         * tests.  If we hasPermission() returns false we immediately short the
974         * process and return false.
975         */
976         Attributes entry = invocation.getProxy().lookup( normName, DirectoryPartitionNexusProxy.LOOKUP_BYPASS );
977         ServerLdapContext ctx = ( ServerLdapContext ) invocation.getCaller();
978         Name userDn = ctx.getPrincipal().getJndiName();
979         Set userGroups = groupCache.getGroups( userDn.toString() );
980         Collection tuples = new HashSet();
981         addPerscriptiveAciTuples( invocation.getProxy(), tuples, normName, entry );
982         addEntryAciTuples( tuples, entry );
983         addSubentryAciTuples( invocation.getProxy(), tuples, normName, entry );
984 
985         if ( ! engine.hasPermission( invocation.getProxy(), userGroups, userDn,
986                 ctx.getPrincipal().getAuthenticationLevel(),
987                 normName, null, null, SEARCH_ENTRY_PERMS, tuples, entry ) )
988         {
989             return false;
990         }
991 
992         /*
993          * For each attribute type we check if access is allowed to the type.  If not
994          * the attribute is yanked out of the entry to be returned.  If permission is
995          * allowed we move on to check if the values are allowed.  Values that are
996          * not allowed are removed from the attribute.  If the attribute has no more
997          * values remaining then the entire attribute is removed.
998          */
999         NamingEnumeration idList = result.getAttributes().getIDs();
1000         while ( idList.hasMore() )
1001         {
1002             // if attribute type scope access is not allowed then remove the attribute and continue
1003             String id = ( String ) idList.next();
1004             Attribute attr = result.getAttributes().get( id );
1005             if ( ! engine.hasPermission( invocation.getProxy(), userGroups, userDn,
1006                     ctx.getPrincipal().getAuthenticationLevel(),
1007                     normName, attr.getID(), null, SEARCH_ATTRVAL_PERMS, tuples, entry ) )
1008             {
1009                 result.getAttributes().remove( attr.getID() );
1010 
1011                 if ( attr.size() == 0 )
1012                 {
1013                     result.getAttributes().remove( attr.getID() );
1014                 }
1015                 continue;
1016             }
1017 
1018             // attribute type scope is ok now let's determine value level scope
1019             for ( int ii = 0; ii < attr.size(); ii++ )
1020             {
1021                 if ( ! engine.hasPermission( invocation.getProxy(), userGroups, userDn,
1022                         ctx.getPrincipal().getAuthenticationLevel(), normName,
1023                         attr.getID(), attr.get( ii ), SEARCH_ATTRVAL_PERMS, tuples, entry ) )
1024                 {
1025                     attr.remove( ii );
1026 
1027                     if ( ii > 0 )
1028                     {
1029                         ii--;
1030                     }
1031                 }
1032             }
1033         }
1034 
1035         return true;
1036     }
1037 
1038 
1039     /***
1040      * WARNING: create one of these filters fresh every time for each new search.
1041      */
1042     class AuthorizationFilter implements SearchResultFilter
1043     {
1044         /*** dedicated normalizing parser for this search - cheaper than synchronization */
1045         final DnParser parser;
1046 
1047         public AuthorizationFilter() throws NamingException
1048         {
1049             parser = new DnParser( new ConcreteNameComponentNormalizer( attrRegistry ) );
1050         }
1051 
1052 
1053         public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
1054                 throws NamingException
1055         {
1056             Name normName = parser.parse( result.getName() );
1057 
1058 // looks like isRelative returns true even when the names for results are absolute!!!!
1059 // @todo this is a big bug in JNDI provider
1060 
1061 //            if ( result.isRelative() )
1062 //            {
1063 //                Name base = parser.parse( ctx.getNameInNamespace() );
1064 //                normName = base.addAll( normName );
1065 //            }
1066 
1067             return filter( invocation, normName, result );
1068         }
1069     }
1070 }