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 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
97
98
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
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
225
226
227
228
229
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
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
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
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
379
380
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 }