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