View Javadoc

1   package org.itracker.web.filters;
2   
3   import java.io.IOException;
4   import java.util.HashSet;
5   import java.util.Iterator;
6   import java.util.Locale;
7   import java.util.Map;
8   import java.util.Set;
9   import java.util.StringTokenizer;
10  import java.util.regex.Pattern;
11  
12  import javax.servlet.Filter;
13  import javax.servlet.FilterChain;
14  import javax.servlet.FilterConfig;
15  import javax.servlet.ServletException;
16  import javax.servlet.ServletRequest;
17  import javax.servlet.ServletResponse;
18  import javax.servlet.http.HttpServletRequest;
19  import javax.servlet.http.HttpServletResponse;
20  import javax.servlet.http.HttpSession;
21  
22  import org.apache.log4j.Logger;
23  import org.apache.struts.Globals;
24  import org.apache.struts.action.ActionForward;
25  import org.apache.struts.action.ActionMessage;
26  import org.apache.struts.action.ActionMessages;
27  import org.itracker.core.resources.ITrackerResources;
28  import org.itracker.model.PermissionType;
29  import org.itracker.model.User;
30  import org.itracker.services.ConfigurationService;
31  import org.itracker.services.ITrackerServices;
32  import org.itracker.services.util.UserUtilities;
33  import org.itracker.web.util.Constants;
34  import org.itracker.web.util.LoginUtilities;
35  import org.itracker.web.util.RequestHelper;
36  import org.itracker.web.util.ServletContextUtils;
37  import org.itracker.web.util.SessionManager;
38  
39  /**
40   * Configurations:
41   * <ul>
42   * <li>AuthExcludedPaths: Comma separated list of Regex url-Patterns (eg.
43   * <code>/login.do, /unprotected-path/**</code>)</li>
44   * </ul>
45   * 
46   * @author ranks
47   * 
48   */
49  public class ExecuteAlwaysFilter implements Filter {
50  
51  	/**
52  	 * Logger for ExecuteAlwaysFilter
53  	 */
54  	private static final Logger log = Logger
55  			.getLogger(ExecuteAlwaysFilter.class);
56  	private static final String DEFAULT_LOGIN_FORWARD = "/login.do";
57  	/**
58  	 * Name for session key for forward after successful authentication.
59  	 */
60  	private static final String SES_KEY_REDIRECT_ON_SUCCESS = ExecuteAlwaysFilter.class
61  			.getName()
62  			+ "/REDIRECT_ON_SUCCESS";
63  
64  	private ITrackerServices iTrackerServices;
65  	/**
66  	 * this match paths which are not protected
67  	 */
68  	private Set<Pattern> unprotectedPaterns = null;
69  
70  	private String loginForwardPath;
71  
72  	public void destroy() {
73  		this.unprotectedPaterns = null;
74  	}
75  
76  	public void doFilter(ServletRequest servletRequest,
77  			ServletResponse response, FilterChain chain) throws IOException,
78  			ServletException {
79  		if (null == this.unprotectedPaterns) {
80  			RuntimeException re = new IllegalStateException(
81  					"Filter has not been initialized yet.");
82  			log.error("doFilter: failed, not initialized", re);
83  			throw re;
84  		}
85  
86  		if (!(servletRequest instanceof HttpServletRequest)) {
87  			RuntimeException re = new IllegalArgumentException(
88  					"Usupported servlet-request of type: "
89  							+ servletRequest.getClass().getName());
90  			log.error("doFilter: failed, invalid request type", re);
91  			throw re;
92  		}
93  
94  		HttpServletRequest request = (HttpServletRequest) servletRequest;
95  
96  		String path = request.getRequestURI().substring(
97  				request.getContextPath().length());
98  		if (log.isDebugEnabled()) {
99  			log.debug("doFilter: called with path " + path);
100 		}
101 
102 		// From IrackerBaseAction.executeAlways
103 		if (log.isDebugEnabled()) {
104 			log
105 					.debug("doFilter: setting the common request attributes, (coming from the former header.jsp)");
106 		}
107 		ConfigurationService configurationService = getITrackerServices()
108 				.getConfigurationService();
109 
110 		boolean protect = isProtected(path, this.unprotectedPaterns);
111 
112 		// do not protect the login-page itself.
113 		if (protect && this.loginForwardPath.equals(path)) {
114 			protect = false;
115 		}
116 
117 		if (log.isDebugEnabled()) {
118 			log.debug("doFilter: protecting '" + path + "': " + protect);
119 		}
120 
121 		
122 		
123 		User currUser = LoginUtilities.getCurrentUser(request);
124 
125 		if (null == currUser && protect) {
126 //			check for autologin
127 			if (LoginUtilities.checkAutoLogin(request, configurationService.getBooleanProperty(
128 					"allow_save_login", true))) {
129 
130 				String login = String.valueOf(request.getAttribute(Constants.AUTH_LOGIN_KEY));
131 				currUser = LoginUtilities.setupSession(login, request, (HttpServletResponse)response);
132 				
133 				try {
134 					SessionManager.createSession(login);
135 				} catch (Exception e) {
136 					handleError(e, request, response);
137 				}
138 				
139 			}
140 		}
141 
142 		setupCommonReqAttributes(request, configurationService);
143 		
144 		if (null != currUser) {
145 			if (log.isDebugEnabled()) {
146 				log.debug("doFilter: found user in session");
147 			}
148 			String currLogin = currUser.getLogin();
149 			
150 			log.info("Login found...: " + currLogin);
151 			if (SessionManager.getSessionNeedsReset(currLogin)) {
152 				// RESET THE SESSION STUFF
153 				HttpSession session = request.getSession();
154 				log.info("Resetting the Session stuff...");
155 				session.removeAttribute(Constants.USER_KEY);
156 				session.removeAttribute(Constants.PERMISSIONS_KEY);
157 				currUser = null;
158 				String newLogin = SessionManager.checkRenamedLogin(currLogin);
159 				if (response instanceof HttpServletResponse) {
160 					currUser = LoginUtilities.setupSession((newLogin == null ? currLogin
161 							: newLogin), request, (HttpServletResponse)response);
162 				}
163 				SessionManager.removeRenamedLogin(currLogin);
164 				if (currUser == null
165 						|| currUser.getStatus() != UserUtilities.STATUS_ACTIVE) {
166 					ActionMessages errors = new ActionMessages();
167 					errors.add(ActionMessages.GLOBAL_MESSAGE,
168 							new ActionMessage(
169 									"itracker.web.error.login.inactive"));
170 					saveErrors(request, errors);
171 //					request.setAttribute(Globals.ERROR_KEY, errors);
172 					
173 					log.info("doFilter: forwarding to login");
174 					forwardToLogin(path
175 							+ (request.getQueryString() != null ? "?"
176 									+ request.getQueryString() : ""), request,
177 							(HttpServletResponse) response);
178 				}
179 			}
180 			
181 			request.setAttribute("currLogin", currLogin);
182 		} else if (!protect) {
183 			// request.setAttribute("permissions", permissions);
184 			// TODO: itracker.web.generic.unknown for unknown user?
185 			request.setAttribute("currLogin", ITrackerResources
186 					.getString("itracker.web.header.guest"));
187 		} else {
188 			// unauthenticated.. forward to login
189 			log.info("doFilter: forwarding to login");
190 			forwardToLogin(path
191 					+ (request.getQueryString() != null ? "?"
192 							+ request.getQueryString() : ""), request,
193 					(HttpServletResponse) response);
194 			return;
195 		}
196 		setupCommonReqAttributesEx(request);
197 		try {
198 			if (log.isDebugEnabled()) {
199 				log.info("doFilter: executing chain..");
200 			}
201 			chain.doFilter(request, response);
202 
203 			if (log.isDebugEnabled()) {
204 				log.info("doFilter: completed chain execution.");
205 			}
206 		} catch (RuntimeException e) {
207 			log.error(
208 					"doFilter: failed to execute chain with runtime exception: "
209 							+ e.getMessage(), e);
210 			handleError(e, request, response);
211 
212 		} catch (IOException ioe) {
213 			log.error("doFilter: failed to execute chain with i/o exception: "
214 					+ ioe.getMessage(), ioe);
215 			handleError(ioe, request, response);
216 
217 		} catch (ServletException se) {
218 			log.error(
219 					"doFilter: failed to execute chain with servlet exception: "
220 							+ se.getMessage(), se);
221 			handleError(se, request, response);
222 
223 		} catch (Error err) {
224 			log.fatal("doFilter: caught fatal error executing filter chain",
225 					err);
226 			throw err;
227 		}
228 	}
229 
230 	private final void handleError(Throwable error, ServletRequest request, ServletResponse response) throws ServletException {
231 
232 		if (null == error) {
233 			log.info("handleError: called with null throwable");
234 			error = new RuntimeException(error);
235 		}
236 		
237 		log.info("handleError: called with " + error.getClass().getSimpleName(), error);
238 		
239 		if (!(response instanceof HttpServletResponse) || !(request instanceof HttpServletRequest)) {
240 			log.error("handleError: unknown request/response: " + request + ", " + response, error);
241 			throw new ServletException(error.getMessage(), error);
242 		}
243 		HttpServletRequest httpRequest = (HttpServletRequest) request;
244 		HttpServletResponse httpResponse = (HttpServletResponse) response;
245 		
246 		ActionMessages errors = new ActionMessages();
247 		errors.add(ActionMessages.GLOBAL_MESSAGE, new ActionMessage("itracker.web.error.system.message",
248 				new Object[]{error.getLocalizedMessage() == null ? error.getMessage() : error.getLocalizedMessage(),
249 						error.getClass().getCanonicalName()}));
250 		
251 		saveErrors((HttpServletRequest)request, errors);
252 		try {
253 //			response.sendError(500, "Internal Server Error");
254 			httpResponse.sendRedirect(httpRequest.getContextPath() + "/error.do");
255 		} catch (IOException e) {
256 			log.fatal("handleError: failed to redirect to error-page");
257 			return;
258 		}
259 	}
260 
261     /**
262      * <p>Save the specified error messages keys into the appropriate request
263      * attribute for use by the &lt;html:errors&gt; tag, if any messages
264      * are required. Otherwise, ensure that the request attribute is not
265      * created.</p>
266      *
267      * @param request The servlet request we are processing
268      * @param errors Error messages object
269      * @since Struts 1.2
270      */
271     protected void saveErrors(HttpServletRequest request, ActionMessages errors) {
272 
273         // Remove any error messages attribute if none are required
274         if ((errors == null) || errors.isEmpty()) {
275             request.removeAttribute(Globals.ERROR_KEY);
276             request.getSession().removeAttribute(Globals.ERROR_KEY);
277             return;
278         }
279 
280         if (log.isInfoEnabled()) {
281         	log.info("saveErrors: saved errors: " + errors);
282         }
283         // Save the error messages we need
284         request.setAttribute(Globals.ERROR_KEY, errors);
285 
286         request.getSession().setAttribute(Globals.ERROR_KEY, errors);
287     }
288 
289 	
290 	private static final void setupCommonReqAttributes(
291 			HttpServletRequest request,
292 			ConfigurationService configurationService) {
293 		boolean allowForgotPassword = true;
294 		boolean allowSelfRegister = false;
295 		boolean allowSaveLogin = true;
296 		String alternateLogo = null;
297 
298 		allowForgotPassword = configurationService.getBooleanProperty(
299 				"allow_forgot_password", true);
300 		allowSelfRegister = configurationService.getBooleanProperty(
301 				"allow_self_register", false);
302 		allowSaveLogin = configurationService.getBooleanProperty(
303 				"allow_save_login", true);
304 		alternateLogo = configurationService
305 				.getProperty("alternate_logo", null);
306 		Locale locale = LoginUtilities.getCurrentLocale(request);
307 
308 		// TODO: this should be configured per-instance. Request server-name
309 		// should only be used for exception and logged (configuration not
310 		// found!)
311 		
312 		String baseURL = configurationService.getSystemBaseURL();
313 		if (null == baseURL) {
314 			baseURL = request.getScheme() + "://" + request.getServerName()
315 				+ ":" + request.getServerPort() + request.getContextPath();
316 			log.warn("setupCommonReqAttributes: not found system_base_url configuration, setting from request: " + baseURL);
317 		}
318 		request.setAttribute("allowForgotPassword", Boolean
319 				.valueOf(allowForgotPassword));
320 		request.setAttribute("allowSelfRegister", Boolean
321 				.valueOf(allowSelfRegister));
322 		request.setAttribute("allowSaveLogin", Boolean.valueOf(allowSaveLogin));
323 		request.setAttribute("alternateLogo", alternateLogo);
324 		request.setAttribute("baseURL", baseURL);
325 		// TODO: remove deprecated currLocale attribute
326 		request.setAttribute("currLocale", locale);
327 		
328 		// set a default page-title key
329 		request.setAttribute("pageTitleKey", "itracker.web.generic.itracker");
330 		request.setAttribute("pageTitleArg", "");
331 
332 
333         request.setAttribute("locales", configurationService.getAvailableLanguages());
334 
335 
336 		request.setAttribute(Constants.LOCALE_KEY, locale);
337 		
338 		
339 	}
340 
341 	private static final void setupCommonReqAttributesEx(HttpServletRequest request) {
342 		final Map<Integer, Set<PermissionType>> permissions = RequestHelper
343 		.getUserPermissions(request.getSession());
344 		request.setAttribute("hasPermissionUserAdmin", UserUtilities.hasPermission(permissions,
345 				UserUtilities.PERMISSION_USER_ADMIN));
346 		request.setAttribute("hasPermissionProductAdmin", UserUtilities.hasPermission(permissions,
347 				UserUtilities.PERMISSION_PRODUCT_ADMIN));
348 		request.setAttribute("contextPath", request.getContextPath());
349 
350 		request.setAttribute("currentDate", new java.util.Date());
351 	}
352 
353 	
354 	private static final boolean isProtected(String path, Set<Pattern> patterns) {
355 		if (null == path) {
356 			path = "";
357 		}
358 
359 		Iterator<Pattern> matchPattern = patterns.iterator();
360 		Pattern pattern;
361 
362 		while (matchPattern.hasNext()) {
363 			pattern = matchPattern.next();
364 			if (log.isDebugEnabled()) {
365 				log.debug("isProtected: processing path " + path
366 						+ " for pattern " + pattern.pattern());
367 			}
368 			if (pattern.matcher(path).matches()) {
369 				if (log.isDebugEnabled()) {
370 					log.debug("isProtected: matched path: " + path);
371 				}
372 				return false;
373 			}
374 		}
375 
376 		if (log.isDebugEnabled()) {
377 			log.debug("isProtected: protecting " + path);
378 		}
379 		return true;
380 	}
381 	
382 
383 
384 	/**
385 	 * 
386 	 */
387 	public void init(FilterConfig filterConfig) throws ServletException {
388 		if (null != unprotectedPaterns) {
389 			throw new IllegalStateException(
390 					"Filter was already initialized before.");
391 		}
392 		String excludePaths = filterConfig
393 				.getInitParameter("AuthExcludedPaths");
394 
395 		this.loginForwardPath = filterConfig.getInitParameter("LoginForward");
396 		if (null == this.loginForwardPath) {
397 			this.loginForwardPath = DEFAULT_LOGIN_FORWARD;
398 		}
399 		this.unprotectedPaterns = new HashSet<Pattern>();
400 		if (null != excludePaths) {
401 			StringTokenizer tk = new StringTokenizer(excludePaths, ",");
402 			while (tk.hasMoreTokens()) {
403 				this.unprotectedPaterns.add(Pattern.compile(tk.nextToken().trim()));
404 			}
405 		}
406 		if (log.isInfoEnabled()) {
407 			log.info("init: initialized with " + this.loginForwardPath
408 					+ ", excludes: " + this.unprotectedPaterns);
409 		}
410 	}
411 
412 	public ITrackerServices getITrackerServices() {
413 		if (null == this.iTrackerServices) {
414 
415 			this.iTrackerServices = ServletContextUtils.getItrackerServices();
416 		}
417 		return iTrackerServices;
418 	}
419 
420 	/**
421 	 * 
422 	 * 
423 	 * 
424 	 * @param request
425 	 * @param response
426 	 * @param thisactionforward
427 	 * @return String - outcome
428 	 */
429 	private void forwardToLogin(String path, HttpServletRequest request,
430 			HttpServletResponse response) {
431 		if (log.isDebugEnabled()) {
432 			log.debug("forwardToLogin: called with " + path + " request: "
433 					+ request + " response: " + response);
434 		}
435 		String forwardPath = request.getContextPath() + this.loginForwardPath;
436 		if (log.isDebugEnabled()) {
437 			log
438 					.debug("forwardToLogin: (formerly Checklogin tag) procedure... to "
439 							+ forwardPath);
440 		}
441 		HttpSession session = request.getSession();
442 		try {
443 
444 			log.info("forwardToLogin: setting redirectURL "
445 					+ SES_KEY_REDIRECT_ON_SUCCESS + " = " + path);
446 			session.setAttribute(SES_KEY_REDIRECT_ON_SUCCESS, path);
447 			session.setAttribute("loginForwarded", true);
448 			response.sendRedirect(forwardPath);
449 			response.flushBuffer();
450 
451 		} catch (Exception e) {
452 			log.error("forwardToLogin: IOException while checking login", e);
453 			response.reset();
454 			try {
455 				session.setAttribute("loginForwarded", Boolean.TRUE);
456 				response.sendRedirect(forwardPath);
457 			} catch (IOException e1) {
458 				log.error("forwardToLogin: failed to redirect to "
459 						+ forwardPath, e1);
460 			}
461 		}
462 	}
463 
464 	public static void redirectToOnLoginSuccess(HttpServletRequest request,
465 			HttpServletResponse response) throws IOException {
466 		String path = (String) request.getSession().getAttribute(
467 				SES_KEY_REDIRECT_ON_SUCCESS);
468 		if (null == path) {
469 			path = "/";
470 		}
471 		if (log.isDebugEnabled()) {
472 			log.debug("redirectToOnLoginSuccess: sending redirect to " + path);
473 		}
474 		response.sendRedirect(path);
475 	}
476 
477 	public static ActionForward forwardToOnLoginSuccess(
478 			HttpServletRequest request, HttpServletResponse response)
479 			throws IOException {
480 		String path = (String) request.getSession().getAttribute(
481 				SES_KEY_REDIRECT_ON_SUCCESS);
482 		if (null == path) {
483 			path ="/";
484 		}
485 		if (log.isDebugEnabled()) {
486 			log.debug("redirectToOnLoginSuccess: sending redirect to " + path);
487 		}
488 		return new ActionForward(path, true);
489 	}
490 }