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 javax.naming.NoPermissionException;
24  
25  import java.util.HashSet;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.directory.server.constants.ServerDNConstants;
30  import org.apache.directory.server.core.CoreSession;
31  import org.apache.directory.server.core.DefaultCoreSession;
32  import org.apache.directory.server.core.DirectoryService;
33  import org.apache.directory.server.core.authn.LdapPrincipal;
34  import org.apache.directory.server.core.entry.ClonedServerEntry;
35  import org.apache.directory.server.core.entry.ServerEntry;
36  import org.apache.directory.server.core.filtering.EntryFilter;
37  import org.apache.directory.server.core.filtering.EntryFilteringCursor;
38  import org.apache.directory.server.core.interceptor.BaseInterceptor;
39  import org.apache.directory.server.core.interceptor.Interceptor;
40  import org.apache.directory.server.core.interceptor.NextInterceptor;
41  import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
42  import org.apache.directory.server.core.interceptor.context.ListOperationContext;
43  import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
44  import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
45  import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
46  import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
47  import org.apache.directory.server.core.interceptor.context.OperationContext;
48  import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
49  import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
50  import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
51  import org.apache.directory.server.core.partition.PartitionNexus;
52  import org.apache.directory.server.schema.registries.AttributeTypeRegistry;
53  import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
54  import org.apache.directory.shared.ldap.constants.SchemaConstants;
55  import org.apache.directory.shared.ldap.entry.EntryAttribute;
56  import org.apache.directory.shared.ldap.entry.Value;
57  import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
58  import org.apache.directory.shared.ldap.name.LdapDN;
59  import org.apache.directory.shared.ldap.schema.AttributeType;
60  import org.apache.directory.shared.ldap.schema.OidNormalizer;
61  import org.slf4j.Logger;
62  import org.slf4j.LoggerFactory;
63  
64  
65  /**
66   * An {@link Interceptor} that controls access to {@link PartitionNexus}.
67   * If a user tries to perform any operations that requires
68   * permission he or she doesn't have, {@link NoPermissionException} will be
69   * thrown and therefore the current invocation chain will terminate.
70   *
71   * @org.apache.xbean.XBean
72   *
73   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
74   * @version $Rev: 690042 $, $Date: 2008-08-29 01:14:21 +0200 (Fr, 29 Aug 2008) $
75   */
76  public class DefaultAuthorizationInterceptor extends BaseInterceptor
77  {
78      /** the logger for this class */
79      private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class );
80  
81      /**
82       * the base distinguished {@link Name} for all users
83       */
84      private static LdapDN USER_BASE_DN;
85  
86      /**
87       * the base distinguished {@link Name} for all groups
88       */
89      private static LdapDN GROUP_BASE_DN;
90  
91      /**
92       * the distinguished {@link Name} for the administrator group
93       */
94      private static LdapDN ADMIN_GROUP_DN;
95  
96      /**
97       * the name parser used by this service
98       */
99      private boolean enabled = true;
100     
101     private Set<String> administrators = new HashSet<String>(2);
102     
103     /** The normalizer mapping containing a relation between an OID and a normalizer */
104     private Map<String, OidNormalizer> normalizerMapping;
105     
106     private PartitionNexus nexus;
107 
108     /** A starage for the uniqueMember attributeType */
109     private AttributeType uniqueMemberAT;
110 
111 
112     /**
113      * Creates a new instance.
114      */
115     public DefaultAuthorizationInterceptor()
116     {
117     }
118 
119 
120     public void init( DirectoryService directoryService ) throws Exception
121     {
122         nexus = directoryService.getPartitionNexus();
123         normalizerMapping = directoryService.getRegistries().getAttributeTypeRegistry().getNormalizerMapping();
124 
125         // disable this static module if basic access control mechanisms are enabled
126         enabled = ! directoryService.isAccessControlEnabled();
127         
128         USER_BASE_DN = PartitionNexus.getUsersBaseName();
129         USER_BASE_DN.normalize( normalizerMapping );
130         
131         GROUP_BASE_DN = PartitionNexus.getGroupsBaseName();
132         GROUP_BASE_DN.normalize( normalizerMapping );
133      
134         ADMIN_GROUP_DN = new LdapDN( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
135         ADMIN_GROUP_DN.normalize( normalizerMapping );
136 
137         AttributeTypeRegistry attrRegistry = directoryService.getRegistries().getAttributeTypeRegistry();
138         
139         uniqueMemberAT = attrRegistry.lookup( SchemaConstants.UNIQUE_MEMBER_AT_OID );
140         
141         loadAdministrators( directoryService );
142     }
143     
144     
145     private void loadAdministrators( DirectoryService directoryService ) throws Exception
146     {
147         // read in the administrators and cache their normalized names
148         Set<String> newAdministrators = new HashSet<String>( 2 );
149         LdapDN adminDn = new LdapDN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
150         adminDn.normalize( directoryService.getRegistries().getAttributeTypeRegistry().getNormalizerMapping() );
151         CoreSession adminSession = new DefaultCoreSession( 
152             new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
153 
154         ServerEntry adminGroup = nexus.lookup( new LookupOperationContext( adminSession, ADMIN_GROUP_DN ) );
155         
156         if ( adminGroup == null )
157         {
158             return;
159         }
160         
161         EntryAttribute uniqueMember = adminGroup.get( uniqueMemberAT );
162         
163         for ( Value<?> value:uniqueMember )
164         {
165             LdapDN memberDn = new LdapDN( ( String ) value.get() );
166             memberDn.normalize( normalizerMapping );
167             newAdministrators.add( memberDn.getNormName() );
168         }
169         
170         administrators = newAdministrators;
171     }
172 
173     
174     // Note:
175     //    Lookup, search and list operations need to be handled using a filter
176     // and so we need access to the filter service.
177 
178     public void delete( NextInterceptor nextInterceptor, DeleteOperationContext opContext ) throws Exception
179     {
180         LdapDN name = opContext.getDn();
181         
182         if ( !enabled )
183         {
184             nextInterceptor.delete( opContext );
185             return;
186         }
187 
188         LdapDN principalDn = getPrincipal().getJndiName();
189 
190         if ( name.isEmpty() )
191         {
192             String msg = "The rootDSE cannot be deleted!";
193             LOG.error( msg );
194             throw new LdapNoPermissionException( msg );
195         }
196 
197         if ( name.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
198         {
199             String msg = "The Administrators group cannot be deleted!";
200             LOG.error( msg );
201             throw new LdapNoPermissionException( msg );
202         }
203 
204         if ( isTheAdministrator( name ) )
205         {
206             String msg = "User " + principalDn.getUpName();
207             msg += " does not have permission to delete the admin account.";
208             msg += " No one not even the admin can delete this account!";
209             LOG.error( msg );
210             throw new LdapNoPermissionException( msg );
211         }
212 
213         if ( name.size() > 2 )
214         {
215             if ( !isAnAdministrator( principalDn ) )
216             {
217                 if ( name.startsWith( USER_BASE_DN ) )
218                 {
219                     String msg = "User " + principalDn.getUpName();
220                     msg += " does not have permission to delete the user account: ";
221                     msg += name.getUpName() + ". Only the admin can delete user accounts.";
222                     LOG.error( msg );
223                     throw new LdapNoPermissionException( msg );
224                 }
225         
226                 if ( name.startsWith( GROUP_BASE_DN ) )
227                 {
228                     String msg = "User " + principalDn.getUpName();
229                     msg += " does not have permission to delete the group entry: ";
230                     msg += name.getUpName() + ". Only the admin can delete groups.";
231                     LOG.error( msg );
232                     throw new LdapNoPermissionException( msg );
233                 }
234             }
235         }
236 
237         nextInterceptor.delete( opContext );
238     }
239 
240     
241     private boolean isTheAdministrator( LdapDN normalizedDn )
242     {
243         return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
244     }
245     
246     
247     private boolean isAnAdministrator( LdapDN normalizedDn )
248     {
249         return isTheAdministrator( normalizedDn ) || administrators.contains( normalizedDn.getNormName() );
250 
251     }
252     
253 
254     // ------------------------------------------------------------------------
255     // Entry Modification Operations
256     // ------------------------------------------------------------------------
257 
258     /**
259      * This policy needs to be really tight too because some attributes may take
260      * part in giving the user permissions to protected resources.  We do not want
261      * users to self access these resources.  As far as we're concerned no one but
262      * the admin needs access.
263      */
264     public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext )
265         throws Exception
266     {
267         if ( enabled )
268         {
269             LdapDN dn = opContext.getDn();
270             
271             protectModifyAlterations( dn );
272             nextInterceptor.modify( opContext );
273 
274             // update administrators if we change administrators group
275             if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
276             {
277                 loadAdministrators( opContext.getSession().getDirectoryService() );
278             }
279         }
280         else
281         {
282             nextInterceptor.modify( opContext );
283         }
284     }
285 
286 
287     private void protectModifyAlterations( LdapDN dn ) throws Exception
288     {
289         LdapDN principalDn = getPrincipal().getJndiName();
290 
291         if ( dn.isEmpty() )
292         {
293             String msg = "The rootDSE cannot be modified!";
294             LOG.error( msg );
295             throw new LdapNoPermissionException( msg );
296         }
297 
298         if ( ! isAnAdministrator( principalDn ) )
299         {
300             // allow self modifications 
301             if ( dn.getNormName().equals( getPrincipal().getJndiName().getNormName() ) )
302             {
303                 return;
304             }
305             
306             if ( dn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) )
307             {
308                 String msg = "User " + principalDn.getUpName();
309                 msg += " does not have permission to modify the account of the";
310                 msg += " admin user.";
311                 LOG.error( msg );
312                 throw new LdapNoPermissionException( msg );
313             }
314 
315             if ( dn.size() > 2 ) 
316                 {
317                 if ( dn.startsWith( USER_BASE_DN ) )
318                 {
319                     String msg = "User " + principalDn.getUpName();
320                     msg += " does not have permission to modify the account of the";
321                     msg += " user " + dn.getUpName() + ".\nEven the owner of an account cannot";
322                     msg += " modify it.\nUser accounts can only be modified by the";
323                     msg += " administrator.";
324                     LOG.error( msg );
325                     throw new LdapNoPermissionException( msg );
326                 }
327     
328                 if ( dn.startsWith( GROUP_BASE_DN ) )
329                 {
330                     String msg = "User " + principalDn.getUpName();
331                     msg += " does not have permission to modify the group entry ";
332                     msg += dn.getUpName() + ".\nGroups can only be modified by the admin.";
333                     LOG.error( msg );
334                     throw new LdapNoPermissionException( msg );
335                 }
336             }
337         }
338     }
339     
340     
341     // ------------------------------------------------------------------------
342     // DN altering operations are a no no for any user entry.  Basically here
343     // are the rules of conduct to follow:
344     //
345     //  o No user should have the ability to move or rename their entry
346     //  o Only the administrator can move or rename non-admin user entries
347     //  o The administrator entry cannot be moved or renamed by anyone
348     // ------------------------------------------------------------------------
349 
350     public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext )
351         throws Exception
352     {
353         if ( enabled )
354         {
355             protectDnAlterations( opContext.getDn() );
356         }
357         
358         nextInterceptor.rename( opContext );
359     }
360 
361 
362     public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception
363     {
364         if ( enabled )
365         {
366             protectDnAlterations( opContext.getDn() );
367         }
368         
369         nextInterceptor.move( opContext );
370     }
371 
372 
373     public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) throws Exception
374     {
375         if ( enabled )
376         {
377             protectDnAlterations( opContext.getDn() );
378         }
379         
380         nextInterceptor.moveAndRename( opContext );
381     }
382 
383 
384     private void protectDnAlterations( LdapDN dn ) throws Exception
385     {
386         LdapDN principalDn = getPrincipal().getJndiName();
387 
388         if ( dn.isEmpty() )
389         {
390             String msg = "The rootDSE cannot be moved or renamed!";
391             LOG.error( msg );
392             throw new LdapNoPermissionException( msg );
393         }
394 
395         if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
396         {
397             String msg = "The Administrators group cannot be moved or renamed!";
398             LOG.error( msg );
399             throw new LdapNoPermissionException( msg );
400         }
401         
402         if ( isTheAdministrator( dn ) )
403         {
404             String msg = "User '" + principalDn.getUpName();
405             msg += "' does not have permission to move or rename the admin";
406             msg += " account.  No one not even the admin can move or";
407             msg += " rename " + dn.getUpName() + "!";
408             LOG.error( msg );
409             throw new LdapNoPermissionException( msg );
410         }
411 
412         if ( dn.size() > 2 && dn.startsWith( USER_BASE_DN ) && !isAnAdministrator( principalDn ) )
413         {
414             String msg = "User '" + principalDn.getUpName();
415             msg += "' does not have permission to move or rename the user";
416             msg += " account: " + dn.getUpName() + ". Only the admin can move or";
417             msg += " rename user accounts.";
418             LOG.error( msg );
419             throw new LdapNoPermissionException( msg );
420         }
421 
422         if ( dn.size() > 2 && dn.startsWith( GROUP_BASE_DN ) && !isAnAdministrator( principalDn ) )
423         {
424             String msg = "User " + principalDn.getUpName();
425             msg += " does not have permission to move or rename the group entry ";
426             msg += dn.getUpName() + ".\nGroups can only be moved or renamed by the admin.";
427             throw new LdapNoPermissionException( msg );
428         }
429     }
430 
431 
432     public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception
433     {
434         ClonedServerEntry serverEntry = nextInterceptor.lookup( opContext );
435         
436         if ( !enabled || ( serverEntry == null ) )
437         {
438             return serverEntry;
439         }
440 
441         protectLookUp( opContext.getSession().getEffectivePrincipal().getJndiName(), opContext.getDn() );
442         return serverEntry;
443     }
444 
445 
446     private void protectLookUp( LdapDN principalDn, LdapDN normalizedDn ) throws Exception
447     {
448         if ( !isAnAdministrator( principalDn ) )
449         {
450             if ( normalizedDn.size() > 2 )
451             {
452                 if( normalizedDn.startsWith( USER_BASE_DN ) )
453                 {
454                     // allow for self reads
455                     if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
456                     {
457                         return;
458                     }
459     
460                     String msg = "Access to user account '" + normalizedDn.getUpName() + "' not permitted";
461                     msg += " for user '" + principalDn.getUpName() + "'.  Only the admin can";
462                     msg += " access user account information";
463                     LOG.error( msg );
464                     throw new LdapNoPermissionException( msg );
465                 }
466 
467                 if ( normalizedDn.startsWith( GROUP_BASE_DN ) )
468                 {
469                     // allow for self reads
470                     if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
471                     {
472                         return;
473                     }
474     
475                     String msg = "Access to group '" + normalizedDn.getUpName() + "' not permitted";
476                     msg += " for user '" + principalDn.getUpName() + "'.  Only the admin can";
477                     msg += " access group information";
478                     LOG.error( msg );
479                     throw new LdapNoPermissionException( msg );
480                 }
481             }
482 
483             if ( isTheAdministrator( normalizedDn ) )
484             {
485                 // allow for self reads
486                 if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
487                 {
488                     return;
489                 }
490 
491                 String msg = "Access to admin account not permitted for user '";
492                 msg += principalDn.getUpName() + "'.  Only the admin can";
493                 msg += " access admin account information";
494                 LOG.error( msg );
495                 throw new LdapNoPermissionException( msg );
496             }
497         }
498     }
499 
500 
501     public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception
502     {
503         EntryFilteringCursor cursor = nextInterceptor.search( opContext );
504 
505         if ( !enabled )
506         {
507             return cursor;
508         }
509 
510         cursor.addEntryFilter( new EntryFilter() {
511             public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
512             {
513                 return DefaultAuthorizationInterceptor.this.isSearchable( operation, result );
514             }
515         } );
516         return cursor;
517     }
518 
519 
520     public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception
521     {
522         EntryFilteringCursor cursor = nextInterceptor.list( opContext );
523         
524         if ( !enabled )
525         {
526             return cursor;
527         }
528 
529         cursor.addEntryFilter( new EntryFilter()
530         {
531             public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry ) throws Exception
532             {
533                 return DefaultAuthorizationInterceptor.this.isSearchable( operation, entry );
534             }
535         } );
536         return cursor;
537     }
538 
539 
540     private boolean isSearchable( OperationContext opContext, ClonedServerEntry result ) throws Exception
541     {
542         LdapDN principalDn = opContext.getSession().getEffectivePrincipal().getJndiName();
543         LdapDN dn = result.getDn();
544         
545         if ( !dn.isNormalized() )
546         {
547             dn.normalize( normalizerMapping );
548         }
549 
550         // Admin users gets full access to all entries
551         if ( isAnAdministrator( principalDn ) )
552         {
553             return true;
554         }
555         
556         // Users reading their own entries should be allowed to see all
557         boolean isSelfRead = dn.getNormName().equals( principalDn.getNormName() );
558         
559         if ( isSelfRead )
560         {
561             return true;
562         }
563         
564         // Block off reads to anything under ou=users and ou=groups if not a self read
565         if ( dn.size() > 2 )
566         {
567             // stuff this if in here instead of up in outer if to prevent 
568             // constant needless reexecution for all entries in other depths
569             
570             if ( dn.getNormName().endsWith( USER_BASE_DN.getNormName() ) 
571                 || dn.getNormName().endsWith( GROUP_BASE_DN.getNormName() ) )
572             {
573                 return false;
574             }
575         }
576         
577         // Non-admin users cannot read the admin entry
578         return ! isTheAdministrator( dn );
579 
580     }
581 }