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 19:39:54 +0100 (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 }