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.authn; 021 022 023 import java.util.ArrayList; 024 import java.util.Collection; 025 import java.util.HashMap; 026 import java.util.HashSet; 027 import java.util.Map; 028 import java.util.Set; 029 030 import org.apache.directory.server.core.CoreSession; 031 import org.apache.directory.server.core.DefaultCoreSession; 032 import org.apache.directory.server.core.DirectoryService; 033 import org.apache.directory.server.core.LdapPrincipal; 034 import org.apache.directory.server.core.entry.ClonedServerEntry; 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.AddOperationContext; 040 import org.apache.directory.server.core.interceptor.context.BindOperationContext; 041 import org.apache.directory.server.core.interceptor.context.CompareOperationContext; 042 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext; 043 import org.apache.directory.server.core.interceptor.context.EntryOperationContext; 044 import org.apache.directory.server.core.interceptor.context.GetMatchedNameOperationContext; 045 import org.apache.directory.server.core.interceptor.context.GetRootDSEOperationContext; 046 import org.apache.directory.server.core.interceptor.context.GetSuffixOperationContext; 047 import org.apache.directory.server.core.interceptor.context.ListOperationContext; 048 import org.apache.directory.server.core.interceptor.context.ListSuffixOperationContext; 049 import org.apache.directory.server.core.interceptor.context.LookupOperationContext; 050 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; 051 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; 052 import org.apache.directory.server.core.interceptor.context.MoveOperationContext; 053 import org.apache.directory.server.core.interceptor.context.OperationContext; 054 import org.apache.directory.server.core.interceptor.context.RenameOperationContext; 055 import org.apache.directory.server.core.interceptor.context.SearchOperationContext; 056 import org.apache.directory.server.i18n.I18n; 057 import org.apache.directory.shared.ldap.constants.AuthenticationLevel; 058 import org.apache.directory.shared.ldap.exception.LdapAuthenticationException; 059 import org.apache.directory.shared.ldap.exception.LdapNoPermissionException; 060 import org.apache.directory.shared.ldap.exception.LdapUnwillingToPerformException; 061 import org.apache.directory.shared.ldap.message.ResultCodeEnum; 062 import org.apache.directory.shared.ldap.name.DN; 063 import org.apache.directory.shared.ldap.util.StringTools; 064 import org.slf4j.Logger; 065 import org.slf4j.LoggerFactory; 066 067 068 /** 069 * An {@link Interceptor} that authenticates users. 070 * 071 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 072 * @version $Rev: 923747 $, $Date: 2010-03-16 15:09:38 +0100 (Tue, 16 Mar 2010) $ 073 * @org.apache.xbean.XBean 074 */ 075 public class AuthenticationInterceptor extends BaseInterceptor 076 { 077 private static final Logger LOG = LoggerFactory.getLogger( AuthenticationInterceptor.class ); 078 079 /** 080 * Speedup for logs 081 */ 082 private static final boolean IS_DEBUG = LOG.isDebugEnabled(); 083 084 private Set<Authenticator> authenticators; 085 private final Map<String, Collection<Authenticator>> authenticatorsMapByType = 086 new HashMap<String, Collection<Authenticator>>(); 087 088 private DirectoryService directoryService; 089 090 091 /** 092 * Creates an authentication service interceptor. 093 */ 094 public AuthenticationInterceptor() 095 { 096 } 097 098 099 /** 100 * Registers and initializes all {@link Authenticator}s to this service. 101 */ 102 public void init( DirectoryService directoryService ) throws Exception 103 { 104 this.directoryService = directoryService; 105 106 if ( authenticators == null ) 107 { 108 setDefaultAuthenticators(); 109 } 110 // Register all authenticators 111 for ( Authenticator authenticator : authenticators ) 112 { 113 register( authenticator, directoryService ); 114 } 115 } 116 117 118 private void setDefaultAuthenticators() 119 { 120 Set<Authenticator> set = new HashSet<Authenticator>(); 121 set.add( new AnonymousAuthenticator() ); 122 set.add( new SimpleAuthenticator() ); 123 set.add( new StrongAuthenticator() ); 124 125 setAuthenticators( set ); 126 } 127 128 129 public Set<Authenticator> getAuthenticators() 130 { 131 return authenticators; 132 } 133 134 135 /** 136 * @param authenticators authenticators to be used by this AuthenticationInterceptor 137 * @org.apache.xbean.Property nestedType="org.apache.directory.server.core.authn.Authenticator" 138 */ 139 public void setAuthenticators( Set<Authenticator> authenticators ) 140 { 141 this.authenticators = authenticators; 142 } 143 144 145 /** 146 * Deinitializes and deregisters all {@link Authenticator}s from this service. 147 */ 148 public void destroy() 149 { 150 authenticatorsMapByType.clear(); 151 Set<Authenticator> copy = new HashSet<Authenticator>( authenticators ); 152 authenticators = null; 153 for ( Authenticator authenticator : copy ) 154 { 155 authenticator.destroy(); 156 } 157 } 158 159 160 /** 161 * Initializes the specified {@link Authenticator} and registers it to 162 * this service. 163 * 164 * @param authenticator Authenticator to initialize and register by type 165 * @param directoryService configuration info to supply to the Authenticator during initialization 166 * @throws javax.naming.Exception if initialization fails. 167 */ 168 private void register( Authenticator authenticator, DirectoryService directoryService ) throws Exception 169 { 170 authenticator.init( directoryService ); 171 172 Collection<Authenticator> authenticatorList = getAuthenticators( authenticator.getAuthenticatorType() ); 173 174 if ( authenticatorList == null ) 175 { 176 authenticatorList = new ArrayList<Authenticator>(); 177 authenticatorsMapByType.put( authenticator.getAuthenticatorType(), authenticatorList ); 178 } 179 180 authenticatorList.add( authenticator ); 181 } 182 183 184 /** 185 * Returns the list of {@link Authenticator}s with the specified type. 186 * 187 * @param type type of Authenticator sought 188 * @return A list of Authenticators of the requested type or <tt>null</tt> if no authenticator is found. 189 */ 190 private Collection<Authenticator> getAuthenticators( String type ) 191 { 192 Collection<Authenticator> result = authenticatorsMapByType.get( type ); 193 194 if ( ( result != null ) && ( result.size() > 0 ) ) 195 { 196 return result; 197 } 198 else 199 { 200 return null; 201 } 202 } 203 204 205 public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception 206 { 207 if ( IS_DEBUG ) 208 { 209 LOG.debug( "Operation Context: {}", opContext ); 210 } 211 212 checkAuthenticated( opContext ); 213 next.add( opContext ); 214 } 215 216 217 public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception 218 { 219 if ( IS_DEBUG ) 220 { 221 LOG.debug( "Operation Context: {}", opContext ); 222 } 223 224 checkAuthenticated( opContext ); 225 next.delete( opContext ); 226 invalidateAuthenticatorCaches( opContext.getDn() ); 227 } 228 229 230 public DN getMatchedName( NextInterceptor next, GetMatchedNameOperationContext opContext ) throws Exception 231 { 232 if ( IS_DEBUG ) 233 { 234 LOG.debug( "Operation Context: {}", opContext ); 235 } 236 237 checkAuthenticated( opContext ); 238 return next.getMatchedName( opContext ); 239 } 240 241 242 public ClonedServerEntry getRootDSE( NextInterceptor next, GetRootDSEOperationContext opContext ) throws Exception 243 { 244 if ( IS_DEBUG ) 245 { 246 LOG.debug( "Operation Context: {}", opContext ); 247 } 248 249 checkAuthenticated( opContext ); 250 return next.getRootDSE( opContext ); 251 } 252 253 254 public DN getSuffix( NextInterceptor next, GetSuffixOperationContext opContext ) throws Exception 255 { 256 if ( IS_DEBUG ) 257 { 258 LOG.debug( "Operation Context: {}", opContext ); 259 } 260 261 checkAuthenticated( opContext ); 262 return next.getSuffix( opContext ); 263 } 264 265 266 public boolean hasEntry( NextInterceptor next, EntryOperationContext opContext ) throws Exception 267 { 268 if ( IS_DEBUG ) 269 { 270 LOG.debug( "Operation Context: {}", opContext ); 271 } 272 273 checkAuthenticated( opContext ); 274 return next.hasEntry( opContext ); 275 } 276 277 278 public EntryFilteringCursor list( NextInterceptor next, ListOperationContext opContext ) throws Exception 279 { 280 if ( IS_DEBUG ) 281 { 282 LOG.debug( "Operation Context: {}", opContext ); 283 } 284 285 checkAuthenticated( opContext ); 286 return next.list( opContext ); 287 } 288 289 290 public Set<String> listSuffixes( NextInterceptor next, ListSuffixOperationContext opContext ) throws Exception 291 { 292 if ( IS_DEBUG ) 293 { 294 LOG.debug( "Operation Context: {}", opContext ); 295 } 296 297 checkAuthenticated( opContext ); 298 return next.listSuffixes( opContext ); 299 } 300 301 302 public ClonedServerEntry lookup( NextInterceptor next, LookupOperationContext opContext ) throws Exception 303 { 304 if ( IS_DEBUG ) 305 { 306 LOG.debug( "Operation Context: {}", opContext ); 307 } 308 309 checkAuthenticated( opContext ); 310 return next.lookup( opContext ); 311 } 312 313 314 private void invalidateAuthenticatorCaches( DN principalDn ) 315 { 316 for ( String authMech : authenticatorsMapByType.keySet() ) 317 { 318 Collection<Authenticator> authenticators = getAuthenticators( authMech ); 319 320 // try each authenticator 321 for ( Authenticator authenticator : authenticators ) 322 { 323 authenticator.invalidateCache( principalDn ); 324 } 325 } 326 } 327 328 329 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception 330 { 331 if ( IS_DEBUG ) 332 { 333 LOG.debug( "Operation Context: {}", opContext ); 334 } 335 336 checkAuthenticated( opContext ); 337 next.modify( opContext ); 338 invalidateAuthenticatorCaches( opContext.getDn() ); 339 } 340 341 342 public void rename( NextInterceptor next, RenameOperationContext opContext ) throws Exception 343 { 344 if ( IS_DEBUG ) 345 { 346 LOG.debug( "Operation Context: {}", opContext ); 347 } 348 349 checkAuthenticated( opContext ); 350 next.rename( opContext ); 351 invalidateAuthenticatorCaches( opContext.getDn() ); 352 } 353 354 355 public boolean compare( NextInterceptor next, CompareOperationContext opContext ) throws Exception 356 { 357 if ( IS_DEBUG ) 358 { 359 LOG.debug( "Operation Context: {}", opContext ); 360 } 361 362 checkAuthenticated( opContext ); 363 boolean result = next.compare( opContext ); 364 invalidateAuthenticatorCaches( opContext.getDn() ); 365 return result; 366 } 367 368 369 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opContext ) 370 throws Exception 371 { 372 if ( IS_DEBUG ) 373 { 374 LOG.debug( "Operation Context: {}", opContext ); 375 } 376 377 checkAuthenticated( opContext ); 378 next.moveAndRename( opContext ); 379 invalidateAuthenticatorCaches( opContext.getDn() ); 380 } 381 382 383 public void move( NextInterceptor next, MoveOperationContext opContext ) throws Exception 384 { 385 if ( IS_DEBUG ) 386 { 387 LOG.debug( "Operation Context: {}", opContext ); 388 } 389 390 checkAuthenticated( opContext ); 391 next.move( opContext ); 392 invalidateAuthenticatorCaches( opContext.getDn() ); 393 } 394 395 396 public EntryFilteringCursor search( NextInterceptor next, SearchOperationContext opContext ) throws Exception 397 { 398 if ( IS_DEBUG ) 399 { 400 LOG.debug( "Operation Context: {}", opContext ); 401 } 402 403 checkAuthenticated( opContext ); 404 return next.search( opContext ); 405 } 406 407 408 /** 409 * Check if the current operation has a valid PrincipalDN or not. 410 * 411 * @param opContext the OperationContext for this operation 412 * @param operation the operation type 413 * @throws Exception 414 */ 415 private void checkAuthenticated( OperationContext operation ) throws Exception 416 { 417 if ( operation.getSession().isAnonymous() && !directoryService.isAllowAnonymousAccess() 418 && !operation.getDn().isEmpty() ) 419 { 420 LOG.error( I18n.err( I18n.ERR_5, operation.getName() ) ); 421 throw new LdapNoPermissionException( I18n.err( I18n.ERR_5, operation.getName() ) ); 422 } 423 } 424 425 426 public void bind( NextInterceptor next, BindOperationContext opContext ) throws Exception 427 { 428 if ( IS_DEBUG ) 429 { 430 LOG.debug( "Operation Context: {}", opContext ); 431 } 432 433 if ( ( opContext.getSession() != null ) && ( opContext.getSession().getEffectivePrincipal() != null ) ) 434 { 435 // null out the credentials 436 opContext.setCredentials( null ); 437 } 438 439 // pick the first matching authenticator type 440 AuthenticationLevel level = opContext.getAuthenticationLevel(); 441 442 if ( level == AuthenticationLevel.UNAUTHENT ) 443 { 444 // This is a case where the Bind request contains a DN, but no password. 445 // We don't check the DN, we just return a UnwillingToPerform error 446 // Cf RFC 4513, chap. 5.1.2 447 throw new LdapUnwillingToPerformException( ResultCodeEnum.UNWILLING_TO_PERFORM, "Cannot Bind for DN " + opContext.getDn().getName() ); 448 } 449 450 Collection<Authenticator> authenticators = getAuthenticators( level.getName() ); 451 452 if ( authenticators == null ) 453 { 454 LOG.debug( "No authenticators found, delegating bind to the nexus." ); 455 456 // as a last resort try binding via the nexus 457 next.bind( opContext ); 458 459 LOG.debug( "Nexus succeeded on bind operation." ); 460 461 // bind succeeded if we got this far 462 // TODO - authentication level not being set 463 LdapPrincipal principal = new LdapPrincipal( opContext.getDn(), AuthenticationLevel.SIMPLE ); 464 CoreSession session = new DefaultCoreSession( principal, directoryService ); 465 opContext.setSession( session ); 466 467 // remove creds so there is no security risk 468 opContext.setCredentials( null ); 469 return; 470 } 471 472 // TODO : we should refactor that. 473 // try each authenticator 474 for ( Authenticator authenticator : authenticators ) 475 { 476 try 477 { 478 // perform the authentication 479 LdapPrincipal principal = authenticator.authenticate( opContext ); 480 481 LdapPrincipal clonedPrincipal = (LdapPrincipal)(principal.clone()); 482 483 // remove creds so there is no security risk 484 opContext.setCredentials( null ); 485 clonedPrincipal.setUserPassword( StringTools.EMPTY_BYTES ); 486 487 // authentication was successful 488 CoreSession session = new DefaultCoreSession( clonedPrincipal, directoryService ); 489 opContext.setSession( session ); 490 491 return; 492 } 493 catch ( LdapAuthenticationException e ) 494 { 495 // authentication failed, try the next authenticator 496 if ( LOG.isInfoEnabled() ) 497 { 498 LOG.info( "Authenticator {} failed to authenticate: {}", authenticator, opContext ); 499 } 500 } 501 catch ( Exception e ) 502 { 503 // Log other exceptions than LdapAuthenticationException 504 if ( LOG.isWarnEnabled() ) 505 { 506 LOG.info( "Unexpected failure for Authenticator {} : {}", authenticator, opContext ); 507 } 508 } 509 } 510 511 if ( LOG.isInfoEnabled() ) 512 { 513 LOG.info( "Cannot bind to the server " ); 514 } 515 516 DN dn = opContext.getDn(); 517 String upDn = ( dn == null ? "" : dn.getName() ); 518 throw new LdapAuthenticationException( I18n.err( I18n.ERR_229, upDn ) ); 519 } 520 }