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.authn; 021 022 023import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.ACCOUNT_LOCKED; 024import static org.apache.directory.api.ldap.extras.controls.ppolicy.PasswordPolicyErrorEnum.PASSWORD_EXPIRED; 025import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_ACCOUNT_LOCKED_TIME_AT; 026import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_CHANGED_TIME_AT; 027import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_END_TIME_AT; 028import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_GRACE_USE_TIME_AT; 029import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_LAST_SUCCESS_AT; 030import static org.apache.directory.api.ldap.model.constants.PasswordPolicySchemaConstants.PWD_START_TIME_AT; 031 032import java.io.IOException; 033import java.util.Collections; 034import java.util.Date; 035 036import org.apache.directory.api.ldap.model.constants.AuthenticationLevel; 037import org.apache.directory.api.ldap.model.entry.Attribute; 038import org.apache.directory.api.ldap.model.entry.DefaultModification; 039import org.apache.directory.api.ldap.model.entry.Entry; 040import org.apache.directory.api.ldap.model.entry.Modification; 041import org.apache.directory.api.ldap.model.entry.ModificationOperation; 042import org.apache.directory.api.ldap.model.exception.LdapException; 043import org.apache.directory.api.ldap.model.exception.LdapOtherException; 044import org.apache.directory.api.ldap.model.name.Dn; 045import org.apache.directory.api.ldap.model.password.PasswordUtil; 046import org.apache.directory.api.util.DateUtils; 047import org.apache.directory.server.core.api.DirectoryService; 048import org.apache.directory.server.core.api.InterceptorEnum; 049import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyConfiguration; 050import org.apache.directory.server.core.api.authn.ppolicy.PasswordPolicyException; 051import org.apache.directory.server.core.api.interceptor.context.ModifyOperationContext; 052import org.apache.directory.server.core.api.partition.Partition; 053import org.apache.directory.server.core.api.partition.PartitionTxn; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057 058/** 059 * Base class for all Authenticators. 060 * 061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 062 */ 063public abstract class AbstractAuthenticator implements Authenticator 064{ 065 /** A logger for the extending classes */ 066 protected static final Logger LOG = LoggerFactory.getLogger( AbstractAuthenticator.class ); 067 068 /** The associated DirectoryService */ 069 private DirectoryService directoryService; 070 071 /** authenticator type */ 072 private final AuthenticationLevel authenticatorType; 073 074 /** The base DN which will be the starting point from which we use the authenticator */ 075 private Dn baseDn; 076 077 078 /** 079 * Creates a new instance. 080 * 081 * @param type the type of this authenticator (e.g. <tt>'simple'</tt>, <tt>'none'</tt>...) 082 */ 083 protected AbstractAuthenticator( AuthenticationLevel type ) 084 { 085 this.authenticatorType = type; 086 this.baseDn = Dn.ROOT_DSE; 087 } 088 089 090 /** 091 * Creates a new instance. 092 * 093 * @param type the type of this authenticator (e.g. <tt>'simple'</tt>, <tt>'none'</tt>...) 094 * @param baseDn The base DN for this authenticator 095 */ 096 protected AbstractAuthenticator( AuthenticationLevel type, Dn baseDn ) 097 { 098 this.authenticatorType = type; 099 this.baseDn = baseDn; 100 } 101 102 103 /** 104 * Returns {@link DirectoryService} for this authenticator. 105 * @return the directory service core 106 */ 107 public DirectoryService getDirectoryService() 108 { 109 return directoryService; 110 } 111 112 113 /** 114 * {@inheritDoc} 115 */ 116 @Override 117 public AuthenticationLevel getAuthenticatorType() 118 { 119 return authenticatorType; 120 } 121 122 123 /** 124 * Initializes (<tt>directoryService</tt> and and calls {@link #doInit()} method. 125 * Please put your initialization code into {@link #doInit()}. 126 * @param directoryService the directory core for this authenticator 127 * @throws LdapException if there is a problem starting up the authenticator 128 */ 129 @Override 130 public final void init( DirectoryService directoryService ) throws LdapException 131 { 132 this.directoryService = directoryService; 133 doInit(); 134 } 135 136 137 /** 138 * Implement your initialization code here. 139 */ 140 protected void doInit() 141 { 142 } 143 144 145 /** 146 * Calls {@link #doDestroy()} method, and clears default properties 147 * (<tt>factoryConfiguration</tt> and <tt>configuration</tt>). 148 * Please put your deinitialization code into {@link #doDestroy()}. 149 */ 150 @Override 151 public final void destroy() 152 { 153 try 154 { 155 doDestroy(); 156 } 157 finally 158 { 159 this.directoryService = null; 160 } 161 } 162 163 164 /** 165 * Implement your deinitialization code here. 166 */ 167 protected void doDestroy() 168 { 169 } 170 171 172 /** 173 * Does nothing leaving it so subclasses can override. 174 */ 175 @Override 176 public void invalidateCache( Dn bindDn ) 177 { 178 } 179 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override 185 public boolean isValid( Dn bindDn ) 186 { 187 // The authenticator is valid if the baseDn is null or if it's a parent of the bindDn 188 return ( baseDn == null ) || ( baseDn.isAncestorOf( bindDn ) ); 189 } 190 191 192 /** 193 * {@inheritDoc} 194 */ 195 @Override 196 public Dn getBaseDn() 197 { 198 return baseDn; 199 } 200 201 202 /** 203 * {@inheritDoc} 204 */ 205 @Override 206 public void setBaseDn( Dn baseDn ) 207 { 208 this.baseDn = baseDn; 209 } 210 211 212 private void internalModify( ModifyOperationContext modContext ) throws LdapException 213 { 214 Partition partition = directoryService.getPartitionNexus().getPartition( modContext.getDn() ); 215 modContext.setPartition( partition ); 216 PartitionTxn partitionTxn = null; 217 218 try 219 { 220 partitionTxn = partition.beginWriteTransaction(); 221 modContext.setTransaction( partitionTxn ); 222 223 directoryService.getPartitionNexus().modify( modContext ); 224 225 partitionTxn.commit(); 226 } 227 catch ( LdapException le ) 228 { 229 try 230 { 231 if ( partitionTxn != null ) 232 { 233 partitionTxn.abort(); 234 } 235 236 throw le; 237 } 238 catch ( IOException ioe ) 239 { 240 throw new LdapOtherException( ioe.getMessage(), ioe ); 241 } 242 } 243 catch ( IOException ioe ) 244 { 245 try 246 { 247 partitionTxn.abort(); 248 249 throw new LdapOtherException( ioe.getMessage(), ioe ); 250 } 251 catch ( IOException ioe2 ) 252 { 253 throw new LdapOtherException( ioe2.getMessage(), ioe2 ); 254 } 255 } 256 } 257 258 259 /** 260 * {@inheritDoc} 261 */ 262 @Override 263 public void checkPwdPolicy( Entry userEntry ) throws LdapException 264 { 265 if ( !directoryService.isPwdPolicyEnabled() ) 266 { 267 return; 268 } 269 270 AuthenticationInterceptor authenticationInterceptor = ( AuthenticationInterceptor ) directoryService 271 .getInterceptor( 272 InterceptorEnum.AUTHENTICATION_INTERCEPTOR.getName() ); 273 PasswordPolicyConfiguration pPolicyConfig = authenticationInterceptor.getPwdPolicy( userEntry ); 274 275 // check for locked out account 276 if ( pPolicyConfig.isPwdLockout() ) 277 { 278 LOG.debug( "checking if account with the Dn {} is locked", userEntry.getDn() ); 279 280 Attribute accountLockAttr = userEntry.get( PWD_ACCOUNT_LOCKED_TIME_AT ); 281 282 if ( accountLockAttr != null ) 283 { 284 String lockedTime = accountLockAttr.getString(); 285 286 if ( "000001010000Z".equals( lockedTime ) ) 287 { 288 throw new PasswordPolicyException( "account was permanently locked", ACCOUNT_LOCKED.getValue() ); 289 } 290 else 291 { 292 Date lockedDate = DateUtils.getDate( lockedTime ); 293 long unlockTime = pPolicyConfig.getPwdLockoutDuration() * 1000L; 294 unlockTime += lockedDate.getTime(); 295 296 Date unlockDate = new Date( unlockTime ); 297 Date now = new Date( directoryService.getTimeProvider().currentIimeMillis() ); 298 299 if ( unlockDate.after( now ) ) 300 { 301 throw new PasswordPolicyException( "account will remain locked till " + unlockDate, 302 ACCOUNT_LOCKED.getValue() ); 303 } 304 else 305 { 306 // remove pwdAccountLockedTime attribute 307 Modification pwdAccountLockMod = new DefaultModification( 308 ModificationOperation.REMOVE_ATTRIBUTE, accountLockAttr ); 309 ModifyOperationContext modContext = new ModifyOperationContext( 310 directoryService.getAdminSession() ); 311 modContext.setDn( userEntry.getDn() ); 312 modContext.setModItems( Collections.singletonList( pwdAccountLockMod ) ); 313 314 internalModify( modContext ); 315 } 316 } 317 } 318 } 319 320 Attribute pwdStartTimeAttr = userEntry.get( PWD_START_TIME_AT ); 321 322 if ( pwdStartTimeAttr != null ) 323 { 324 Date pwdStartTime = DateUtils.getDate( pwdStartTimeAttr.getString() ); 325 326 if ( System.currentTimeMillis() < pwdStartTime.getTime() ) 327 { 328 throw new PasswordPolicyException( "account is locked, will be activated after " + pwdStartTime, 329 ACCOUNT_LOCKED.getValue() ); 330 } 331 } 332 333 Attribute pwdEndTimeAttr = userEntry.get( PWD_END_TIME_AT ); 334 335 if ( pwdEndTimeAttr != null ) 336 { 337 Date pwdEndTime = DateUtils.getDate( pwdEndTimeAttr.getString() ); 338 339 if ( System.currentTimeMillis() >= pwdEndTime.getTime() ) 340 { 341 throw new PasswordPolicyException( 342 "password end time reached, will be locked till administrator activates it", 343 ACCOUNT_LOCKED.getValue() ); 344 } 345 } 346 347 if ( pPolicyConfig.getPwdMaxIdle() > 0 ) 348 { 349 Attribute pwdLastSuccessTimeAttr = userEntry.get( PWD_LAST_SUCCESS_AT ); 350 351 // Let's be sure that the user has already logged in 352 if ( pwdLastSuccessTimeAttr != null ) 353 { 354 long time = pPolicyConfig.getPwdMaxIdle() * 1000L; 355 time += DateUtils.getDate( pwdLastSuccessTimeAttr.getString() ).getTime(); 356 357 if ( directoryService.getTimeProvider().currentIimeMillis() >= time ) 358 { 359 throw new PasswordPolicyException( 360 "account locked due to the max idle time of the password was exceeded", 361 ACCOUNT_LOCKED.getValue() ); 362 } 363 } 364 } 365 366 // Check that the password is not too old and need to be disabled 367 if ( pPolicyConfig.getPwdMaxAge() > 0 ) 368 { 369 // In case we have a grace number of attempts 370 if ( pPolicyConfig.getPwdGraceAuthNLimit() > 0 ) 371 { 372 Attribute pwdGraceUseAttr = userEntry.get( PWD_GRACE_USE_TIME_AT ); 373 374 // check for grace authentication count 375 if ( ( pwdGraceUseAttr != null ) && ( pwdGraceUseAttr.size() >= pPolicyConfig.getPwdGraceAuthNLimit() ) ) 376 { 377 throw new PasswordPolicyException( "password expired and max grace logins were used", 378 PASSWORD_EXPIRED.getValue() ); 379 } 380 } 381 else 382 { 383 // No grace attempt : check if the password has expired or not 384 Attribute pwdChangeTimeAttr = userEntry.get( PWD_CHANGED_TIME_AT ); 385 386 // If the attr is null, this is the admin user. We don't block it 387 if ( pwdChangeTimeAttr != null ) 388 { 389 boolean expired = PasswordUtil.isPwdExpired( pwdChangeTimeAttr.getString(), 390 pPolicyConfig.getPwdMaxAge(), directoryService.getTimeProvider() ); 391 392 if ( expired ) 393 { 394 throw new PasswordPolicyException( "password expired", PASSWORD_EXPIRED.getValue() ); 395 } 396 } 397 } 398 } 399 } 400}