View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.server.core.authz;
21  
22  
23  import java.io.IOException;
24  import java.util.HashSet;
25  import java.util.Set;
26  
27  import javax.naming.NoPermissionException;
28  
29  import org.apache.directory.api.ldap.model.entry.Attribute;
30  import org.apache.directory.api.ldap.model.entry.Entry;
31  import org.apache.directory.api.ldap.model.entry.Value;
32  import org.apache.directory.api.ldap.model.exception.LdapException;
33  import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException;
34  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
35  import org.apache.directory.api.ldap.model.name.Dn;
36  import org.apache.directory.server.constants.ServerDNConstants;
37  import org.apache.directory.server.core.api.CoreSession;
38  import org.apache.directory.server.core.api.DirectoryService;
39  import org.apache.directory.server.core.api.InterceptorEnum;
40  import org.apache.directory.server.core.api.filtering.EntryFilter;
41  import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
42  import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
43  import org.apache.directory.server.core.api.interceptor.Interceptor;
44  import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext;
45  import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext;
46  import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext;
47  import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext;
48  import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext;
49  import org.apache.directory.server.core.api.interceptor.context.OperationContext;
50  import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext;
51  import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
52  import org.apache.directory.server.core.api.partition.Partition;
53  import org.apache.directory.server.core.api.partition.PartitionNexus;
54  import org.apache.directory.server.core.api.partition.PartitionTxn;
55  import org.apache.directory.server.core.shared.partition.DefaultPartitionNexus;
56  import org.apache.directory.server.i18n.I18n;
57  import org.slf4j.Logger;
58  import org.slf4j.LoggerFactory;
59  
60  
61  /**
62   * An {@link Interceptor} that controls access to {@link DefaultPartitionNexus}.
63   * If a user tries to perform any operations that requires
64   * permission he or she doesn't have, {@link NoPermissionException} will be
65   * thrown and therefore the current invocation chain will terminate.
66   *
67   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
68   */
69  public class DefaultAuthorizationInterceptor extends BaseInterceptor
70  {
71      /** the logger for this class */
72      private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class );
73  
74      /** the base distinguished {@link Name} for the admin system */
75      private Dn adminSystemDn;
76  
77      /** the base distinguished {@link Name} for all groups */
78      private Dn groupsBaseDn;
79  
80      /** the base distinguished {@link Name} for all users */
81      private Dn usersBaseDn;
82  
83      /** the distinguished {@link Name} for the administrator group */
84      private Dn adminGroupDn;
85  
86      private Set<String> administrators = new HashSet<>( 2 );
87  
88      private PartitionNexus nexus;
89  
90      /**
91       * the search result filter to use for collective attribute injection
92       */
93      private class DefaultAuthorizationSearchFilter implements EntryFilter
94      {
95          /**
96           * {@inheritDoc}
97           */
98          public boolean accept( SearchOperationContext operation, Entry entry ) throws LdapException
99          {
100             return DefaultAuthorizationInterceptor.this.isSearchable( operation, entry );
101         }
102 
103 
104         /**
105          * {@inheritDoc}
106          */
107         public String toString( String tabs )
108         {
109             return tabs + "DefaultAuthorizationSearchFilter";
110         }
111     }
112 
113 
114     /**
115      * Creates a new instance of DefaultAuthorizationInterceptor.
116      */
117     public DefaultAuthorizationInterceptor()
118     {
119         super( InterceptorEnum.DEFAULT_AUTHORIZATION_INTERCEPTOR );
120     }
121 
122 
123     /**
124      * {@inheritDoc}
125      */
126     @Override
127     public void init( DirectoryService directoryService ) throws LdapException
128     {
129         super.init( directoryService );
130 
131         nexus = directoryService.getPartitionNexus();
132 
133         adminSystemDn = dnFactory.create( ServerDNConstants.ADMIN_SYSTEM_DN );
134 
135         groupsBaseDn = dnFactory.create( ServerDNConstants.GROUPS_SYSTEM_DN );
136 
137         usersBaseDn = dnFactory.create( ServerDNConstants.USERS_SYSTEM_DN );
138 
139         adminGroupDn = dnFactory.create( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
140 
141         loadAdministrators( directoryService );
142     }
143 
144 
145     private void loadAdministrators( DirectoryService directoryService ) throws LdapException
146     {
147         // read in the administrators and cache their normalized names
148         Set<String> newAdministrators = new HashSet<>( 2 );
149         CoreSession adminSession = directoryService.getAdminSession();
150         Partition partition = nexus.getPartition( adminGroupDn );
151         Entry adminGroup;
152         
153         LookupOperationContexttor/context/LookupOperationContext.html#LookupOperationContext">LookupOperationContext lookupContext = new LookupOperationContext( adminSession, adminGroupDn );
154         lookupContext.setPartition( partition );
155 
156         try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
157         { 
158             lookupContext.setTransaction( partitionTxn );
159             adminGroup = nexus.lookup( lookupContext );
160         }
161         catch ( IOException ioe )
162         {
163             throw new LdapOtherException( ioe.getMessage(), ioe );
164         }
165 
166         if ( adminGroup == null )
167         {
168             return;
169         }
170 
171         Attribute uniqueMember = adminGroup.get( directoryService.getAtProvider().getUniqueMember() );
172 
173         for ( Value value : uniqueMember )
174         {
175             Dn memberDn = dnFactory.create( value.getString() );
176             newAdministrators.add( memberDn.getNormName() );
177         }
178 
179         administrators = newAdministrators;
180     }
181 
182 
183     // Note:
184     //    Lookup, search and list operations need to be handled using a filter
185     // and so we need access to the filter service.
186     /**
187      * {@inheritDoc}
188      */
189     @Override
190     public void delete( DeleteOperationContext deleteContext ) throws LdapException
191     {
192         if ( deleteContext.getSession().getDirectoryService().isAccessControlEnabled() )
193         {
194             next( deleteContext );
195             return;
196         }
197 
198         Dn dn = deleteContext.getDn();
199 
200         if ( dn.isEmpty() )
201         {
202             String msg = I18n.err( I18n.ERR_12 );
203             LOG.error( msg );
204             throw new LdapNoPermissionException( msg );
205         }
206 
207         if ( dn.equals( adminGroupDn ) )
208         {
209             String msg = I18n.err( I18n.ERR_13 );
210             LOG.error( msg );
211             throw new LdapNoPermissionException( msg );
212         }
213 
214         Dn principalDn = getPrincipal( deleteContext ).getDn();
215 
216         if ( dn.equals( adminSystemDn ) )
217         {
218             String msg = I18n.err( I18n.ERR_14, principalDn.getName() );
219             LOG.error( msg );
220             throw new LdapNoPermissionException( msg );
221         }
222 
223         if ( dn.size() > 2 && !isAnAdministrator( principalDn ) )
224         {
225             if ( dn.isDescendantOf( adminSystemDn ) )
226             {
227                 String msg = I18n.err( I18n.ERR_15, principalDn.getName(), dn.getName() );
228                 LOG.error( msg );
229                 throw new LdapNoPermissionException( msg );
230             }
231 
232             if ( dn.isDescendantOf( groupsBaseDn ) )
233             {
234                 String msg = I18n.err( I18n.ERR_16, principalDn.getName(), dn.getName() );
235                 LOG.error( msg );
236                 throw new LdapNoPermissionException( msg );
237             }
238 
239             if ( dn.isDescendantOf( usersBaseDn ) )
240             {
241                 String msg = I18n.err( I18n.ERR_16, principalDn.getName(), dn.getName() );
242                 LOG.error( msg );
243                 throw new LdapNoPermissionException( msg );
244             }
245         }
246 
247         next( deleteContext );
248     }
249 
250 
251     /**
252      * {@inheritDoc}
253      */
254     @Override
255     public Entry lookup( LookupOperationContext lookupContext ) throws LdapException
256     {
257         CoreSession session = lookupContext.getSession();
258         Entry entry = next( lookupContext );
259 
260         if ( session.getDirectoryService().isAccessControlEnabled() )
261         {
262             return entry;
263         }
264 
265         protectLookUp( session.getEffectivePrincipal().getDn(), lookupContext.getDn() );
266 
267         return entry;
268     }
269 
270 
271     // ------------------------------------------------------------------------
272     // Entry Modification Operations
273     // ------------------------------------------------------------------------
274     /**
275      * This policy needs to be really tight too because some attributes may take
276      * part in giving the user permissions to protected resources.  We do not want
277      * users to self access these resources.  As far as we're concerned no one but
278      * the admin needs access.
279      */
280     /**
281      * {@inheritDoc}
282      */
283     @Override
284     public void modify( ModifyOperationContext modifyContext ) throws LdapException
285     {
286         if ( !modifyContext.getSession().getDirectoryService().isAccessControlEnabled() )
287         {
288             Dn dn = modifyContext.getDn();
289 
290             protectModifyAlterations( modifyContext, dn );
291             next( modifyContext );
292 
293             // update administrators if we change administrators group
294             if ( dn.getNormName().equals( adminGroupDn.getNormName() ) )
295             {
296                 loadAdministrators( modifyContext.getSession().getDirectoryService() );
297             }
298         }
299         else
300         {
301             next( modifyContext );
302         }
303     }
304 
305 
306     /**
307      * {@inheritDoc}
308      */
309     @Override
310     public void move( MoveOperationContext moveContext ) throws LdapException
311     {
312         if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() )
313         {
314             protectDnAlterations( moveContext, moveContext.getDn() );
315         }
316 
317         next( moveContext );
318     }
319 
320 
321     /**
322      * {@inheritDoc}
323      */
324     @Override
325     public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException
326     {
327         if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() )
328         {
329             protectDnAlterations( moveAndRenameContext, moveAndRenameContext.getDn() );
330         }
331 
332         next( moveAndRenameContext );
333     }
334 
335 
336     // ------------------------------------------------------------------------
337     // Dn altering operations are a no no for any user entry.  Basically here
338     // are the rules of conduct to follow:
339     //
340     //  o No user should have the ability to move or rename their entry
341     //  o Only the administrator can move or rename non-admin user entries
342     //  o The administrator entry cannot be moved or renamed by anyone
343     // ------------------------------------------------------------------------
344     /**
345      * {@inheritDoc}
346      */
347     @Override
348     public void rename( RenameOperationContext renameContext ) throws LdapException
349     {
350         if ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() )
351         {
352             protectDnAlterations( renameContext, renameContext.getDn() );
353         }
354 
355         next( renameContext );
356     }
357 
358 
359     /**
360      * {@inheritDoc}
361      */
362     @Override
363     public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException
364     {
365         EntryFilteringCursor cursor = next( searchContext );
366 
367         if ( searchContext.getSession().getDirectoryService().isAccessControlEnabled() )
368         {
369             return cursor;
370         }
371 
372         cursor.addEntryFilter( new DefaultAuthorizationSearchFilter() );
373 
374         return cursor;
375     }
376 
377 
378     private boolean isTheAdministrator( Dn dn )
379     {
380         return dn.getNormName().equals( adminSystemDn.getNormName() );
381     }
382 
383 
384     private boolean isAnAdministrator( Dn dn )
385     {
386         return isTheAdministrator( dn ) || administrators.contains( dn.getNormName() );
387     }
388 
389 
390     private void protectModifyAlterations( OperationContext opCtx, Dn dn ) throws LdapException
391     {
392         Dn principalDn = getPrincipal( opCtx ).getDn();
393 
394         if ( dn.isEmpty() )
395         {
396             String msg = I18n.err( I18n.ERR_17 );
397             LOG.error( msg );
398             throw new LdapNoPermissionException( msg );
399         }
400 
401         if ( !isAnAdministrator( principalDn ) )
402         {
403             // allow self modifications
404             if ( dn.equals( getPrincipal( opCtx ).getDn() ) )
405             {
406                 return;
407             }
408 
409             if ( dn.equals( adminSystemDn ) )
410             {
411                 String msg = I18n.err( I18n.ERR_18, principalDn.getName() );
412                 LOG.error( msg );
413                 throw new LdapNoPermissionException( msg );
414             }
415 
416             if ( dn.size() > 2 )
417             {
418                 if ( dn.isDescendantOf( adminSystemDn ) )
419                 {
420                     String msg = I18n.err( I18n.ERR_19, principalDn.getName(), dn.getName() );
421                     LOG.error( msg );
422                     throw new LdapNoPermissionException( msg );
423                 }
424 
425                 if ( dn.isDescendantOf( groupsBaseDn ) )
426                 {
427                     String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() );
428                     LOG.error( msg );
429                     throw new LdapNoPermissionException( msg );
430                 }
431 
432                 if ( dn.isDescendantOf( usersBaseDn ) )
433                 {
434                     String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() );
435                     LOG.error( msg );
436                     throw new LdapNoPermissionException( msg );
437                 }
438             }
439         }
440     }
441 
442 
443     private void protectDnAlterations( OperationContext opCtx, Dn dn ) throws LdapException
444     {
445         Dn principalDn = getPrincipal( opCtx ).getDn();
446 
447         if ( dn.isEmpty() )
448         {
449             String msg = I18n.err( I18n.ERR_234 );
450             LOG.error( msg );
451             throw new LdapNoPermissionException( msg );
452         }
453 
454         if ( dn.equals( adminGroupDn ) )
455         {
456             String msg = I18n.err( I18n.ERR_21 );
457             LOG.error( msg );
458             throw new LdapNoPermissionException( msg );
459         }
460 
461         if ( isTheAdministrator( dn ) )
462         {
463             String msg = I18n.err( I18n.ERR_22, principalDn.getName(), dn.getName() );
464             LOG.error( msg );
465             throw new LdapNoPermissionException( msg );
466         }
467 
468         if ( ( dn.size() > 2 ) && !isAnAdministrator( principalDn ) )
469         {
470             if ( dn.isDescendantOf( adminSystemDn ) )
471             {
472                 String msg = I18n.err( I18n.ERR_23, principalDn.getName(), dn.getName() );
473                 LOG.error( msg );
474                 throw new LdapNoPermissionException( msg );
475             }
476 
477             if ( dn.isDescendantOf( groupsBaseDn ) )
478             {
479                 String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() );
480                 LOG.error( msg );
481                 throw new LdapNoPermissionException( msg );
482             }
483 
484             if ( dn.isDescendantOf( usersBaseDn ) )
485             {
486                 String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() );
487                 LOG.error( msg );
488                 throw new LdapNoPermissionException( msg );
489             }
490         }
491     }
492 
493 
494     private void protectLookUp( Dn principalDn, Dn normalizedDn ) throws LdapException
495     {
496         if ( !isAnAdministrator( principalDn ) )
497         {
498             if ( normalizedDn.size() > 2 )
499             {
500                 if ( normalizedDn.isDescendantOf( adminSystemDn ) )
501                 {
502                     // allow for self reads
503                     if ( normalizedDn.equals( principalDn ) )
504                     {
505                         return;
506                     }
507 
508                     String msg = I18n.err( I18n.ERR_25, normalizedDn.getName(), principalDn.getName() );
509                     LOG.error( msg );
510                     throw new LdapNoPermissionException( msg );
511                 }
512 
513                 if ( normalizedDn.isDescendantOf( groupsBaseDn ) || normalizedDn.isDescendantOf( usersBaseDn ) )
514                 {
515                     // allow for self reads
516                     if ( normalizedDn.equals( principalDn ) )
517                     {
518                         return;
519                     }
520 
521                     String msg = I18n.err( I18n.ERR_26, normalizedDn.getName(), principalDn.getName() );
522                     LOG.error( msg );
523                     throw new LdapNoPermissionException( msg );
524                 }
525             }
526 
527             if ( isTheAdministrator( normalizedDn ) )
528             {
529                 // allow for self reads
530                 if ( normalizedDn.equals( principalDn ) )
531                 {
532                     return;
533                 }
534 
535                 String msg = I18n.err( I18n.ERR_27, principalDn.getName() );
536                 LOG.error( msg );
537                 throw new LdapNoPermissionException( msg );
538             }
539         }
540     }
541 
542 
543     // False positive, we want to keep the comment
544     private boolean isSearchable( OperationContext opContext, Entry entry ) throws LdapException
545     {
546         Dn principalDn = opContext.getSession().getEffectivePrincipal().getDn();
547         Dn dn = entry.getDn();
548         
549         if ( !dn.isSchemaAware() )
550         {
551             dn = new Dn( schemaManager, dn );
552         }
553 
554         // Admin users gets full access to all entries
555         if ( isAnAdministrator( principalDn ) )
556         {
557             return true;
558         }
559 
560         // Users reading their own entries should be allowed to see all
561         boolean isSelfRead = dn.equals( principalDn );
562 
563         if ( isSelfRead )
564         {
565             return true;
566         }
567 
568         // Block off reads to anything under ou=users and ou=groups if not a self read
569         if ( dn.size() >= 2 )
570         {
571             // stuff this if in here instead of up in outer if to prevent
572             // constant needless reexecution for all entries in other depths
573 
574             if ( dn.isDescendantOf( adminSystemDn ) || dn.isDescendantOf( groupsBaseDn )
575                 || dn.isDescendantOf( usersBaseDn ) )
576             {
577                 return false;
578             }
579         }
580 
581         // Non-admin users cannot read the admin entry
582         return !isTheAdministrator( dn );
583     }
584 }