001 package edu.nrao.sss.webapp; 002 003 import javax.servlet.*; 004 import javax.servlet.http.*; 005 import java.io.*; 006 import java.net.URLEncoder; 007 import java.util.*; 008 import javax.xml.xpath.XPathExpressionException; 009 import org.xml.sax.SAXException; 010 import javax.xml.parsers.ParserConfigurationException; 011 012 import org.apache.log4j.Logger; 013 014 /** 015 * AuthFilter redirects users to login with the User DB before proceeding with 016 * their query (if they haven't logged in yet). 017 * <p> 018 * When a request is received, the session is retrieved or created if it 019 * doesn't exist. If there is a edu.nrao.sss.webapp.User object in the session 020 * that has a name equal to {@link #userAttributeName}, then our query is 021 * passed through unaltered. If the user object doesn't exist, the filter looks 022 * to see if there is a paramter in the request query equal to {@link #AUTH_TOKEN}. 023 * If so, we create a user from the authentication token provided and store it 024 * in the session. If the authentication token is invalid, an exception is thrown. 025 * <p> 026 * If AUTH_TOKEN was not found in the request parameters and there is no user 027 * object, then the user needs to log in. We redirect to 028 * the address specified in {@link #loginPage}. 029 * <p> 030 * The only exception to the behavior described above is when the original request 031 * parameters include a parameter that ends with "LOGOUT", in which case the current 032 * session is ivalidated (as are the cookies) and the user is redirected to the 033 * user db again with an address of <code>userDB + "?action=returnUser&tok=" + 034 * authenticationToken</code>. 035 * 036 * If an extraneous or unnecessary AUTH_TOKEN query parameter is in the url, 037 * it is removed, and the user is redirected to remove the token from his/her 038 * browser url. 039 */ 040 public class AuthFilter implements Filter 041 { 042 private static final Logger log = Logger.getLogger(AuthFilter.class); 043 044 private static final Logger loginLog = Logger.getLogger("AuthFilter.Login"); 045 046 /** Name of the Fitler param used in web.xml to specify the userdb url*/ 047 private static final String USER_DB = "UserDbUrl"; 048 049 /** The base url name for the user data base. The default is 050 * "http://webtest.aoc.nrao.edu/userdb" 051 */ 052 protected String userDB; 053 054 055 056 /** Name of the Fitler param used in web.xml to specify the attribute name 057 * in the session object that the User object is stored under. 058 * 059 * @see User 060 */ 061 private static final String SESSION_USER = "UserAttributeName"; 062 063 /** The name of the attribute that will be put in the session that holds the 064 * User object. The default is "sessionUser" 065 * 066 * @see User 067 */ 068 protected static String userAttributeName; 069 070 071 /** The name of the Session attribute holding a copy of the request query parameters. */ 072 private static final String QUERY_CACHE_NAME = "RequestQueryCache"; 073 074 /** The name of the query parameter to retrieve the auth token. */ 075 protected static final String AUTH_TOKEN = "authenticationToken"; 076 077 /** 078 * set equal to this.userDB + "?action=login&returnAddress=" 079 */ 080 protected String loginPage; 081 082 /** 083 * This method initializes the filter from param values in the web.xml 084 * config file. 085 * 086 * @see Filter#init 087 */ 088 public void init(FilterConfig conf) throws ServletException 089 { 090 ServletContext ctx = conf.getServletContext(); 091 this.userDB = conf.getInitParameter(USER_DB); 092 if (this.userDB == null) 093 { 094 //Try to find it in the ServletContext. 095 this.userDB = ctx.getInitParameter(USER_DB); 096 097 //Assign a default if still not found. 098 if (this.userDB == null) 099 this.userDB = "http://webtest.aoc.nrao.edu/userdb"; 100 } 101 102 this.loginPage = this.userDB + "?action=login&returnAddress="; 103 104 userAttributeName = conf.getInitParameter(SESSION_USER); 105 if (userAttributeName == null) 106 { 107 //Try to find it in the ServletContext. 108 userAttributeName = ctx.getInitParameter(SESSION_USER); 109 110 //Assign a default if still not found. 111 if (userAttributeName == null) 112 userAttributeName = "sessionUser"; 113 } 114 } 115 116 /** 117 * @return the name of the HttpSession attribute that the User object is 118 * stored under. 119 */ 120 public static String getUserAttributeName() 121 { 122 return userAttributeName; 123 } 124 125 /** 126 * This method checks to see if request has a valid session and it has a 127 * User object in the session. If not, it redirects the user to the userDB. 128 * @see #AuthFilter 129 * @see Filter#doFilter 130 */ 131 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 132 throws IOException, ServletException 133 { 134 HttpServletRequest req = (HttpServletRequest)request; 135 HttpServletResponse res = (HttpServletResponse)response; 136 137 //This shortcut is added here at the top to check if the user is logging out. 138 for (Enumeration e = req.getParameterNames(); e.hasMoreElements();) 139 { 140 String name = (String)e.nextElement(); 141 if (name.endsWith("LOGOUT")) 142 { 143 HttpSession s = req.getSession(false); 144 145 String udb = this.userDB; 146 if (s != null) 147 { 148 User u = (User)s.getAttribute(userAttributeName); 149 150 s.invalidate(); 151 152 Cookie[] cookies = req.getCookies(); 153 if (cookies != null) 154 { 155 for (Cookie cookie : cookies) 156 cookie.setMaxAge(0); 157 } 158 159 if (u != null) 160 { 161 String tok = u.getAuthToken(); 162 163 if (tok != null && tok.length() > 0) 164 { 165 udb = this.userDB + "?action=returnUser&tok=" + URLEncoder.encode(tok, "UTF-8"); 166 } 167 168 loginLog.info( 169 "(SessionId: " + s.getId() + ") " + 170 "User " + u.getUserId() + " has logged out." 171 ); 172 } 173 } 174 175 String url = res.encodeRedirectURL(udb); 176 177 res.sendRedirect(url); 178 return; 179 } 180 } 181 182 //Check to see if we have a session at all. If not, create one. 183 HttpSession session = req.getSession(true); 184 185 //Check if we have an object in the session called 186 //userAttributeName 187 User user; 188 189 try 190 { 191 user = (User)session.getAttribute(userAttributeName); 192 } 193 catch(ClassCastException cce) 194 { 195 user = null; 196 } 197 198 if (user == null) 199 { 200 //User needs to either log in or finish our login process by getting 201 //user's XML from userdb. 202 203 String authTok = req.getParameter(AUTH_TOKEN); 204 if (authTok == null) 205 { 206 //The user hasn't logged in yet. 207 log.debug("User needs to log in, redirecting."); 208 209 //Need to save query params here so we can put them back in the url 210 //after login. 211 cacheQueryInSession(req); 212 213 redirectToLogin(req, res); 214 } 215 216 else 217 { 218 log.debug("User has authToken, but we need to rebuild url & redirect to final destination."); 219 220 //The user already logged in, but we haven't built a user object 221 //from the xml query yet. 222 try 223 { 224 user = new User(authTok, this.userDB); 225 //put our user in the session 226 session.setAttribute(userAttributeName, user); 227 228 StringBuffer loc = req.getRequestURL(); 229 230 //Need to fetch query params and add to loc here. 231 boolean addedParams = false; 232 Object params = session.getAttribute(QUERY_CACHE_NAME); 233 234 if (params != null && params instanceof Map) 235 { 236 String query = createQueryString((Map)params); 237 if (query.length() > 0) 238 { 239 addedParams = true; 240 loc.append(query); 241 } 242 session.removeAttribute(QUERY_CACHE_NAME); 243 } 244 245 //Make sure any parameters that were passed besides AUTH_TOKEN 246 //are also included in the url. 247 String query = createQueryString(req.getParameterMap()); 248 249 //If we've already added a query string, replace the '?' of this 250 //query string with an '&' 251 if (addedParams) 252 query = query.replaceFirst("\\?", "&"); 253 254 loc.append(query); 255 256 loginLog.info( 257 "(SessionId: " + session.getId() + ") " + 258 "User " + user.getUserId() + " has logged in to access '" + loc + "'." 259 ); 260 261 res.sendRedirect(res.encodeRedirectURL(loc.toString())); 262 } catch ( XPathExpressionException e) { throw new ServletException(e); 263 } catch ( SAXException e) { throw new ServletException(e); 264 } catch ( IOException e) { throw new ServletException(e); 265 } catch (ParserConfigurationException e) { throw new ServletException(e); 266 } 267 } 268 } 269 270 else 271 { 272 String authTok = req.getParameter(AUTH_TOKEN); 273 if (authTok == null) 274 { 275 log.debug("User has already logged in, granting access."); 276 277 //Just forward the request on. 278 chain.doFilter(req, res); 279 } 280 281 else 282 { 283 //redirect to clear authTok from the query string. Make sure we 284 //keep all other query params in the query string. 285 StringBuffer loc = req.getRequestURL(); 286 287 String query = createQueryString(req.getParameterMap()); 288 loc.append(query); 289 290 res.sendRedirect(res.encodeRedirectURL(loc.toString())); 291 } 292 } 293 } 294 295 /** 296 * Forwards to the user db appending an appropriately encoded return address. 297 */ 298 private void redirectToLogin(HttpServletRequest req, HttpServletResponse res) 299 throws IOException, ServletException 300 { 301 StringBuffer retAddr = req.getRequestURL(); 302 String encodedRetAddr = java.net.URLEncoder.encode(retAddr.toString(), "UTF-8"); 303 304 res.sendRedirect( 305 res.encodeRedirectURL(this.loginPage + encodedRetAddr) 306 ); 307 } 308 309 /** 310 * Caches all query parameters in the HttpSession associated with this 311 * request for later retrieval. 312 */ 313 private void cacheQueryInSession(HttpServletRequest req) 314 { 315 HttpSession s = req.getSession(true); 316 s.setAttribute(QUERY_CACHE_NAME, req.getParameterMap()); 317 } 318 319 /** 320 * Creates a query string from <code>queryParams</code>. 321 * @param queryParams A Map of query Parameters with String keys and String[] values. 322 * @return A url query string with all the values in queryParams EXCEPT 323 * {@link #AUTH_TOKEN}. If queryParams is empty, returns an empty string. 324 */ 325 private String createQueryString(Map queryParams) 326 throws IOException 327 { 328 StringBuilder queryStr = new StringBuilder("?"); 329 for (Object key : queryParams.keySet()) 330 { 331 //Don't put auth token back in. 332 if (!AUTH_TOKEN.equals(key)) 333 { 334 String keyStr = (String)key; 335 String[] vals = (String[])queryParams.get(key); 336 for (String val : vals) 337 { 338 String encodedVal = URLEncoder.encode(val, "UTF-8"); 339 queryStr.append(keyStr).append("=").append(encodedVal).append("&"); 340 } 341 } 342 } 343 344 //If no parameters were added, return an empty string 345 if (queryStr.length() == 1) 346 return ""; 347 348 else 349 { 350 //Remove the trailing "&" 351 queryStr.deleteCharAt(queryStr.length() - 1); 352 return queryStr.toString(); 353 } 354 } 355 356 /** 357 * This method is currently empty 358 * @see Filter#destroy 359 */ 360 public void destroy() {} 361 }