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 java.util.Map;
21  
22  import javax.naming.Name;
23  import javax.naming.NamingEnumeration;
24  import javax.naming.NamingException;
25  import javax.naming.NoPermissionException;
26  import javax.naming.directory.Attributes;
27  import javax.naming.directory.ModificationItem;
28  import javax.naming.directory.SearchControls;
29  import javax.naming.directory.SearchResult;
30  import javax.naming.ldap.LdapContext;
31  
32  import org.apache.ldap.common.exception.LdapNoPermissionException;
33  import org.apache.ldap.common.filter.ExprNode;
34  import org.apache.ldap.common.name.DnParser;
35  import org.apache.ldap.server.DirectoryServiceConfiguration;
36  import org.apache.ldap.server.configuration.InterceptorConfiguration;
37  import org.apache.ldap.server.enumeration.SearchResultFilteringEnumeration;
38  import org.apache.ldap.server.enumeration.SearchResultFilter;
39  import org.apache.ldap.server.interceptor.BaseInterceptor;
40  import org.apache.ldap.server.interceptor.Interceptor;
41  import org.apache.ldap.server.interceptor.NextInterceptor;
42  import org.apache.ldap.server.invocation.InvocationStack;
43  import org.apache.ldap.server.invocation.Invocation;
44  import org.apache.ldap.server.jndi.ServerContext;
45  import org.apache.ldap.server.partition.DirectoryPartitionNexus;
46  import org.apache.ldap.server.schema.AttributeTypeRegistry;
47  import org.apache.ldap.server.schema.ConcreteNameComponentNormalizer;
48  
49  
50  /***
51   * An {@link Interceptor} that controls access to {@link DirectoryPartitionNexus}.
52   * If a user tries to perform any operations that requires
53   * permission he or she doesn't have, {@link NoPermissionException} will be
54   * thrown and therefore the current invocation chain will terminate.
55   *
56   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
57   * @version $Rev: 201550 $, $Date: 2005-06-23 23:08:31 -0400 (Thu, 23 Jun 2005) $
58   */
59  public class OldAuthorizationService extends BaseInterceptor
60  {
61      /***
62       * the administrator's distinguished {@link Name}
63       */
64      private static final Name ADMIN_DN = DirectoryPartitionNexus.getAdminName();
65  
66      /***
67       * the base distinguished {@link Name} for all users
68       */
69      private static final Name USER_BASE_DN = DirectoryPartitionNexus.getUsersBaseName();
70  
71      /***
72       * the base distinguished {@link Name} for all groups
73       */
74      private static final Name GROUP_BASE_DN = DirectoryPartitionNexus.getGroupsBaseName();
75  
76      /***
77       * the name parser used by this service
78       */
79      private DnParser dnParser;
80      private boolean enabled = true;
81  
82  
83  
84      /***
85       * Creates a new instance.
86       */
87      public OldAuthorizationService()
88      {
89      }
90  
91  
92      public void init( DirectoryServiceConfiguration factoryCfg, InterceptorConfiguration cfg ) throws NamingException
93      {
94          AttributeTypeRegistry atr = factoryCfg.getGlobalRegistries().getAttributeTypeRegistry();
95          dnParser = new DnParser( new ConcreteNameComponentNormalizer( atr ) );
96  
97          // disable this static module if basic access control mechanisms are enabled
98          enabled = ! factoryCfg.getStartupConfiguration().isAccessControlEnabled();
99      }
100 
101 
102     // Note:
103     //    Lookup, search and list operations need to be handled using a filter
104     // and so we need access to the filter service.
105 
106     public void delete( NextInterceptor nextInterceptor, Name name ) throws NamingException
107     {
108         if ( !enabled )
109         {
110             nextInterceptor.delete( name );
111             return;
112         }
113 
114         Name principalDn = getPrincipal().getJndiName();
115 
116         if ( name.toString().equals( "" ) )
117         {
118             String msg = "The rootDSE cannot be deleted!";
119             throw new LdapNoPermissionException( msg );
120         }
121 
122         if ( name == ADMIN_DN || name.equals( ADMIN_DN ) )
123         {
124             String msg = "User " + principalDn;
125             msg += " does not have permission to delete the admin account.";
126             msg += " No one not even the admin can delete this account!";
127             throw new LdapNoPermissionException( msg );
128         }
129 
130         if ( name.size() > 2 && name.startsWith( USER_BASE_DN )
131                 && !principalDn.equals( ADMIN_DN ) )
132         {
133             String msg = "User " + principalDn;
134             msg += " does not have permission to delete the user account: ";
135             msg += name + ". Only the admin can delete user accounts.";
136             throw new LdapNoPermissionException( msg );
137         }
138 
139         if ( name.size() > 2 && name.startsWith( GROUP_BASE_DN )
140                 && !principalDn.equals( ADMIN_DN ) )
141         {
142             String msg = "User " + principalDn;
143             msg += " does not have permission to delete the group entry: ";
144             msg += name + ". Only the admin can delete groups.";
145             throw new LdapNoPermissionException( msg );
146         }
147 
148         nextInterceptor.delete( name );
149     }
150 
151 
152     /***
153      * Note that we do nothing here. First because this is not an externally
154      * exposed function via the JNDI interfaces.  It is used internally by
155      * the provider for optimization purposes so there is no reason for us to
156      * start to constrain it.
157      */
158     public boolean hasEntry( NextInterceptor nextInterceptor, Name name ) throws NamingException
159     {
160         return super.hasEntry( nextInterceptor, name );
161     }
162 
163 
164     // ------------------------------------------------------------------------
165     // Entry Modification Operations
166     // ------------------------------------------------------------------------
167 
168 
169     /***
170      * This policy needs to be really tight too because some attributes may take
171      * part in giving the user permissions to protected resources.  We do not want
172      * users to self access these resources.  As far as we're concerned no one but
173      * the admin needs access.
174      */
175     public void modify( NextInterceptor nextInterceptor, Name name, int modOp, Attributes attrs ) throws NamingException
176     {
177         if ( enabled )
178         {
179             protectModifyAlterations( name );
180         }
181 
182         nextInterceptor.modify( name, modOp, attrs );
183     }
184 
185 
186     /***
187      * This policy needs to be really tight too because some attributes may take part
188      * in giving the user permissions to protected resources.  We do not want users to
189      * self access these resources.  As far as we're concerned no one but the admin
190      * needs access.
191      */
192     public void modify( NextInterceptor nextInterceptor, Name name, ModificationItem[] items ) throws NamingException
193     {
194         if ( enabled )
195         {
196             protectModifyAlterations( name );
197         }
198         nextInterceptor.modify( name, items );
199     }
200 
201 
202     private void protectModifyAlterations( Name dn ) throws LdapNoPermissionException
203     {
204         Name principalDn = getPrincipal().getJndiName();
205 
206         if ( dn.toString().equals( "" ) )
207         {
208             String msg = "The rootDSE cannot be modified!";
209             throw new LdapNoPermissionException( msg );
210         }
211 
212         if ( !principalDn.equals( ADMIN_DN ) )
213         {
214             if ( dn.equals( DirectoryPartitionNexus.ADMIN_PRINCIPAL ) )
215             {
216                 String msg = "User " + principalDn;
217                 msg += " does not have permission to modify the account of the";
218                 msg += " admin user.";
219                 throw new LdapNoPermissionException( msg );
220             }
221             
222             if ( dn.size() > 2 && dn.startsWith( USER_BASE_DN ) )
223             {
224                 String msg = "User " + principalDn;
225                 msg += " does not have permission to modify the account of the";
226                 msg += " user " + dn + ".\nEven the owner of an account cannot";
227                 msg += " modify it.\nUser accounts can only be modified by the";
228                 msg += " administrator.";
229                 throw new LdapNoPermissionException( msg );
230             }
231 
232             if ( dn.size() > 2 && dn.startsWith( GROUP_BASE_DN ) )
233             {
234                 String msg = "User " + principalDn;
235                 msg += " does not have permission to modify the group entry ";
236                 msg += dn + ".\nGroups can only be modified by the admin.";
237                 throw new LdapNoPermissionException( msg );
238             }
239         }
240     }
241 
242 
243     // ------------------------------------------------------------------------
244     // DN altering operations are a no no for any user entry.  Basically here
245     // are the rules of conduct to follow:
246     //
247     //  o No user should have the ability to move or rename their entry
248     //  o Only the administrator can move or rename non-admin user entries
249     //  o The administrator entry cannot be moved or renamed by anyone
250     // ------------------------------------------------------------------------
251 
252 
253     public void modifyRn( NextInterceptor nextInterceptor, Name name, String newRn, boolean deleteOldRn ) throws NamingException
254     {
255         if ( enabled )
256         {
257             protectDnAlterations( name );
258         }
259         nextInterceptor.modifyRn( name, newRn, deleteOldRn );
260     }
261 
262 
263     public void move( NextInterceptor nextInterceptor, Name oriChildName, Name newParentName ) throws NamingException
264     {
265         if ( enabled )
266         {
267             protectDnAlterations( oriChildName );
268         }
269         nextInterceptor.move( oriChildName, newParentName );
270     }
271 
272 
273     public void move( NextInterceptor nextInterceptor,
274             Name oriChildName, Name newParentName, String newRn,
275             boolean deleteOldRn ) throws NamingException
276     {
277         if ( enabled )
278         {
279             protectDnAlterations( oriChildName );
280         }
281         nextInterceptor.move( oriChildName, newParentName, newRn, deleteOldRn );
282     }
283 
284 
285     private void protectDnAlterations( Name dn ) throws LdapNoPermissionException
286     {
287         Name principalDn = getPrincipal().getJndiName();
288 
289         if ( dn.toString().equals( "" ) )
290         {
291             String msg = "The rootDSE cannot be moved or renamed!";
292             throw new LdapNoPermissionException( msg );
293         }
294 
295         if ( dn == ADMIN_DN || dn.equals( ADMIN_DN ) )
296         {
297             String msg = "User '" + principalDn;
298             msg += "' does not have permission to move or rename the admin";
299             msg += " account.  No one not even the admin can move or";
300             msg += " rename " + dn + "!";
301             throw new LdapNoPermissionException( msg );
302         }
303 
304         if ( dn.size() > 2 && dn.startsWith( USER_BASE_DN ) && !principalDn.equals( ADMIN_DN ) )
305         {
306             String msg = "User '" + principalDn;
307             msg += "' does not have permission to move or rename the user";
308             msg += " account: " + dn + ". Only the admin can move or";
309             msg += " rename user accounts.";
310             throw new LdapNoPermissionException( msg );
311         }
312 
313         if ( dn.size() > 2 && dn.startsWith( GROUP_BASE_DN ) && !principalDn.equals( ADMIN_DN ) )
314         {
315             String msg = "User " + principalDn;
316             msg += " does not have permission to move or rename the group entry ";
317             msg += dn + ".\nGroups can only be moved or renamed by the admin.";
318             throw new LdapNoPermissionException( msg );
319         }
320     }
321 
322 
323     public Attributes lookup( NextInterceptor nextInterceptor, Name name ) throws NamingException
324     {
325         Attributes attributes = nextInterceptor.lookup( name );
326         if ( ! enabled || attributes == null )
327         {
328             return attributes;
329         }
330 
331         protectLookUp( name );
332         return attributes;
333     }
334 
335 
336     public Attributes lookup( NextInterceptor nextInterceptor, Name name, String[] attrIds ) throws NamingException
337     {
338         Attributes attributes = nextInterceptor.lookup( name, attrIds );
339         if ( ! enabled || attributes == null )
340         {
341             return attributes;
342         }
343 
344         protectLookUp( name );
345         return attributes;
346     }
347 
348 
349     private void protectLookUp( Name dn ) throws NamingException
350     {
351         LdapContext ctx =
352             ( LdapContext ) InvocationStack.getInstance().peek().getCaller();
353         Name principalDn = ( ( ServerContext ) ctx ).getPrincipal().getJndiName();
354 
355         if ( !principalDn.equals( ADMIN_DN ) )
356         {
357             if ( dn.size() > 2 && dn.startsWith( USER_BASE_DN ) )
358             {
359                 // allow for self reads
360                 if ( dn.toString().equals( principalDn.toString() ) )
361                 {
362                     return;
363                 }
364 
365                 String msg = "Access to user account '" + dn + "' not permitted";
366                 msg += " for user '" + principalDn + "'.  Only the admin can";
367                 msg += " access user account information";
368                 throw new LdapNoPermissionException( msg );
369             }
370 
371             if ( dn.size() > 2 && dn.startsWith( GROUP_BASE_DN ) )
372             {
373                 // allow for self reads
374                 if ( dn.toString().equals( principalDn.toString() ) )
375                 {
376                     return;
377                 }
378 
379                 String msg = "Access to group '" + dn + "' not permitted";
380                 msg += " for user '" + principalDn + "'.  Only the admin can";
381                 msg += " access group information";
382                 throw new LdapNoPermissionException( msg );
383             }
384 
385             if ( dn.equals( ADMIN_DN ) )
386             {
387                 // allow for self reads
388                 if ( dn.toString().equals( principalDn.toString() ) )
389                 {
390                     return;
391                 }
392 
393                 String msg = "Access to admin account not permitted for user '";
394                 msg += principalDn + "'.  Only the admin can";
395                 msg += " access admin account information";
396                 throw new LdapNoPermissionException( msg );
397             }
398         }
399     }
400 
401 
402     public NamingEnumeration search( NextInterceptor nextInterceptor,
403             Name base, Map env, ExprNode filter,
404             SearchControls searchCtls ) throws NamingException
405     {
406         NamingEnumeration e = nextInterceptor.search( base, env, filter, searchCtls );
407         if ( !enabled )
408         {
409             return e;
410         }
411         //if ( searchCtls.getReturningAttributes() != null )
412         //{
413         //    return null;
414         //}
415         
416         Invocation invocation = InvocationStack.getInstance().peek();
417         return new SearchResultFilteringEnumeration( e, searchCtls, invocation,
418             new SearchResultFilter()
419             {
420                 public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
421                         throws NamingException
422                 {
423                     return OldAuthorizationService.this.isSearchable( invocation, result );
424                 }
425             });
426     }
427 
428 
429     public NamingEnumeration list( NextInterceptor nextInterceptor, Name base ) throws NamingException
430     {
431         NamingEnumeration e = nextInterceptor.list( base );
432         if ( !enabled )
433         {
434             return e;
435         }
436 
437         Invocation invocation = InvocationStack.getInstance().peek();
438         return new SearchResultFilteringEnumeration( e, null, invocation,
439             new SearchResultFilter()
440             {
441                 public boolean accept( Invocation invocation, SearchResult result, SearchControls controls )
442                         throws NamingException
443                 {
444                     return OldAuthorizationService.this.isSearchable( invocation, result );
445                 }
446             } );
447     }
448 
449 
450     private boolean isSearchable( Invocation invocataion, SearchResult result )
451             throws NamingException
452     {
453         Name dn;
454 
455         synchronized ( dnParser )
456         {
457             dn = dnParser.parse( result.getName() );
458         }
459 
460         Name principalDn = ( ( ServerContext ) invocataion.getCaller() ).getPrincipal().getJndiName();
461         if ( !principalDn.equals( ADMIN_DN ) )
462         {
463             if ( dn.size() > 2 )
464             {
465                 if ( dn.startsWith( USER_BASE_DN ) || dn.startsWith( GROUP_BASE_DN ) )
466                 {
467                     return false;
468                 }
469             }
470 
471             if ( dn.equals( ADMIN_DN ) )
472             {
473                 return false;
474             }
475 
476         }
477 
478         return true;
479     }
480 }