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 }