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 */ 020package org.apache.directory.server.core.authz; 021 022 023import java.io.IOException; 024import java.util.HashSet; 025import java.util.Set; 026 027import javax.naming.NoPermissionException; 028 029import org.apache.directory.api.ldap.model.entry.Attribute; 030import org.apache.directory.api.ldap.model.entry.Entry; 031import org.apache.directory.api.ldap.model.entry.Value; 032import org.apache.directory.api.ldap.model.exception.LdapException; 033import org.apache.directory.api.ldap.model.exception.LdapNoPermissionException; 034import org.apache.directory.api.ldap.model.exception.LdapOtherException; 035import org.apache.directory.api.ldap.model.name.Dn; 036import org.apache.directory.server.constants.ServerDNConstants; 037import org.apache.directory.server.core.api.CoreSession; 038import org.apache.directory.server.core.api.DirectoryService; 039import org.apache.directory.server.core.api.InterceptorEnum; 040import org.apache.directory.server.core.api.filtering.EntryFilter; 041import org.apache.directory.server.core.api.filtering.EntryFilteringCursor; 042import org.apache.directory.server.core.api.interceptor.BaseInterceptor; 043import org.apache.directory.server.core.api.interceptor.Interceptor; 044import org.apache.directory.server.core.api.interceptor.context.DeleteOperationContext; 045import org.apache.directory.server.core.api.interceptor.context.LookupOperationContext; 046import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 047import org.apache.directory.server.core.api.interceptor.context.MoveAndRenameOperationContext; 048import org.apache.directory.server.core.api.interceptor.context.MoveOperationContext; 049import org.apache.directory.server.core.api.interceptor.context.OperationContext; 050import org.apache.directory.server.core.api.interceptor.context.RenameOperationContext; 051import org.apache.directory.server.core.api.interceptor.context.SearchOperationContext; 052import org.apache.directory.server.core.api.partition.Partition; 053import org.apache.directory.server.core.api.partition.PartitionNexus; 054import org.apache.directory.server.core.api.partition.PartitionTxn; 055import org.apache.directory.server.core.shared.partition.DefaultPartitionNexus; 056import org.apache.directory.server.i18n.I18n; 057import org.slf4j.Logger; 058import org.slf4j.LoggerFactory; 059 060 061/** 062 * An {@link Interceptor} that controls access to {@link DefaultPartitionNexus}. 063 * If a user tries to perform any operations that requires 064 * permission he or she doesn't have, {@link NoPermissionException} will be 065 * thrown and therefore the current invocation chain will terminate. 066 * 067 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 068 */ 069public class DefaultAuthorizationInterceptor extends BaseInterceptor 070{ 071 /** the logger for this class */ 072 private static final Logger LOG = LoggerFactory.getLogger( DefaultAuthorizationInterceptor.class ); 073 074 /** the base distinguished {@link Name} for the admin system */ 075 private Dn adminSystemDn; 076 077 /** the base distinguished {@link Name} for all groups */ 078 private Dn groupsBaseDn; 079 080 /** the base distinguished {@link Name} for all users */ 081 private Dn usersBaseDn; 082 083 /** the distinguished {@link Name} for the administrator group */ 084 private Dn adminGroupDn; 085 086 private Set<String> administrators = new HashSet<>( 2 ); 087 088 private PartitionNexus nexus; 089 090 /** 091 * the search result filter to use for collective attribute injection 092 */ 093 private class DefaultAuthorizationSearchFilter implements EntryFilter 094 { 095 /** 096 * {@inheritDoc} 097 */ 098 public boolean accept( SearchOperationContext operation, Entry entry ) throws LdapException 099 { 100 return DefaultAuthorizationInterceptor.this.isSearchable( operation, entry ); 101 } 102 103 104 /** 105 * {@inheritDoc} 106 */ 107 public String toString( String tabs ) 108 { 109 return tabs + "DefaultAuthorizationSearchFilter"; 110 } 111 } 112 113 114 /** 115 * Creates a new instance of DefaultAuthorizationInterceptor. 116 */ 117 public DefaultAuthorizationInterceptor() 118 { 119 super( InterceptorEnum.DEFAULT_AUTHORIZATION_INTERCEPTOR ); 120 } 121 122 123 /** 124 * {@inheritDoc} 125 */ 126 @Override 127 public void init( DirectoryService directoryService ) throws LdapException 128 { 129 super.init( directoryService ); 130 131 nexus = directoryService.getPartitionNexus(); 132 133 adminSystemDn = dnFactory.create( ServerDNConstants.ADMIN_SYSTEM_DN ); 134 135 groupsBaseDn = dnFactory.create( ServerDNConstants.GROUPS_SYSTEM_DN ); 136 137 usersBaseDn = dnFactory.create( ServerDNConstants.USERS_SYSTEM_DN ); 138 139 adminGroupDn = dnFactory.create( ServerDNConstants.ADMINISTRATORS_GROUP_DN ); 140 141 loadAdministrators( directoryService ); 142 } 143 144 145 private void loadAdministrators( DirectoryService directoryService ) throws LdapException 146 { 147 // read in the administrators and cache their normalized names 148 Set<String> newAdministrators = new HashSet<>( 2 ); 149 CoreSession adminSession = directoryService.getAdminSession(); 150 Partition partition = nexus.getPartition( adminGroupDn ); 151 Entry adminGroup; 152 153 LookupOperationContext lookupContext = new LookupOperationContext( adminSession, adminGroupDn ); 154 lookupContext.setPartition( partition ); 155 156 try ( PartitionTxn partitionTxn = partition.beginReadTransaction() ) 157 { 158 lookupContext.setTransaction( partitionTxn ); 159 adminGroup = nexus.lookup( lookupContext ); 160 } 161 catch ( IOException ioe ) 162 { 163 throw new LdapOtherException( ioe.getMessage(), ioe ); 164 } 165 166 if ( adminGroup == null ) 167 { 168 return; 169 } 170 171 Attribute uniqueMember = adminGroup.get( directoryService.getAtProvider().getUniqueMember() ); 172 173 for ( Value value : uniqueMember ) 174 { 175 Dn memberDn = dnFactory.create( value.getString() ); 176 newAdministrators.add( memberDn.getNormName() ); 177 } 178 179 administrators = newAdministrators; 180 } 181 182 183 // Note: 184 // Lookup, search and list operations need to be handled using a filter 185 // and so we need access to the filter service. 186 /** 187 * {@inheritDoc} 188 */ 189 @Override 190 public void delete( DeleteOperationContext deleteContext ) throws LdapException 191 { 192 if ( deleteContext.getSession().getDirectoryService().isAccessControlEnabled() ) 193 { 194 next( deleteContext ); 195 return; 196 } 197 198 Dn dn = deleteContext.getDn(); 199 200 if ( dn.isEmpty() ) 201 { 202 String msg = I18n.err( I18n.ERR_12 ); 203 LOG.error( msg ); 204 throw new LdapNoPermissionException( msg ); 205 } 206 207 if ( dn.equals( adminGroupDn ) ) 208 { 209 String msg = I18n.err( I18n.ERR_13 ); 210 LOG.error( msg ); 211 throw new LdapNoPermissionException( msg ); 212 } 213 214 Dn principalDn = getPrincipal( deleteContext ).getDn(); 215 216 if ( dn.equals( adminSystemDn ) ) 217 { 218 String msg = I18n.err( I18n.ERR_14, principalDn.getName() ); 219 LOG.error( msg ); 220 throw new LdapNoPermissionException( msg ); 221 } 222 223 if ( dn.size() > 2 && !isAnAdministrator( principalDn ) ) 224 { 225 if ( dn.isDescendantOf( adminSystemDn ) ) 226 { 227 String msg = I18n.err( I18n.ERR_15, principalDn.getName(), dn.getName() ); 228 LOG.error( msg ); 229 throw new LdapNoPermissionException( msg ); 230 } 231 232 if ( dn.isDescendantOf( groupsBaseDn ) ) 233 { 234 String msg = I18n.err( I18n.ERR_16, principalDn.getName(), dn.getName() ); 235 LOG.error( msg ); 236 throw new LdapNoPermissionException( msg ); 237 } 238 239 if ( dn.isDescendantOf( usersBaseDn ) ) 240 { 241 String msg = I18n.err( I18n.ERR_16, principalDn.getName(), dn.getName() ); 242 LOG.error( msg ); 243 throw new LdapNoPermissionException( msg ); 244 } 245 } 246 247 next( deleteContext ); 248 } 249 250 251 /** 252 * {@inheritDoc} 253 */ 254 @Override 255 public Entry lookup( LookupOperationContext lookupContext ) throws LdapException 256 { 257 CoreSession session = lookupContext.getSession(); 258 Entry entry = next( lookupContext ); 259 260 if ( session.getDirectoryService().isAccessControlEnabled() ) 261 { 262 return entry; 263 } 264 265 protectLookUp( session.getEffectivePrincipal().getDn(), lookupContext.getDn() ); 266 267 return entry; 268 } 269 270 271 // ------------------------------------------------------------------------ 272 // Entry Modification Operations 273 // ------------------------------------------------------------------------ 274 /** 275 * This policy needs to be really tight too because some attributes may take 276 * part in giving the user permissions to protected resources. We do not want 277 * users to self access these resources. As far as we're concerned no one but 278 * the admin needs access. 279 */ 280 /** 281 * {@inheritDoc} 282 */ 283 @Override 284 public void modify( ModifyOperationContext modifyContext ) throws LdapException 285 { 286 if ( !modifyContext.getSession().getDirectoryService().isAccessControlEnabled() ) 287 { 288 Dn dn = modifyContext.getDn(); 289 290 protectModifyAlterations( modifyContext, dn ); 291 next( modifyContext ); 292 293 // update administrators if we change administrators group 294 if ( dn.getNormName().equals( adminGroupDn.getNormName() ) ) 295 { 296 loadAdministrators( modifyContext.getSession().getDirectoryService() ); 297 } 298 } 299 else 300 { 301 next( modifyContext ); 302 } 303 } 304 305 306 /** 307 * {@inheritDoc} 308 */ 309 @Override 310 public void move( MoveOperationContext moveContext ) throws LdapException 311 { 312 if ( !moveContext.getSession().getDirectoryService().isAccessControlEnabled() ) 313 { 314 protectDnAlterations( moveContext, moveContext.getDn() ); 315 } 316 317 next( moveContext ); 318 } 319 320 321 /** 322 * {@inheritDoc} 323 */ 324 @Override 325 public void moveAndRename( MoveAndRenameOperationContext moveAndRenameContext ) throws LdapException 326 { 327 if ( !moveAndRenameContext.getSession().getDirectoryService().isAccessControlEnabled() ) 328 { 329 protectDnAlterations( moveAndRenameContext, moveAndRenameContext.getDn() ); 330 } 331 332 next( moveAndRenameContext ); 333 } 334 335 336 // ------------------------------------------------------------------------ 337 // Dn altering operations are a no no for any user entry. Basically here 338 // are the rules of conduct to follow: 339 // 340 // o No user should have the ability to move or rename their entry 341 // o Only the administrator can move or rename non-admin user entries 342 // o The administrator entry cannot be moved or renamed by anyone 343 // ------------------------------------------------------------------------ 344 /** 345 * {@inheritDoc} 346 */ 347 @Override 348 public void rename( RenameOperationContext renameContext ) throws LdapException 349 { 350 if ( !renameContext.getSession().getDirectoryService().isAccessControlEnabled() ) 351 { 352 protectDnAlterations( renameContext, renameContext.getDn() ); 353 } 354 355 next( renameContext ); 356 } 357 358 359 /** 360 * {@inheritDoc} 361 */ 362 @Override 363 public EntryFilteringCursor search( SearchOperationContext searchContext ) throws LdapException 364 { 365 EntryFilteringCursor cursor = next( searchContext ); 366 367 if ( searchContext.getSession().getDirectoryService().isAccessControlEnabled() ) 368 { 369 return cursor; 370 } 371 372 cursor.addEntryFilter( new DefaultAuthorizationSearchFilter() ); 373 374 return cursor; 375 } 376 377 378 private boolean isTheAdministrator( Dn dn ) 379 { 380 return dn.getNormName().equals( adminSystemDn.getNormName() ); 381 } 382 383 384 private boolean isAnAdministrator( Dn dn ) 385 { 386 return isTheAdministrator( dn ) || administrators.contains( dn.getNormName() ); 387 } 388 389 390 private void protectModifyAlterations( OperationContext opCtx, Dn dn ) throws LdapException 391 { 392 Dn principalDn = getPrincipal( opCtx ).getDn(); 393 394 if ( dn.isEmpty() ) 395 { 396 String msg = I18n.err( I18n.ERR_17 ); 397 LOG.error( msg ); 398 throw new LdapNoPermissionException( msg ); 399 } 400 401 if ( !isAnAdministrator( principalDn ) ) 402 { 403 // allow self modifications 404 if ( dn.equals( getPrincipal( opCtx ).getDn() ) ) 405 { 406 return; 407 } 408 409 if ( dn.equals( adminSystemDn ) ) 410 { 411 String msg = I18n.err( I18n.ERR_18, principalDn.getName() ); 412 LOG.error( msg ); 413 throw new LdapNoPermissionException( msg ); 414 } 415 416 if ( dn.size() > 2 ) 417 { 418 if ( dn.isDescendantOf( adminSystemDn ) ) 419 { 420 String msg = I18n.err( I18n.ERR_19, principalDn.getName(), dn.getName() ); 421 LOG.error( msg ); 422 throw new LdapNoPermissionException( msg ); 423 } 424 425 if ( dn.isDescendantOf( groupsBaseDn ) ) 426 { 427 String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() ); 428 LOG.error( msg ); 429 throw new LdapNoPermissionException( msg ); 430 } 431 432 if ( dn.isDescendantOf( usersBaseDn ) ) 433 { 434 String msg = I18n.err( I18n.ERR_20, principalDn.getName(), dn.getName() ); 435 LOG.error( msg ); 436 throw new LdapNoPermissionException( msg ); 437 } 438 } 439 } 440 } 441 442 443 private void protectDnAlterations( OperationContext opCtx, Dn dn ) throws LdapException 444 { 445 Dn principalDn = getPrincipal( opCtx ).getDn(); 446 447 if ( dn.isEmpty() ) 448 { 449 String msg = I18n.err( I18n.ERR_234 ); 450 LOG.error( msg ); 451 throw new LdapNoPermissionException( msg ); 452 } 453 454 if ( dn.equals( adminGroupDn ) ) 455 { 456 String msg = I18n.err( I18n.ERR_21 ); 457 LOG.error( msg ); 458 throw new LdapNoPermissionException( msg ); 459 } 460 461 if ( isTheAdministrator( dn ) ) 462 { 463 String msg = I18n.err( I18n.ERR_22, principalDn.getName(), dn.getName() ); 464 LOG.error( msg ); 465 throw new LdapNoPermissionException( msg ); 466 } 467 468 if ( ( dn.size() > 2 ) && !isAnAdministrator( principalDn ) ) 469 { 470 if ( dn.isDescendantOf( adminSystemDn ) ) 471 { 472 String msg = I18n.err( I18n.ERR_23, principalDn.getName(), dn.getName() ); 473 LOG.error( msg ); 474 throw new LdapNoPermissionException( msg ); 475 } 476 477 if ( dn.isDescendantOf( groupsBaseDn ) ) 478 { 479 String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() ); 480 LOG.error( msg ); 481 throw new LdapNoPermissionException( msg ); 482 } 483 484 if ( dn.isDescendantOf( usersBaseDn ) ) 485 { 486 String msg = I18n.err( I18n.ERR_24, principalDn.getName(), dn.getName() ); 487 LOG.error( msg ); 488 throw new LdapNoPermissionException( msg ); 489 } 490 } 491 } 492 493 494 private void protectLookUp( Dn principalDn, Dn normalizedDn ) throws LdapException 495 { 496 if ( !isAnAdministrator( principalDn ) ) 497 { 498 if ( normalizedDn.size() > 2 ) 499 { 500 if ( normalizedDn.isDescendantOf( adminSystemDn ) ) 501 { 502 // allow for self reads 503 if ( normalizedDn.equals( principalDn ) ) 504 { 505 return; 506 } 507 508 String msg = I18n.err( I18n.ERR_25, normalizedDn.getName(), principalDn.getName() ); 509 LOG.error( msg ); 510 throw new LdapNoPermissionException( msg ); 511 } 512 513 if ( normalizedDn.isDescendantOf( groupsBaseDn ) || normalizedDn.isDescendantOf( usersBaseDn ) ) 514 { 515 // allow for self reads 516 if ( normalizedDn.equals( principalDn ) ) 517 { 518 return; 519 } 520 521 String msg = I18n.err( I18n.ERR_26, normalizedDn.getName(), principalDn.getName() ); 522 LOG.error( msg ); 523 throw new LdapNoPermissionException( msg ); 524 } 525 } 526 527 if ( isTheAdministrator( normalizedDn ) ) 528 { 529 // allow for self reads 530 if ( normalizedDn.equals( principalDn ) ) 531 { 532 return; 533 } 534 535 String msg = I18n.err( I18n.ERR_27, principalDn.getName() ); 536 LOG.error( msg ); 537 throw new LdapNoPermissionException( msg ); 538 } 539 } 540 } 541 542 543 // False positive, we want to keep the comment 544 private boolean isSearchable( OperationContext opContext, Entry entry ) throws LdapException 545 { 546 Dn principalDn = opContext.getSession().getEffectivePrincipal().getDn(); 547 Dn dn = entry.getDn(); 548 549 if ( !dn.isSchemaAware() ) 550 { 551 dn = new Dn( schemaManager, dn ); 552 } 553 554 // Admin users gets full access to all entries 555 if ( isAnAdministrator( principalDn ) ) 556 { 557 return true; 558 } 559 560 // Users reading their own entries should be allowed to see all 561 boolean isSelfRead = dn.equals( principalDn ); 562 563 if ( isSelfRead ) 564 { 565 return true; 566 } 567 568 // Block off reads to anything under ou=users and ou=groups if not a self read 569 if ( dn.size() >= 2 ) 570 { 571 // stuff this if in here instead of up in outer if to prevent 572 // constant needless reexecution for all entries in other depths 573 574 if ( dn.isDescendantOf( adminSystemDn ) || dn.isDescendantOf( groupsBaseDn ) 575 || dn.isDescendantOf( usersBaseDn ) ) 576 { 577 return false; 578 } 579 } 580 581 // Non-admin users cannot read the admin entry 582 return !isTheAdministrator( dn ); 583 } 584}