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