001 /** 002 * Copyright 2010 The Kuali Foundation Licensed under the 003 * Educational Community License, Version 2.0 (the "License"); you may 004 * not use this file except in compliance with the License. You may 005 * obtain a copy of the License at 006 * 007 * http://www.osedu.org/licenses/ECL-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, 010 * software distributed under the License is distributed on an "AS IS" 011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 012 * or implied. See the License for the specific language governing 013 * permissions and limitations under the License. 014 */ 015 016 package org.kuali.student.security.kim; 017 018 import org.kuali.rice.core.config.Config; 019 import org.kuali.rice.core.config.ConfigContext; 020 import org.kuali.student.security.spring.KSRiceDefaultUserDetailsService; 021 import org.springframework.dao.DataAccessException; 022 import org.springframework.security.AuthenticationException; 023 import org.springframework.security.AuthenticationServiceException; 024 import org.springframework.security.BadCredentialsException; 025 import org.springframework.security.providers.AuthenticationProvider; 026 import org.springframework.security.providers.UsernamePasswordAuthenticationToken; 027 import org.springframework.security.providers.dao.AbstractUserDetailsAuthenticationProvider; 028 import org.springframework.security.providers.dao.DaoAuthenticationProvider; 029 import org.springframework.security.providers.dao.SaltSource; 030 import org.springframework.security.providers.encoding.PasswordEncoder; 031 import org.springframework.security.providers.encoding.PlaintextPasswordEncoder; 032 import org.springframework.security.userdetails.UserDetails; 033 import org.springframework.security.userdetails.UserDetailsService; 034 import org.springframework.util.Assert; 035 036 /** 037 * An {@link AuthenticationProvider} implementation that retrieves user details 038 * from an {@link UserDetailsService}. Slightly modified from {@link DaoAuthenticationProvider} 039 * 040 * @author Kuali Student Team 041 * 042 */ 043 public class KimAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { 044 045 //~ Instance fields ================================================================================================ 046 047 private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder(); 048 049 private SaltSource saltSource; 050 051 private UserDetailsService userDetailsService; 052 053 private boolean includeDetailsObject = true; 054 055 //~ Methods ======================================================================================================== 056 057 protected void additionalAuthenticationChecks(UserDetails userDetails, 058 UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 059 Object salt = null; 060 Config config = ConfigContext.getCurrentContextConfig(); 061 String ksIgnoreRiceLogin = config.getProperty("ks.ignore.rice.login"); 062 063 // We skip the salt value since the password won't match when people are testing. 064 // I do have a concern... the context of this attribute, does it imply use another authentication handler ? 065 // I ask since Rice is used 2 determine the workflows... 066 if(!Boolean.valueOf(ksIgnoreRiceLogin)){ 067 if (this.saltSource != null) { 068 salt = this.saltSource.getSalt(userDetails); 069 } 070 071 if (authentication.getCredentials() == null) { 072 throw new BadCredentialsException(messages.getMessage( 073 "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), 074 includeDetailsObject ? userDetails : null); 075 } 076 077 String presentedPassword = authentication.getCredentials().toString(); 078 079 if (!passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { 080 throw new BadCredentialsException(messages.getMessage( 081 "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), 082 includeDetailsObject ? userDetails : null); 083 } 084 } 085 } 086 087 protected void doAfterPropertiesSet() throws Exception { 088 Assert.notNull(this.userDetailsService, "A UserDetailsService must be set"); 089 } 090 091 protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) 092 throws AuthenticationException { 093 UserDetails loadedUser; 094 095 UserDetailsService ksRiceDefaultUserDetailsService = this.getUserDetailsService(); 096 if(!(ksRiceDefaultUserDetailsService instanceof KSRiceDefaultUserDetailsService)){ 097 throw new AuthenticationServiceException( 098 "UserDetailsService is not an an instance of KSRiceDefaultUserDetailsService"); 099 } 100 101 try { 102 //loadedUser = this.getUserDetailsService().loadUserByUsername(username); 103 loadedUser = ((KSRiceDefaultUserDetailsService)ksRiceDefaultUserDetailsService).loadUserByUsernameAndToken(username, authentication); 104 } 105 catch (DataAccessException repositoryProblem) { 106 throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem); 107 } 108 109 if (loadedUser == null) { 110 throw new AuthenticationServiceException( 111 "UserDetailsService returned null, which is an interface contract violation"); 112 } 113 return loadedUser; 114 } 115 116 /** 117 * Sets the PasswordEncoder instance to be used to encode and validate passwords. 118 * If not set, {@link PlaintextPasswordEncoder} will be used by default. 119 * 120 * @param passwordEncoder The passwordEncoder to use 121 */ 122 public void setPasswordEncoder(PasswordEncoder passwordEncoder) { 123 this.passwordEncoder = passwordEncoder; 124 } 125 126 protected PasswordEncoder getPasswordEncoder() { 127 return passwordEncoder; 128 } 129 130 /** 131 * The source of salts to use when decoding passwords. <code>null</code> 132 * is a valid value, meaning the <code>DaoAuthenticationProvider</code> 133 * will present <code>null</code> to the relevant <code>PasswordEncoder</code>. 134 * 135 * @param saltSource to use when attempting to decode passwords via the <code>PasswordEncoder</code> 136 */ 137 public void setSaltSource(SaltSource saltSource) { 138 this.saltSource = saltSource; 139 } 140 141 protected SaltSource getSaltSource() { 142 return saltSource; 143 } 144 145 public void setUserDetailsService(UserDetailsService userDetailsService) { 146 this.userDetailsService = userDetailsService; 147 } 148 149 protected UserDetailsService getUserDetailsService() { 150 return userDetailsService; 151 } 152 153 protected boolean isIncludeDetailsObject() { 154 return includeDetailsObject; 155 } 156 }