001: /*
002: * JOSSO: Java Open Single Sign-On
003: *
004: * Copyright 2004-2008, Atricore, Inc.
005: *
006: * This is free software; you can redistribute it and/or modify it
007: * under the terms of the GNU Lesser General Public License as
008: * published by the Free Software Foundation; either version 2.1 of
009: * the License, or (at your option) any later version.
010: *
011: * This software is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this software; if not, write to the Free
018: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
019: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
020: */
021:
022: package org.josso.tc60.agent;
023:
024: import org.apache.catalina.*;
025: import org.apache.catalina.authenticator.SavedRequest;
026: import org.apache.catalina.connector.Request;
027: import org.apache.catalina.connector.Response;
028: import org.apache.catalina.deploy.SecurityConstraint;
029: import org.apache.catalina.util.LifecycleSupport;
030: import org.apache.catalina.valves.ValveBase;
031:
032: import org.josso.Lookup;
033: import org.josso.agent.Constants;
034: import org.josso.agent.LocalSession;
035: import org.josso.agent.SingleSignOnEntry;
036: import org.josso.agent.SSOAgentRequest;
037:
038: import javax.servlet.ServletException;
039: import javax.servlet.http.*;
040: import java.io.IOException;
041: import java.util.HashMap;
042:
043: /**
044: * Single Sign-On Agent implementation for Tomcat Catalina.
045: *
046: * @author <a href="mailto:gbrigand@josso.org">Gianluca Brigandi</a>
047: * @version CVS $Id: SSOAgentValve.java 403 2006-12-28 21:55:06Z sgonzalez $
048: */
049: public class SSOAgentValve extends ValveBase implements Lifecycle,
050: SessionListener {
051:
052: /**
053: * The debugging detail level for this component.
054: */
055: protected int debug = 0;
056:
057: /**
058: * Descriptive information about this Valve implementation.
059: */
060: protected static String info = "org.apache.catalina.authenticator.SingleSignOn";
061:
062: /**
063: * The lifecycle event support for this component.
064: */
065: protected LifecycleSupport lifecycle = new LifecycleSupport(this );
066:
067: /**
068: * Component started flag.
069: */
070: protected boolean started = false;
071: private CatalinaSSOAgent _agent;
072:
073: /**
074: * Catalina Session to Local Session Map.
075: */
076: HashMap _sessionMap = new HashMap();
077:
078: // ------------------------------------------------------------- Properties
079:
080: /**
081: * Return the debugging detail level.
082: */
083: public int getDebug() {
084:
085: return (this .debug);
086:
087: }
088:
089: /**
090: * Set the debugging detail level.
091: *
092: * @param debug The new debugging detail level
093: */
094: public void setDebug(int debug) {
095:
096: this .debug = debug;
097:
098: }
099:
100: // ------------------------------------------------------ SessionListener Methods
101:
102: public void sessionEvent(SessionEvent event) {
103:
104: // obtain the local session for the catalina session, and notify the listeners for it.
105: LocalSession localSession = (LocalSession) _sessionMap
106: .get(event.getSession());
107:
108: if (event.getType().equals(Session.SESSION_DESTROYED_EVENT))
109: localSession.expire();
110: }
111:
112: // ------------------------------------------------------ Lifecycle Methods
113:
114: /**
115: * Add a lifecycle event listener to this component.
116: *
117: * @param listener The listener to add
118: */
119: public void addLifecycleListener(LifecycleListener listener) {
120:
121: lifecycle.addLifecycleListener(listener);
122:
123: }
124:
125: /**
126: * Get the lifecycle listeners associated with this lifecycle. If this
127: * Lifecycle has no listeners registered, a zero-length array is returned.
128: */
129: public LifecycleListener[] findLifecycleListeners() {
130:
131: return lifecycle.findLifecycleListeners();
132:
133: }
134:
135: /**
136: * Remove a lifecycle event listener from this component.
137: *
138: * @param listener The listener to remove
139: */
140: public void removeLifecycleListener(LifecycleListener listener) {
141:
142: lifecycle.removeLifecycleListener(listener);
143:
144: }
145:
146: /**
147: * Set the Container to which this Valve is attached.
148: *
149: * @param container The container to which we are attached
150: public void setContainer(Container container) {
151:
152: if (!(container instanceof Context))
153: throw new IllegalArgumentException
154: ("The SSOAgentValve must be associated to a Catalina Context or Host");
155:
156: super.setContainer(container);
157: _container = container;
158: }
159: */
160:
161: /**
162: * Prepare for the beginning of active use of the public methods of this
163: * component. This method should be called after <code>configure()</code>,
164: * and before any of the public methods of the component are utilized.
165: *
166: * @throws LifecycleException if this component detects a fatal error
167: * that prevents this component from being used
168: */
169: public void start() throws LifecycleException {
170:
171: // Validate and update our current component state
172: if (started)
173: throw new LifecycleException("Agent already started");
174: lifecycle.fireLifecycleEvent(START_EVENT, null);
175: started = true;
176:
177: try {
178: Lookup lookup = Lookup.getInstance();
179: lookup.init("josso-agent-config.xml");
180: _agent = (CatalinaSSOAgent) lookup.lookupSSOAgent();
181: _agent.setDebug(debug);
182: _agent.setCatalinaContainer(container);
183: } catch (Exception e) {
184: e.printStackTrace(System.err);
185: throw new LifecycleException("Error starting SSO Agent : "
186: + e.getMessage());
187: }
188: _agent.start();
189:
190: if (debug >= 1)
191: log("Started");
192: }
193:
194: /**
195: * Gracefully terminate the active use of the public methods of this
196: * component. This method should be the last one called on a given
197: * instance of this component.
198: *
199: * @throws LifecycleException if this component detects a fatal error
200: * that needs to be reported
201: */
202: public void stop() throws LifecycleException {
203:
204: // Validate and update our current component state
205: if (!started)
206: throw new LifecycleException("Agent not started");
207: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
208: started = false;
209:
210: _agent.stop();
211:
212: if (debug >= 1)
213: log("Stopped");
214:
215: }
216:
217: // ---------------------------------------------------------- Valve Methods
218:
219: /**
220: * Return descriptive information about this Valve implementation.
221: */
222: public String getInfo() {
223: return (info);
224: }
225:
226: /**
227: * Perform single-sign-on support processing for this request.
228: *
229: * @param request The servlet request we are processing
230: * @param response The servlet response we are creating
231: * in the current processing pipeline
232: * @throws IOException if an input/output error occurs
233: * @throws ServletException if a servlet error occurs
234: */
235:
236: public void invoke(Request request, Response response)
237: throws IOException, ServletException {
238:
239: HttpServletRequest hreq = (HttpServletRequest) request
240: .getRequest();
241: HttpServletResponse hres = (HttpServletResponse) response
242: .getResponse();
243:
244: if (debug >= 1)
245: log("Processing : " + hreq.getContextPath());
246:
247: try {
248: // ------------------------------------------------------------------
249: // Check with the agent if this context should be processed.
250: // ------------------------------------------------------------------
251: String contextPath = hreq.getContextPath();
252:
253: // In catalina, the empty context is considered the root context
254: if ("".equals(contextPath))
255: contextPath = "/";
256:
257: if (!_agent.isPartnerApp(contextPath)) {
258: getNext().invoke(request, response);
259: if (debug >= 1)
260: log("Context is not a josso partner app : "
261: + hreq.getContextPath());
262:
263: return;
264: }
265:
266: // ------------------------------------------------------------------
267: // Check if the partner application required the login form
268: // ------------------------------------------------------------------
269: if (debug >= 1)
270: log("Checking if its a josso_login_request for '"
271: + hreq.getRequestURI() + "'");
272:
273: if (hreq.getRequestURI()
274: .endsWith(Constants.JOSSO_LOGIN_URI)) {
275:
276: if (debug >= 1)
277: log("josso_login_request received for uri '"
278: + hreq.getRequestURI() + "'");
279:
280: String loginUrl = _agent.getGatewayLoginUrl();
281: String onErrorUrl = _agent.getGatewayLoginErrorUrl();
282:
283: // TODO: this must be configurable
284: // TODO: improve URL handling
285:
286: String backto = _agent.buildBackToURL(hreq,
287: Constants.JOSSO_SECURITY_CHECK_URI);
288:
289: loginUrl = loginUrl
290: + "?josso_back_to="
291: + backto
292: + (onErrorUrl != null ? "&josso_on_error="
293: + onErrorUrl : "");
294:
295: if (debug >= 1)
296: log("Redirecting to login url '" + loginUrl + "'");
297:
298: hres.sendRedirect(hres.encodeRedirectURL(loginUrl));
299:
300: return;
301:
302: }
303:
304: // ------------------------------------------------------------------
305: // Check if the partner application required a logout
306: // ------------------------------------------------------------------
307: if (debug >= 1)
308: log("Checking if its a josso_logout request for '"
309: + hreq.getRequestURI() + "'");
310:
311: if (hreq.getRequestURI().endsWith(
312: Constants.JOSSO_LOGOUT_URI)) {
313:
314: if (debug >= 1)
315: log("josso_logout request received for uri '"
316: + hreq.getRequestURI() + "'");
317:
318: String backto = _agent.buildBackToURL(hreq, "/");
319: String logoutUrl = _agent.getGatewayLogoutUrl()
320: + (backto != null ? "?josso_back_to=" + backto
321: : "");
322:
323: if (debug >= 1)
324: log("Redirecting to logout url '" + logoutUrl + "'");
325:
326: // Clear previous COOKIE ...
327: Cookie ssoCookie = _agent.newJossoCookie(request
328: .getContextPath(), "-");
329: hres.addCookie(ssoCookie);
330:
331: hres.sendRedirect(hres.encodeRedirectURL(logoutUrl));
332:
333: return;
334:
335: }
336:
337: // ------------------------------------------------------------------
338: // Check for the single sign on cookie
339: // ------------------------------------------------------------------
340: if (debug >= 1)
341: log("Checking for SSO cookie");
342: Cookie cookie = null;
343: Cookie cookies[] = hreq.getCookies();
344: if (cookies == null)
345: cookies = new Cookie[0];
346: for (int i = 0; i < cookies.length; i++) {
347: if (org.josso.gateway.Constants.JOSSO_SINGLE_SIGN_ON_COOKIE
348: .equals(cookies[i].getName())) {
349: cookie = cookies[i];
350: break;
351: }
352: }
353: if (cookie == null) {
354: if (debug >= 1)
355: log("SSO cookie is not present, checking for outbound relaying");
356:
357: if (!(hreq.getRequestURI().endsWith(
358: Constants.JOSSO_SECURITY_CHECK_URI) && hreq
359: .getParameter("josso_assertion_id") != null)) {
360: log("SSO cookie not present and relaying was not requested, skipping");
361: getNext().invoke(request, response);
362: return;
363: }
364:
365: }
366:
367: // ------------------------------------------------------------------
368: // Check if this URI is subject to SSO protection
369: // ------------------------------------------------------------------
370:
371: // There are some web-resources to ignore.
372: String[] ignoredWebResources = _agent.getPartnerAppConfig(
373: contextPath).getIgnoredWebRources();
374:
375: if (debug >= 1)
376: log("Found ["
377: + (ignoredWebResources != null ? ignoredWebResources.length
378: + ""
379: : "no") + "] ignored web resources ");
380:
381: if (ignoredWebResources != null
382: && ignoredWebResources.length > 0) {
383:
384: Realm realm = request.getContext().getRealm();
385: SecurityConstraint[] constraints = realm
386: .findSecurityConstraints(request, request
387: .getContext());
388:
389: if ((constraints != null)) {
390:
391: for (int i = 0; i < ignoredWebResources.length; i++) {
392:
393: // For each ignored web resource, find a web resource collection that matchs.
394: String ignoredWebResource = ignoredWebResources[i];
395: for (int j = 0; j < constraints.length; j++) {
396:
397: // Find a matching web resource collection for each ignored web resources
398: SecurityConstraint constraint = constraints[j];
399: if (constraint
400: .findCollection(ignoredWebResource) != null) {
401:
402: // We should ignore this URI, it's not subject to SSO protection.
403: if (debug >= 1)
404: log("Not subject to SSO protection : web-resource-name:"
405: + ignoredWebResource);
406: getNext().invoke(request, response);
407: return;
408: }
409: }
410: }
411: }
412: }
413:
414: // This URI should be protected by SSO, go on ...
415:
416: String jossoSessionId = (cookie == null) ? null : cookie
417: .getValue();
418: Session session = getSession(request, true);
419:
420: if (debug >= 1)
421: log("Session is: " + session);
422:
423: LocalSession localSession = new CatalinaLocalSession(
424: session);
425:
426: // ------------------------------------------------------------------
427: // Invoke the SSO Agent
428: // ------------------------------------------------------------------
429: if (debug >= 1)
430: log("Executing agent...");
431:
432: _agent.setCatalinaContainer(request.getContext());
433:
434: // ------------------------------------------------------------------
435: // Check if a user has been authenitcated and should be checked by the agent.
436: // ------------------------------------------------------------------
437: if (debug >= 1)
438: log("Checking if its a josso_security_check for '"
439: + hreq.getRequestURI() + "'");
440:
441: if (hreq.getRequestURI().endsWith(
442: Constants.JOSSO_SECURITY_CHECK_URI)
443: && hreq.getParameter("josso_assertion_id") != null) {
444:
445: if (debug >= 1)
446: log("josso_security_check received for uri '"
447: + hreq.getRequestURI() + "' assertion id '"
448: + hreq.getParameter("josso_assertion_id"));
449:
450: String assertionId = hreq
451: .getParameter(Constants.JOSSO_ASSERTION_ID_PARAMETER);
452:
453: CatalinaSSOAgentRequest relayRequest;
454:
455: if (debug >= 1)
456: log("Outbound relaying requested for assertion id ["
457: + assertionId + "]");
458:
459: relayRequest = new CatalinaSSOAgentRequest(
460: SSOAgentRequest.ACTION_RELAY, null,
461: localSession, assertionId);
462:
463: relayRequest.setContext(request.getContext());
464: SingleSignOnEntry entry = _agent
465: .processRequest(relayRequest);
466:
467: if (debug >= 1)
468: log("Outbound relaying succesfull for assertion id ["
469: + assertionId + "]");
470:
471: if (debug >= 1)
472: log("Assertion id [" + assertionId
473: + "] mapped to SSO session id ["
474: + entry.ssoId + "]");
475:
476: // The cookie is valid to for the partner application only ... in the future each partner app may
477: // store a different auth. token (SSO SESSION) value
478: cookie = _agent.newJossoCookie(
479: request.getContextPath(), entry.ssoId);
480: hres.addCookie(cookie);
481:
482: // Redirect the user to the original request URI (which will cause
483: // the original request to be restored)
484:
485: String requestURI = savedRequestURL(session);
486: if (requestURI == null) {
487:
488: // If no saved request is found, redirect to the partner app root :
489: requestURI = hreq
490: .getRequestURI()
491: .substring(
492: 0,
493: (hreq.getRequestURI().length() - Constants.JOSSO_SECURITY_CHECK_URI
494: .length()));
495:
496: // If we're behind a reverse proxy, we have to alter the URL ... this was not necessary on tomcat 5.0 ?!
497: String singlePointOfAccess = _agent
498: .getSinglePointOfAccess();
499: if (singlePointOfAccess != null) {
500: requestURI = singlePointOfAccess + requestURI;
501: } else {
502: String reverseProxyHost = hreq
503: .getHeader(org.josso.gateway.Constants.JOSSO_REVERSE_PROXY_HEADER);
504: if (reverseProxyHost != null) {
505: requestURI = reverseProxyHost + requestURI;
506: }
507: }
508:
509: if (debug >= 1)
510: log("No saved request found, using : '"
511: + requestURI + "'");
512: }
513:
514: if (debug >= 1)
515: log("Redirecting to original '" + requestURI + "'");
516:
517: hres.sendRedirect(hres.encodeRedirectURL(requestURI));
518:
519: return;
520: }
521:
522: CatalinaSSOAgentRequest r;
523:
524: log("Creating Security Context for Session [" + session
525: + "]");
526: r = new CatalinaSSOAgentRequest(
527: SSOAgentRequest.ACTION_ESTABLISH_SECURITY_CONTEXT,
528: jossoSessionId, localSession);
529:
530: r.setContext(request.getContext());
531: SingleSignOnEntry entry = _agent.processRequest(r);
532:
533: if (debug >= 1)
534: log("Executed agent.");
535:
536: if (_sessionMap.get(localSession.getWrapped()) == null) {
537: // the local session is new so, make the valve listen for its events so that it can
538: // map them to local session events.
539: session.addSessionListener(this );
540: _sessionMap.put(session, localSession);
541: }
542:
543: // ------------------------------------------------------------------
544: // Has a valid user already been authenticated?
545: // ------------------------------------------------------------------
546: if (debug >= 1)
547: log("Process request for '" + hreq.getRequestURI()
548: + "'");
549:
550: if (entry != null) {
551: if (debug >= 1)
552: log("Principal '" + entry.principal
553: + "' has already been authenticated");
554:
555: (request).setAuthType(entry.authType);
556: (request).setUserPrincipal(entry.principal);
557:
558: }
559:
560: // propagate the login and logout URLs to
561: // partner applications.
562: hreq.setAttribute("org.josso.agent.gateway-login-url",
563: _agent.getGatewayLoginUrl());
564: hreq.setAttribute("org.josso.agent.gateway-logout-url",
565: _agent.getGatewayLogoutUrl());
566: hreq.setAttribute("org.josso.agent.ssoSessionid",
567: jossoSessionId);
568:
569: // ------------------------------------------------------------------
570: // Invoke the next Valve in our pipeline
571: // ------------------------------------------------------------------
572: getNext().invoke(request, response);
573: } finally {
574: if (debug >= 1)
575: log("Processed : " + hreq.getContextPath());
576: }
577: }
578:
579: // --------------------------------------------------------- Public Methods
580:
581: /**
582: * Return a String rendering of this object.
583: */
584: public String toString() {
585:
586: StringBuffer sb = new StringBuffer("SingleSignOn[");
587: // Sometimes the container is not present when this method is invoked ...
588: sb.append(container != null ? container.getName() : "");
589: sb.append("]");
590: return (sb.toString());
591:
592: }
593:
594: // -------------------------------------------------------- Package Methods
595:
596: // ------------------------------------------------------ Protected Methods
597:
598: /**
599: * Return the internal Session that is associated with this HttpRequest,
600: * or <code>null</code> if there is no such Session.
601: *
602: * @param request The HttpRequest we are processing
603: */
604: protected Session getSession(Request request) {
605:
606: return (getSession(request, false));
607:
608: }
609:
610: /**
611: * Return the internal Session that is associated with this HttpRequest,
612: * possibly creating a new one if necessary, or <code>null</code> if
613: * there is no such session and we did not create one.
614: *
615: * @param request The HttpRequest we are processing
616: * @param create Should we create a session if needed?
617: */
618: protected Session getSession(Request request, boolean create) {
619:
620: HttpServletRequest hreq = (HttpServletRequest) request
621: .getRequest();
622: HttpSession hses = hreq.getSession(create);
623: if (debug >= 1)
624: log("getCurrentSession() : hses " + hses);
625:
626: if (hses == null)
627: return (null);
628: // Get catalina Context from request ...
629: Manager manager = request.getContext().getManager();
630:
631: if (debug >= 1)
632: log("getCurrentSession() : manager is " + manager);
633: if (manager == null)
634: return (null);
635: else {
636: try {
637: return (manager.findSession(hses.getId()));
638: } catch (IOException e) {
639: return (null);
640: }
641: }
642:
643: }
644:
645: /**
646: * Log a message on the Logger associated with our Container (if any).
647: *
648: * @param message Message to be logged
649: */
650: protected void log(String message) {
651: if (_agent != null)
652: _agent.log(message);
653: }
654:
655: /**
656: * Log a message on the Logger associated with our Container (if any).
657: *
658: * @param message Message to be logged
659: * @param throwable Associated exception
660: */
661: protected void log(String message, Throwable throwable) {
662: if (_agent != null)
663: _agent.log(message, throwable);
664: }
665:
666: /**
667: * Return the request URI (with the corresponding query string, if any)
668: * from the saved request so that we can redirect to it.
669: *
670: * @param session Our current session
671: */
672: private String savedRequestURL(Session session) {
673:
674: SavedRequest saved = (SavedRequest) session
675: .getNote(org.apache.catalina.authenticator.Constants.FORM_REQUEST_NOTE);
676: if (saved == null)
677: return (null);
678: StringBuffer sb = new StringBuffer(saved.getRequestURI());
679: if (saved.getQueryString() != null) {
680: sb.append('?');
681: sb.append(saved.getQueryString());
682: }
683: return (sb.toString());
684:
685: }
686:
687: }
|