1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
101
102
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
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
230
231
232
233
234
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
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
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
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 }