001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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 edu.yale.its.tp.cas.servlet;
017:
018: import java.io.IOException;
019: import java.io.PrintWriter;
020: import java.security.SecureRandom;
021:
022: import javax.servlet.ServletConfig;
023: import javax.servlet.ServletContext;
024: import javax.servlet.ServletException;
025: import javax.servlet.http.HttpServlet;
026: import javax.servlet.http.HttpServletRequest;
027: import javax.servlet.http.HttpServletResponse;
028:
029: import edu.yale.its.tp.cas.ticket.GrantorCache;
030: import edu.yale.its.tp.cas.ticket.ProxyGrantingTicket;
031: import edu.yale.its.tp.cas.ticket.ServiceTicket;
032: import edu.yale.its.tp.cas.ticket.ServiceTicketCache;
033: import edu.yale.its.tp.cas.ticket.TicketException;
034: import edu.yale.its.tp.cas.ticket.Util;
035: import edu.yale.its.tp.cas.util.SecureURL;
036:
037: /**
038: * Handles ST validation and PGT acquisition.
039: */
040: public class ServiceValidate extends HttpServlet {
041:
042: // *********************************************************************
043: // KFSConstants
044:
045: // failure codes
046: private static final String INVALID_REQUEST = "INVALID_REQUEST";
047: private static final String INVALID_TICKET = "INVALID_TICKET";
048: private static final String INVALID_SERVICE = "INVALID_SERVICE";
049: private static final String INTERNAL_ERROR = "INTERNAL_ERROR";
050:
051: // PGT IOU length
052: private static final int PGT_IOU_LENGTH = 50;
053:
054: // *********************************************************************
055: // Internal state
056:
057: protected ServiceTicketCache stCache;
058: protected GrantorCache pgtCache;
059: private static int serial = 0;
060: private ServletContext app;
061:
062: // *********************************************************************
063: // Initialization
064:
065: public void init(ServletConfig config) throws ServletException {
066: // retrieve the context and the cache
067: app = config.getServletContext();
068: stCache = (ServiceTicketCache) app.getAttribute("stCache");
069: pgtCache = (GrantorCache) app.getAttribute("pgtCache");
070: }
071:
072: // *********************************************************************
073: // Request handling
074:
075: public void doGet(HttpServletRequest request,
076: HttpServletResponse response) throws IOException,
077: ServletException {
078: PrintWriter out = null;
079: try {
080: out = response.getWriter();
081: if (request.getParameter("service") == null
082: || request.getParameter("ticket") == null) {
083: validationFailure(out, INVALID_REQUEST,
084: "'service' and 'ticket' parameters are both required");
085: } else {
086: String ticket = request.getParameter("ticket");
087: String service = request.getParameter("service");
088: String renew = request.getParameter("renew");
089: ServiceTicket st = (ServiceTicket) stCache
090: .getTicket(ticket);
091: if (st == null) {
092: validationFailure(out, INVALID_TICKET, "ticket '"
093: + ticket + "' not recognized");
094: } else if (!st.getService().equals(service)) {
095: validationFailure(out, INVALID_SERVICE, "ticket '"
096: + ticket
097: + "' does not match supplied service");
098: } else if ("true".equals(renew) && !st.isFromNewLogin()) {
099: validationFailure(out, INVALID_TICKET,
100: "ticket not backed by initial CAS login, as requested");
101: } else {
102: String pgtIOU = null;
103: if (request.getParameter("pgtUrl") != null)
104: pgtIOU = sendPgt(st, request
105: .getParameter("pgtUrl"));
106: validationSuccess(out, st, pgtIOU);
107: }
108: }
109: } catch (Exception ex) {
110: try {
111: if (out != null)
112: validationFailure(out, INTERNAL_ERROR,
113: "Unexpected exception");
114: // to do: log?
115: } catch (IOException ignoredEx) {
116: // ignore
117: }
118: }
119: }
120:
121: // *********************************************************************
122: // Response-management methods
123:
124: /** Sends a validation failure message to the given PrintWriter. */
125: protected static void validationFailure(PrintWriter out,
126: String code, String errorMessage) throws IOException {
127: out
128: .println("<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>");
129: out
130: .println(" <cas:authenticationFailure code='" + code
131: + "'>");
132: out.println(" " + errorMessage);
133: out.println(" </cas:authenticationFailure>");
134: out.println("</cas:serviceResponse>");
135: }
136:
137: /** Sends a validation success message to the given PrintWriter. */
138: protected void validationSuccess(PrintWriter out, ServiceTicket st,
139: String pgtIOU) {
140: out
141: .println("<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>");
142: out.println(" <cas:authenticationSuccess>");
143: out
144: .println(" <cas:user>" + st.getUsername()
145: + "</cas:user>");
146: if (pgtIOU != null && !pgtIOU.equals("")) {
147: out.println(" <cas:proxyGrantingTicket>" + pgtIOU
148: + "</cas:proxyGrantingTicket>");
149: }
150: out.println(" </cas:authenticationSuccess>");
151: out.println("</cas:serviceResponse>");
152: }
153:
154: /** Creates and sends a new PGT, returning a unique IOU for this PGT. */
155: private String sendPgt(ServiceTicket st, String callbackUrl)
156: throws TicketException {
157: // first, create the PGT and save it to the cache
158: ProxyGrantingTicket pgt = new ProxyGrantingTicket(st,
159: callbackUrl);
160: String pgtToken = pgtCache.addTicket(pgt);
161:
162: // now, create an IOU (with a serial and a random component)
163: byte[] b = new byte[PGT_IOU_LENGTH];
164: SecureRandom sr = new SecureRandom();
165: sr.nextBytes(b);
166: String pgtIou = "PGTIOU-" + (serial++) + "-"
167: + Util.toPrintable(b);
168:
169: // now, send this PGT/IOU pair to our callback URL
170: boolean sent = callbackWithPgt(callbackUrl, pgtToken, pgtIou);
171:
172: // return the IOU if appropriate
173: if (sent)
174: return pgtIou;
175: else
176: return null;
177: }
178:
179: /**
180: * Contacts the URL with a PGT and an IOU, but only if the URL's server's certificate appears appropriate for the URL. Returns
181: * <tt>true</tt> on success, <tt>false</tt> on failure of any kind.
182: */
183: private boolean callbackWithPgt(String callbackUrl, String pgtId,
184: String iouId) {
185: try {
186: String target = null;
187: if (callbackUrl.indexOf('?') == -1)
188: target = callbackUrl + "?pgtIou=" + iouId + "&pgtId="
189: + pgtId;
190: else
191: target = callbackUrl + "&pgtIou=" + iouId + "&pgtId="
192: + pgtId;
193: SecureURL.retrieve(target);
194:
195: // we succeeded!
196: return true;
197:
198: } catch (IOException ex) {
199: app.log("PGT callback failed: " + ex.toString());
200: return false;
201: }
202: }
203:
204: /**
205: * Returns true if the DN is appropriate for the expected server, false otherwise.
206: */
207: public static boolean validateDn(String dn, String expectedServer) {
208: // check the CN literally against 'expectedServer'
209: // (we can add other, more lenient checks later)
210: int cnIndex = dn.indexOf("CN=") + "CN=".length();
211: if (cnIndex == -1)
212: return false;
213: int commaIndex = dn.substring(cnIndex).indexOf(',') + cnIndex;
214: String dnCn;
215: if (commaIndex <= cnIndex)
216: dnCn = dn.substring(cnIndex);
217: else
218: dnCn = dn.substring(cnIndex, commaIndex);
219: return (dnCn.equals(expectedServer));
220: }
221:
222: }
|