001/* 002 * (C) Copyright 2000-2003 Yale University. All rights reserved. 003 * 004 * THIS SOFTWARE IS PROVIDED "AS IS," AND ANY EXPRESS OR IMPLIED 005 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 006 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, ARE EXPRESSLY 007 * DISCLAIMED. IN NO EVENT SHALL YALE UNIVERSITY OR ITS EMPLOYEES BE 008 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 009 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED, THE COSTS OF 010 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA OR 011 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 012 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 013 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 014 * SOFTWARE, EVEN IF ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH 015 * DAMAGE. 016 * 017 * Redistribution and use of this software in source or binary forms, 018 * with or without modification, are permitted, provided that the 019 * following conditions are met: 020 * 021 * 1. Any redistribution must include the above copyright notice and 022 * disclaimer and this list of conditions in any related documentation 023 * and, if feasible, in the redistributed software. 024 * 025 * 2. Any redistribution must include the acknowledgment, "This product 026 * includes software developed by Yale University," in any related 027 * documentation and, if feasible, in the redistributed software. 028 * 029 * 3. The names "Yale" and "Yale University" must not be used to endorse 030 * or promote products derived from this software. 031 */ 032 033package edu.yale.its.tp.cas.client; 034 035import java.io.IOException; 036import java.io.StringReader; 037import java.util.HashMap; 038import java.util.Map; 039 040import javax.xml.parsers.ParserConfigurationException; 041import javax.xml.parsers.SAXParserFactory; 042 043import org.nuxeo.common.utils.URIUtils; 044import org.xml.sax.Attributes; 045import org.xml.sax.InputSource; 046import org.xml.sax.SAXException; 047import org.xml.sax.XMLReader; 048import org.xml.sax.helpers.DefaultHandler; 049 050import edu.yale.its.tp.cas.util.SecureURL; 051 052/** 053 * Validates STs and optionally retrieves PGT IOUs. Designed with a bean-like interface for simplicity and generality. 054 */ 055public class ServiceTicketValidator { 056 057 // ********************************************************************* 058 // Private state 059 060 protected String casValidateUrl, proxyCallbackUrl, st, service, pgtIou, user, errorCode, errorMessage, 061 entireResponse; 062 063 protected boolean renew = false; 064 065 protected boolean attemptedAuthentication; 066 067 protected boolean successfulAuthentication; 068 069 // ********************************************************************* 070 // Accessors 071 072 /** 073 * Sets the CAS validation URL to use when validating tickets and retrieving PGT IOUs. 074 */ 075 public void setCasValidateUrl(String x) { 076 this.casValidateUrl = x; 077 } 078 079 /** 080 * Gets the CAS validation URL to use when validating tickets and retrieving PGT IOUs. 081 */ 082 public String getCasValidateUrl() { 083 return this.casValidateUrl; 084 } 085 086 /** 087 * Sets the callback URL, owned logically by the calling service, to receive the PGTid/PGTiou mapping. 088 */ 089 public void setProxyCallbackUrl(String x) { 090 this.proxyCallbackUrl = x; 091 } 092 093 /** 094 * Sets the "renew" flag on authentication. When set to "true", authentication will only succeed if this was an 095 * initial login (forced by the "renew" flag being set on login). 096 */ 097 public void setRenew(boolean b) { 098 this.renew = b; 099 } 100 101 /** 102 * Gets the callback URL, owned logically by the calling service, to receive the PGTid/PGTiou mapping. 103 */ 104 public String getProxyCallbackUrl() { 105 return this.proxyCallbackUrl; 106 } 107 108 /** 109 * Sets the ST to validate. 110 */ 111 public void setServiceTicket(String x) { 112 this.st = x; 113 } 114 115 /** 116 * Sets the service to use when validating. 117 */ 118 public void setService(String x) { 119 this.service = x; 120 } 121 122 /** 123 * Returns the strongly authenticated username. 124 */ 125 public String getUser() { 126 return this.user; 127 } 128 129 /** 130 * Returns the PGT IOU returned by CAS. 131 */ 132 public String getPgtIou() { 133 return this.pgtIou; 134 } 135 136 /** 137 * Returns <tt>true</tt> if the most recent authentication attempted succeeded, <tt>false</tt> otherwise. 138 */ 139 public boolean isAuthenticationSuccesful() { 140 return this.successfulAuthentication; 141 } 142 143 /** 144 * Returns an error message if CAS authentication failed. 145 */ 146 public String getErrorMessage() { 147 return this.errorMessage; 148 } 149 150 /** 151 * Returns CAS's error code if authentication failed. 152 */ 153 public String getErrorCode() { 154 return this.errorCode; 155 } 156 157 /** 158 * Retrieves CAS's entire response, if authentication was succsesful. 159 */ 160 public String getResponse() { 161 return this.entireResponse; 162 } 163 164 // ********************************************************************* 165 // Actuator 166 167 public void validate() throws IOException, SAXException, ParserConfigurationException { 168 if (casValidateUrl == null || st == null) 169 throw new IllegalStateException("must set validation URL and ticket"); 170 clear(); 171 attemptedAuthentication = true; 172 173 Map<String, String> urlParameters = new HashMap<String, String>(); 174 urlParameters.put("service", service); 175 urlParameters.put("ticket", st); 176 if (proxyCallbackUrl != null) { 177 urlParameters.put("pgtUrl", proxyCallbackUrl); 178 } 179 if (renew) { 180 urlParameters.put("renew", "true"); 181 } 182 183 String url = URIUtils.addParametersToURIQuery(casValidateUrl, urlParameters); 184 String response = SecureURL.retrieve(url, false); 185 this.entireResponse = response; 186 187 // parse the response and set appropriate properties 188 if (response != null) { 189 XMLReader r = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); 190 r.setFeature("http://xml.org/sax/features/namespaces", false); 191 r.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 192 r.setContentHandler(newHandler()); 193 r.parse(new InputSource(new StringReader(response))); 194 } 195 } 196 197 // ********************************************************************* 198 // Response parser 199 200 protected DefaultHandler newHandler() { 201 return new Handler(); 202 } 203 204 protected class Handler extends DefaultHandler { 205 206 // ********************************************** 207 // Constants 208 209 protected static final String AUTHENTICATION_SUCCESS = "cas:authenticationSuccess"; 210 211 protected static final String AUTHENTICATION_FAILURE = "cas:authenticationFailure"; 212 213 protected static final String PROXY_GRANTING_TICKET = "cas:proxyGrantingTicket"; 214 215 protected static final String USER = "cas:user"; 216 217 // ********************************************** 218 // Parsing state 219 220 protected StringBuffer currentText = new StringBuffer(); 221 222 protected boolean authenticationSuccess = false; 223 224 protected boolean authenticationFailure = false; 225 226 protected String netid, pgtIou, errorCode, errorMessage; 227 228 // ********************************************** 229 // Parsing logic 230 231 public void startElement(String ns, String ln, String qn, Attributes a) { 232 // clear the buffer 233 currentText = new StringBuffer(); 234 235 // check outer elements 236 if (qn.equals(AUTHENTICATION_SUCCESS)) { 237 authenticationSuccess = true; 238 } else if (qn.equals(AUTHENTICATION_FAILURE)) { 239 authenticationFailure = true; 240 errorCode = a.getValue("code"); 241 if (errorCode != null) 242 errorCode = errorCode.trim(); 243 } 244 } 245 246 public void characters(char[] ch, int start, int length) { 247 // store the body, in stages if necessary 248 currentText.append(ch, start, length); 249 } 250 251 public void endElement(String ns, String ln, String qn) throws SAXException { 252 if (authenticationSuccess) { 253 if (qn.equals(USER)) 254 user = currentText.toString().trim(); 255 if (qn.equals(PROXY_GRANTING_TICKET)) 256 pgtIou = currentText.toString().trim(); 257 } else if (authenticationFailure) { 258 if (qn.equals(AUTHENTICATION_FAILURE)) 259 errorMessage = currentText.toString().trim(); 260 } 261 } 262 263 public void endDocument() throws SAXException { 264 // save values as appropriate 265 if (authenticationSuccess) { 266 ServiceTicketValidator.this.user = user; 267 ServiceTicketValidator.this.pgtIou = pgtIou; 268 ServiceTicketValidator.this.successfulAuthentication = true; 269 } else if (authenticationFailure) { 270 ServiceTicketValidator.this.errorMessage = errorMessage; 271 ServiceTicketValidator.this.errorCode = errorCode; 272 ServiceTicketValidator.this.successfulAuthentication = false; 273 } else 274 throw new SAXException("no indication of success of failure from CAS"); 275 } 276 } 277 278 // ********************************************************************* 279 // Utility methods 280 281 /** 282 * Clears internally manufactured state. 283 */ 284 protected void clear() { 285 user = pgtIou = errorMessage = null; 286 attemptedAuthentication = false; 287 successfulAuthentication = false; 288 } 289 290}