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}