001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.server.core.authz;
021
022
023 import java.util.HashSet;
024 import java.util.Set;
025
026 import javax.naming.NoPermissionException;
027
028 import org.apache.directory.server.constants.ServerDNConstants;
029 import org.apache.directory.server.core.CoreSession;
030 import org.apache.directory.server.core.DefaultCoreSession;
031 import org.apache.directory.server.core.DirectoryService;
032 import org.apache.directory.server.core.LdapPrincipal;
033 import org.apache.directory.server.core.entry.ClonedServerEntry;
034 import org.apache.directory.server.core.filtering.EntryFilter;
035 import org.apache.directory.server.core.filtering.EntryFilteringCursor;
036 import org.apache.directory.server.core.interceptor.BaseInterceptor;
037 import org.apache.directory.server.core.interceptor.Interceptor;
038 import org.apache.directory.server.core.interceptor.NextInterceptor;
039 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
040 import org.apache.directory.server.core.interceptor.context.ListOperationContext;
041 import org.apache.directory.server.core.interceptor.context.LookupOperationContext;
042 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
043 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
044 import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
045 import org.apache.directory.server.core.interceptor.context.OperationContext;
046 import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
047 import org.apache.directory.server.core.interceptor.context.SearchOperationContext;
048 import org.apache.directory.server.core.interceptor.context.SearchingOperationContext;
049 import org.apache.directory.server.core.partition.DefaultPartitionNexus;
050 import org.apache.directory.server.core.partition.PartitionNexus;
051 import org.apache.directory.server.i18n.I18n;
052 import org.apache.directory.shared.ldap.constants.AuthenticationLevel;
053 import org.apache.directory.shared.ldap.constants.SchemaConstants;
054 import org.apache.directory.shared.ldap.entry.EntryAttribute;
055 import org.apache.directory.shared.ldap.entry.ServerEntry;
056 import org.apache.directory.shared.ldap.entry.Value;
057 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException;
058 import org.apache.directory.shared.ldap.name.DN;
059 import org.apache.directory.shared.ldap.schema.AttributeType;
060 import org.apache.directory.shared.ldap.schema.SchemaManager;
061 import org.slf4j.Logger;
062 import org.slf4j.LoggerFactory;
063
064
065 /**
066 * An {@link Interceptor} that controls access to {@link DefaultPartitionNexus}.
067 * If a user tries to perform any operations that requires
068 * permission he or she doesn't have, {@link NoPermissionException} will be
069 * thrown and therefore the current invocation chain will terminate.
070 *
071 * @org.apache.xbean.XBean
072 *
073 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
074 * @version $Rev: 927146 $, $Date: 2010-03-24 20:39:54 +0200 (Wed, 24 Mar 2010) $
075 */
076 public class DefaultAuthorizationInterceptor extends BaseInterceptor
077 {
078 /** the logger for this class */
079 private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class );
080
081 /**
082 * the base distinguished {@link Name} for all users
083 */
084 private static DN USER_BASE_DN;
085
086 /**
087 * the base distinguished {@link Name} for all groups
088 */
089 private static DN GROUP_BASE_DN;
090
091 /**
092 * the distinguished {@link Name} for the administrator group
093 */
094 private static DN ADMIN_GROUP_DN;
095
096 private Set<String> administrators = new HashSet<String>(2);
097
098 private PartitionNexus nexus;
099
100 /** A starage for the uniqueMember attributeType */
101 private AttributeType uniqueMemberAT;
102
103
104 /**
105 * Creates a new instance.
106 */
107 public DefaultAuthorizationInterceptor()
108 {
109 // Nothing to do
110 }
111
112
113 public void init( DirectoryService directoryService ) throws Exception
114 {
115 nexus = directoryService.getPartitionNexus();
116 SchemaManager schemaManager = directoryService.getSchemaManager();
117
118 USER_BASE_DN = new DN( ServerDNConstants.ADMIN_SYSTEM_DN );
119 USER_BASE_DN.normalize( schemaManager.getNormalizerMapping() );
120
121 GROUP_BASE_DN = new DN( ServerDNConstants.GROUPS_SYSTEM_DN );
122 GROUP_BASE_DN.normalize( schemaManager.getNormalizerMapping() );
123
124 ADMIN_GROUP_DN = new DN( ServerDNConstants.ADMINISTRATORS_GROUP_DN );
125 ADMIN_GROUP_DN.normalize( schemaManager.getNormalizerMapping() );
126
127 uniqueMemberAT = schemaManager.lookupAttributeTypeRegistry( SchemaConstants.UNIQUE_MEMBER_AT_OID );
128
129 loadAdministrators( directoryService );
130 }
131
132
133 private void loadAdministrators( DirectoryService directoryService ) throws Exception
134 {
135 // read in the administrators and cache their normalized names
136 Set<String> newAdministrators = new HashSet<String>( 2 );
137 DN adminDn = new DN( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
138 adminDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
139 CoreSession adminSession = new DefaultCoreSession(
140 new LdapPrincipal( adminDn, AuthenticationLevel.STRONG ), directoryService );
141
142 ServerEntry adminGroup = nexus.lookup( new LookupOperationContext( adminSession, ADMIN_GROUP_DN ) );
143
144 if ( adminGroup == null )
145 {
146 return;
147 }
148
149 EntryAttribute uniqueMember = adminGroup.get( uniqueMemberAT );
150
151 for ( Value<?> value:uniqueMember )
152 {
153 DN memberDn = new DN( value.getString() );
154 memberDn.normalize( directoryService.getSchemaManager().getNormalizerMapping() );
155 newAdministrators.add( memberDn.getNormName() );
156 }
157
158 administrators = newAdministrators;
159 }
160
161
162 // Note:
163 // Lookup, search and list operations need to be handled using a filter
164 // and so we need access to the filter service.
165
166 public void delete( NextInterceptor nextInterceptor, DeleteOperationContext opContext ) throws Exception
167 {
168 DN name = opContext.getDn();
169
170 if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() )
171 {
172 nextInterceptor.delete( opContext );
173 return;
174 }
175
176 DN principalDn = getPrincipal().getClonedName();
177
178 if ( name.isEmpty() )
179 {
180 String msg = I18n.err( I18n.ERR_12 );
181 LOG.error( msg );
182 throw new LdapNoPermissionException( msg );
183 }
184
185 if ( name.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
186 {
187 String msg = I18n.err( I18n.ERR_13 );
188 LOG.error( msg );
189 throw new LdapNoPermissionException( msg );
190 }
191
192 if ( isTheAdministrator( name ) )
193 {
194 String msg = I18n.err( I18n.ERR_14, principalDn.getName() );
195 LOG.error( msg );
196 throw new LdapNoPermissionException( msg );
197 }
198
199 if ( name.size() > 2 )
200 {
201 if ( !isAnAdministrator( principalDn ) )
202 {
203 if ( name.isChildOf( USER_BASE_DN ) )
204 {
205 String msg = I18n.err( I18n.ERR_15, principalDn.getName(), name.getName() );
206 LOG.error( msg );
207 throw new LdapNoPermissionException( msg );
208 }
209
210 if ( name.isChildOf( GROUP_BASE_DN ) )
211 {
212 String msg = I18n.err( I18n.ERR_16, principalDn.getName(), name.getName() );
213 LOG.error( msg );
214 throw new LdapNoPermissionException( msg );
215 }
216 }
217 }
218
219 nextInterceptor.delete( opContext );
220 }
221
222
223 private boolean isTheAdministrator( DN normalizedDn )
224 {
225 return normalizedDn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED );
226 }
227
228
229 private boolean isAnAdministrator( DN normalizedDn )
230 {
231 return isTheAdministrator( normalizedDn ) || administrators.contains( normalizedDn.getNormName() );
232
233 }
234
235
236 // ------------------------------------------------------------------------
237 // Entry Modification Operations
238 // ------------------------------------------------------------------------
239
240 /**
241 * This policy needs to be really tight too because some attributes may take
242 * part in giving the user permissions to protected resources. We do not want
243 * users to self access these resources. As far as we're concerned no one but
244 * the admin needs access.
245 */
246 public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext )
247 throws Exception
248 {
249 if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
250 {
251 DN dn = opContext.getDn();
252
253 protectModifyAlterations( dn );
254 nextInterceptor.modify( opContext );
255
256 // update administrators if we change administrators group
257 if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
258 {
259 loadAdministrators( opContext.getSession().getDirectoryService() );
260 }
261 }
262 else
263 {
264 nextInterceptor.modify( opContext );
265 }
266 }
267
268
269 private void protectModifyAlterations( DN dn ) throws Exception
270 {
271 DN principalDn = getPrincipal().getClonedName();
272
273 if ( dn.isEmpty() )
274 {
275 String msg = I18n.err( I18n.ERR_17 );
276 LOG.error( msg );
277 throw new LdapNoPermissionException( msg );
278 }
279
280 if ( ! isAnAdministrator( principalDn ) )
281 {
282 // allow self modifications
283 if ( dn.getNormName().equals( getPrincipal().getName() ) )
284 {
285 return;
286 }
287
288 if ( dn.getNormName().equals( ServerDNConstants.ADMIN_SYSTEM_DN_NORMALIZED ) )
289 {
290 String msg = I18n.err( I18n.ERR_18, principalDn.getName() );
291 LOG.error( msg );
292 throw new LdapNoPermissionException( msg );
293 }
294
295 if ( dn.size() > 2 )
296 {
297 if ( dn.isChildOf( USER_BASE_DN ) )
298 {
299 String msg = I18n.err( I18n.ERR_19, principalDn.getName(), dn.getName() );
300 LOG.error( msg );
301 throw new LdapNoPermissionException( msg );
302 }
303
304 if ( dn.isChildOf( GROUP_BASE_DN ) )
305 {
306 String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() );
307 LOG.error( msg );
308 throw new LdapNoPermissionException( msg );
309 }
310 }
311 }
312 }
313
314
315 // ------------------------------------------------------------------------
316 // DN altering operations are a no no for any user entry. Basically here
317 // are the rules of conduct to follow:
318 //
319 // o No user should have the ability to move or rename their entry
320 // o Only the administrator can move or rename non-admin user entries
321 // o The administrator entry cannot be moved or renamed by anyone
322 // ------------------------------------------------------------------------
323
324 public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext )
325 throws Exception
326 {
327 if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
328 {
329 protectDnAlterations( opContext.getDn() );
330 }
331
332 nextInterceptor.rename( opContext );
333 }
334
335
336 public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws Exception
337 {
338 if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
339 {
340 protectDnAlterations( opContext.getDn() );
341 }
342
343 nextInterceptor.move( opContext );
344 }
345
346
347 public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) throws Exception
348 {
349 if ( !opContext.getSession().getDirectoryService().isAccessControlEnabled() )
350 {
351 protectDnAlterations( opContext.getDn() );
352 }
353
354 nextInterceptor.moveAndRename( opContext );
355 }
356
357
358 private void protectDnAlterations( DN dn ) throws Exception
359 {
360 DN principalDn = getPrincipal().getClonedName();
361
362 if ( dn.isEmpty() )
363 {
364 String msg = I18n.err( I18n.ERR_234 );
365 LOG.error( msg );
366 throw new LdapNoPermissionException( msg );
367 }
368
369 if ( dn.getNormName().equals( ADMIN_GROUP_DN.getNormName() ) )
370 {
371 String msg = I18n.err( I18n.ERR_21 );
372 LOG.error( msg );
373 throw new LdapNoPermissionException( msg );
374 }
375
376 if ( isTheAdministrator( dn ) )
377 {
378 String msg = I18n.err( I18n.ERR_22, principalDn.getName(), dn.getName() );
379 LOG.error( msg );
380 throw new LdapNoPermissionException( msg );
381 }
382
383 if ( dn.size() > 2 && dn.isChildOf( USER_BASE_DN ) && !isAnAdministrator( principalDn ) )
384 {
385 String msg = I18n.err( I18n.ERR_23, principalDn.getName(), dn.getName() );
386 LOG.error( msg );
387 throw new LdapNoPermissionException( msg );
388 }
389
390 if ( dn.size() > 2 && dn.isChildOf( GROUP_BASE_DN ) && !isAnAdministrator( principalDn ) )
391 {
392 String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() );
393 LOG.error( msg );
394 throw new LdapNoPermissionException( msg );
395 }
396 }
397
398
399 public ClonedServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws Exception
400 {
401 ClonedServerEntry serverEntry = nextInterceptor.lookup( opContext );
402
403 if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() || ( serverEntry == null ) )
404 {
405 return serverEntry;
406 }
407
408 protectLookUp( opContext.getSession().getEffectivePrincipal().getClonedName(), opContext.getDn() );
409 return serverEntry;
410 }
411
412
413 private void protectLookUp( DN principalDn, DN normalizedDn ) throws Exception
414 {
415 if ( !isAnAdministrator( principalDn ) )
416 {
417 if ( normalizedDn.size() > 2 )
418 {
419 if( normalizedDn.isChildOf( USER_BASE_DN ) )
420 {
421 // allow for self reads
422 if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
423 {
424 return;
425 }
426
427 String msg = I18n.err( I18n.ERR_25, normalizedDn.getName(), principalDn.getName() );
428 LOG.error( msg );
429 throw new LdapNoPermissionException( msg );
430 }
431
432 if ( normalizedDn.isChildOf( GROUP_BASE_DN ) )
433 {
434 // allow for self reads
435 if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
436 {
437 return;
438 }
439
440 String msg = I18n.err( I18n.ERR_26, normalizedDn.getName(), principalDn.getName() );
441 LOG.error( msg );
442 throw new LdapNoPermissionException( msg );
443 }
444 }
445
446 if ( isTheAdministrator( normalizedDn ) )
447 {
448 // allow for self reads
449 if ( normalizedDn.getNormName().equals( principalDn.getNormName() ) )
450 {
451 return;
452 }
453
454 String msg = I18n.err( I18n.ERR_27, principalDn.getName() );
455 LOG.error( msg );
456 throw new LdapNoPermissionException( msg );
457 }
458 }
459 }
460
461
462 public EntryFilteringCursor search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws Exception
463 {
464 EntryFilteringCursor cursor = nextInterceptor.search( opContext );
465
466 if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() )
467 {
468 return cursor;
469 }
470
471 cursor.addEntryFilter( new EntryFilter() {
472 public boolean accept( SearchingOperationContext operation, ClonedServerEntry result ) throws Exception
473 {
474 return DefaultAuthorizationInterceptor.this.isSearchable( operation, result );
475 }
476 } );
477 return cursor;
478 }
479
480
481 public EntryFilteringCursor list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws Exception
482 {
483 EntryFilteringCursor cursor = nextInterceptor.list( opContext );
484
485 if ( opContext.getSession().getDirectoryService().isAccessControlEnabled() )
486 {
487 return cursor;
488 }
489
490 cursor.addEntryFilter( new EntryFilter()
491 {
492 public boolean accept( SearchingOperationContext operation, ClonedServerEntry entry ) throws Exception
493 {
494 return DefaultAuthorizationInterceptor.this.isSearchable( operation, entry );
495 }
496 } );
497 return cursor;
498 }
499
500
501 private boolean isSearchable( OperationContext opContext, ClonedServerEntry result ) throws Exception
502 {
503 DN principalDn = opContext.getSession().getEffectivePrincipal().getClonedName();
504 DN dn = result.getDn();
505
506 if ( !dn.isNormalized() )
507 {
508 dn.normalize( opContext.getSession().getDirectoryService().getSchemaManager().getNormalizerMapping() );
509 }
510
511 // Admin users gets full access to all entries
512 if ( isAnAdministrator( principalDn ) )
513 {
514 return true;
515 }
516
517 // Users reading their own entries should be allowed to see all
518 boolean isSelfRead = dn.getNormName().equals( principalDn.getNormName() );
519
520 if ( isSelfRead )
521 {
522 return true;
523 }
524
525 // Block off reads to anything under ou=users and ou=groups if not a self read
526 if ( dn.size() > 2 )
527 {
528 // stuff this if in here instead of up in outer if to prevent
529 // constant needless reexecution for all entries in other depths
530
531 if ( dn.getNormName().endsWith( USER_BASE_DN.getNormName() )
532 || dn.getNormName().endsWith( GROUP_BASE_DN.getNormName() ) )
533 {
534 return false;
535 }
536 }
537
538 // Non-admin users cannot read the admin entry
539 return ! isTheAdministrator( dn );
540
541 }
542 }