001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kim.client.acegi;
017    
018    import org.acegisecurity.Authentication;
019    import org.acegisecurity.AuthenticationException;
020    import org.acegisecurity.BadCredentialsException;
021    import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
022    import org.acegisecurity.providers.cas.CasAuthenticationProvider;
023    import org.acegisecurity.providers.cas.CasAuthenticationToken;
024    import org.acegisecurity.providers.cas.StatelessTicketCache;
025    import org.acegisecurity.ui.cas.CasProcessingFilter;
026    import org.acegisecurity.userdetails.UserDetails;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    
030    /**
031     * A {@link CasAuthenticationProvider} implementation that integrates with 
032     * Kuali Identity Management (KIM).<p>This 
033     * <code>CasAuthenticationProvider</code> is capable of validating {@link
034     * UsernamePasswordAuthenticationToken} requests which contains a 
035     * distributed session token. It can also validate a previously created 
036     * {@link CasAuthenticationToken}.</p>
037     *
038     * Verifies the the <code>UserDetails</code> based on a valid CAS ticket 
039     * response.
040     * 
041     * @author Kuali Rice Team (rice.collab@kuali.org)
042    */
043    public class KualiCasAuthenticationProvider extends CasAuthenticationProvider {
044        
045        private static final Log logger = LogFactory.getLog(KualiCasAuthenticationProvider.class);
046    
047        /**
048         * This overridden method is copied from CAS verbatim.  For some reason 
049         * {@link authenticateNow} would not override and the super method 
050         * would get called until did this method was also overridden.
051         * 
052         * @see org.acegisecurity.providers.cas.CasAuthenticationProvider#authenticate(org.acegisecurity.Authentication)
053         */
054        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
055            StatelessTicketCache statelessTicketCache = this.getStatelessTicketCache();
056            String key = this.getKey();
057            if (!supports(authentication.getClass())) {
058                return null;
059            }
060    
061            if (authentication instanceof UsernamePasswordAuthenticationToken
062                && (!CasProcessingFilter.CAS_STATEFUL_IDENTIFIER.equals(authentication.getPrincipal().toString())
063                && !CasProcessingFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal().toString()))) {
064                // UsernamePasswordAuthenticationToken not CAS related
065                return null;
066            }
067    
068            // If an existing CasAuthenticationToken, just check we created it
069            if (authentication instanceof CasAuthenticationToken) {
070                if (key.hashCode() == ((CasAuthenticationToken) authentication).getKeyHash()) {
071                    return authentication;
072                } else {
073                    throw new BadCredentialsException(messages.getMessage("CasAuthenticationProvider.incorrectKey",
074                            "The presented CasAuthenticationToken does not contain the expected key"));
075                }
076            }
077    
078            // Ensure credentials are presented
079            if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
080                throw new BadCredentialsException(messages.getMessage("CasAuthenticationProvider.noServiceTicket",
081                        "Failed to provide a CAS service ticket to validate"));
082            }
083    
084            boolean stateless = false;
085    
086            if (authentication instanceof UsernamePasswordAuthenticationToken
087                && CasProcessingFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal())) {
088                stateless = true;
089            }
090    
091            CasAuthenticationToken result = null;
092    
093            if (stateless) {
094                // Try to obtain from cache
095                result = statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
096            }
097    
098            if (result == null) {
099                result = this.authenticateNow(authentication);
100                result.setDetails(authentication.getDetails());
101            }
102    
103            if (stateless) {
104                // Add to cache
105                statelessTicketCache.putTicketInCache(result);
106            }
107    
108            return result;
109        }
110        
111        /**
112         * This overridden method is differs from the super method by 
113         * populating the user details by passing the full response
114         * 
115         * @see org.acegisecurity.providers.cas.CasAuthenticationProvider#authenticateNow(Authentication authentication)
116         */
117        private CasAuthenticationToken authenticateNow(Authentication authentication) throws AuthenticationException {
118            // Validate
119            KualiTicketResponse response = (KualiTicketResponse)this.getTicketValidator().confirmTicketValid(authentication.getCredentials().toString());
120    
121            // Check proxy list is trusted
122            this.getCasProxyDecider().confirmProxyListTrusted(response.getProxyList());
123            if (logger.isDebugEnabled()) {
124                logger.debug("authenticationNOW:" + response);
125            }
126            // Lookup user details      
127            logger.debug("\n\npopulating authorities\n\n");
128            UserDetails userDetails = ((KualiCasAuthoritiesPopulator)this.getCasAuthoritiesPopulator()).getUserDetails(response);        
129    
130            // Construct CasAuthenticationToken
131            return new CasAuthenticationToken(this.getKey(), userDetails, authentication.getCredentials(),
132                userDetails.getAuthorities(), userDetails, response.getProxyList(), response.getProxyGrantingTicketIou());
133        }
134    }