001 /**
002 * Copyright 2005-2013 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 }