001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kim.client.acegi;
017
018import org.acegisecurity.Authentication;
019import org.acegisecurity.AuthenticationException;
020import org.acegisecurity.BadCredentialsException;
021import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
022import org.acegisecurity.providers.cas.CasAuthenticationProvider;
023import org.acegisecurity.providers.cas.CasAuthenticationToken;
024import org.acegisecurity.providers.cas.StatelessTicketCache;
025import org.acegisecurity.ui.cas.CasProcessingFilter;
026import org.acegisecurity.userdetails.UserDetails;
027import org.apache.commons.logging.Log;
028import 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*/
043public 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}