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    }