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