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