View Javadoc

1   package com.atlassian.seraph.auth;
2   
3   import java.io.IOException;
4   import java.security.Principal;
5   import java.util.Iterator;
6   import java.util.List;
7   import java.util.Map;
8   
9   import javax.servlet.http.Cookie;
10  import javax.servlet.http.HttpServletRequest;
11  import javax.servlet.http.HttpServletResponse;
12  
13  import org.apache.log4j.Logger;
14  
15  import com.atlassian.seraph.config.SecurityConfig;
16  import com.atlassian.seraph.config.SecurityConfigFactory;
17  import com.atlassian.seraph.cookie.CookieFactory;
18  import com.atlassian.seraph.cookie.CookieHandler;
19  import com.atlassian.seraph.interceptor.LogoutInterceptor;
20  import com.atlassian.seraph.util.RedirectUtils;
21  import com.opensymphony.user.EntityNotFoundException;
22  import com.opensymphony.user.User;
23  import com.opensymphony.user.UserManager;
24  import com.opensymphony.user.provider.ejb.util.Base64;
25  
26  /**
27   * This authenticator stores the currently logged in user in the session as OSUser User objects. <p/> It also provides
28   * for cookie logins and creates cookies if needed. <p/> Includes code from Jive 1.2.4 (released under the Apache
29   * license)
30   */
31  public class DefaultAuthenticator extends AbstractAuthenticator
32  {
33      /**
34       * The key used to store the user object in the session
35       */
36      public static final String LOGGED_IN_KEY = "seraph_defaultauthenticator_user";
37  
38      /**
39       * The key used to indicate that the user has logged out and session regarding of it containing a cookie is not
40       * logged in.
41       */
42      public static final String LOGGED_OUT_KEY = "seraph_defaultauthenticator_logged_out_user";
43  
44      private static final Logger log = Logger.getLogger(DefaultAuthenticator.class);
45  
46      // --------------------------------------------------------------------------------------------------------- members
47  
48      private String loginCookieKey;
49      private String authType;
50      private int autoLoginCookieAge;
51      private String loginCookiePath;
52  
53      public void init(Map params, SecurityConfig config)
54      {
55          if (log.isDebugEnabled())
56          {
57              log.debug(this.getClass().getName() + " $Revision: 16581 $ initializing");
58          }
59          super.init(params, config);
60          this.loginCookieKey = config.getLoginCookieKey();
61          this.authType = config.getAuthType();
62          this.autoLoginCookieAge = config.getAutoLoginCookieAge();
63          this.loginCookiePath = config.getLoginCookiePath();
64      }
65  
66      /**
67       * @deprecated Use {@link RoleMapper} directly
68       */
69      public boolean isUserInRole(HttpServletRequest request, String role)
70      {
71          return getRoleMapper().hasRole(getUser(request), request, role);
72      }
73  
74      /**
75       * Tries to authenticate a user (via OSUser). If successful, sets a session attribute and cookie indicating their
76       * logged-in status.
77       *
78       * @return Whether the user was authenticated. This base implementation returns false if any errors occur, rather
79       *         than throw an exception.
80       */
81      public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)
82              throws AuthenticatorException
83      {
84          final boolean dbg = log.isDebugEnabled();
85          final Principal user = getUser(username);
86          CookieHandler cookieHandler = CookieFactory.getCookieHandler();
87  
88          // check that they can login (they have the USE permission or ADMINISTER permission)
89          if (user == null)
90          {
91              log.info("Cannot login user '" + username + "' as they do not exist.");
92          }
93          else
94          {
95              boolean authenticated = authenticate(user, password);
96              if (dbg)
97              {
98                  log.debug("User : " + username + " has " + (authenticated ? "been" : "no been") + " authenticated");
99              }
100             if (authenticated)
101             {
102                 request.getSession().setAttribute(LOGGED_IN_KEY, user);
103                 request.getSession().setAttribute(LOGGED_OUT_KEY, null);
104 
105                 final boolean canLogin = getRoleMapper().canLogin(user, request);
106                 if (dbg)
107                 {
108                     log.debug("User : " + username + " " + (canLogin ? "can" : "CANT") + " login according to the RoleMapper");
109                 }
110                 if (canLogin)
111                 {
112                     if (cookie && response != null)
113                     {
114                         cookieHandler.setCookie(request, response, getLoginCookieKey(), encodeCookie(username, password), autoLoginCookieAge, getCookiePath(request));
115                     }
116                     return true;
117                 }
118                 else
119                 {
120                     request.getSession().removeAttribute(LOGGED_IN_KEY);
121                 }
122             }
123             else
124             {
125                 log.info("Cannot login user '" + username + "' as they used an incorrect password");
126             }
127         }
128 
129         if (response != null && cookieHandler.getCookie(request, getLoginCookieKey()) != null)
130         {
131             log.warn("User: " + username + " tried to login but they do not have USE permission or weren't found. Deleting cookie.");
132 
133             try
134             {
135                 cookieHandler.invalidateCookie(request, response, getLoginCookieKey(), getCookiePath(request));
136             }
137             catch (Exception e)
138             {
139                 log.error("Could not invalidate cookie: " + e, e);
140             }
141         }
142 
143         return false;
144     }
145 
146     // override this method if you need to retrieve the role mapper from elsewhere than the singleton-factory (injected
147     // depency for instance)
148     protected RoleMapper getRoleMapper()
149     {
150         return SecurityConfigFactory.getInstance().getRoleMapper();
151     }
152 
153     // The following two methods are the only OSUser-specific parts of this class
154 
155     /**
156      * Uses OSUser to retrieve a Principal for a given username. Returns null if no user exists.
157      */
158     protected Principal getUser(String username)
159     {
160         if (log.isDebugEnabled())
161         {
162             log.debug("Looking in UserManager for user : " + username);
163         }
164         try
165         {
166             return UserManager.getInstance().getUser(username);
167         }
168         catch (EntityNotFoundException e)
169         {
170             log.warn("Could not find user : " + username + " in UserManager : " + e);
171         }
172         return null;
173     }
174 
175     /**
176      * Uses OSUser's authenticate() to authenticate a user.
177      */
178     protected boolean authenticate(Principal user, String password)
179     {
180         return ((User) user).authenticate(password);
181     }
182 
183     public boolean logout(HttpServletRequest request, HttpServletResponse response) throws AuthenticatorException
184     {
185         boolean dbg = log.isDebugEnabled();
186         if (dbg)
187         {
188             log.debug("logout requested.  Calling interceptors and clearing cookies");
189         }
190         List interceptors = getLogoutInterceptors();
191         CookieHandler cookieHandler = CookieFactory.getCookieHandler();
192 
193         for (Iterator iterator = interceptors.iterator(); iterator.hasNext();)
194         {
195             LogoutInterceptor interceptor = (LogoutInterceptor) iterator.next();
196             interceptor.beforeLogout(request, response);
197         }
198 
199         request.getSession().setAttribute(LOGGED_IN_KEY, null);
200         request.getSession().setAttribute(LOGGED_OUT_KEY, Boolean.TRUE);
201 
202         // Logout is sometimes called as part of a getUser request, if the user is not found
203         // logout may be called, but some getUser calls only pass in the request, and null response.
204         if (response != null && cookieHandler.getCookie(request, getLoginCookieKey()) != null)
205         {
206             try
207             {
208                 cookieHandler.invalidateCookie(request, response, getLoginCookieKey(), getCookiePath(request));
209             }
210             catch (Exception e)
211             {
212                 log.error("Could not invalidate cookie: " + e, e);
213             }
214         }
215 
216         for (Iterator iterator = interceptors.iterator(); iterator.hasNext();)
217         {
218             LogoutInterceptor interceptor = (LogoutInterceptor) iterator.next();
219             interceptor.afterLogout(request, response);
220         }
221 
222         return true;
223     }
224 
225     /**
226      * Returns the currently logged in user, trying in order: <p/>
227      * <ol>
228      * <li>Session, only if one exists</li>
229      * <li>Cookie, only if no session exists</li>
230      * <li>Basic authentication, if the above fail, and authType=basic</li>
231      * </ol>
232      * <p/> Warning: only in the case of cookie and basic auth will the user be authenticated.
233      *
234      * @param response a response object that may be modified if basic auth is enabled
235      * @return a Principal object for the user if found, otherwise null
236      */
237     public Principal getUser(HttpServletRequest request, HttpServletResponse response)
238     {
239 	    final boolean dbg = log.isDebugEnabled();
240 	    if (request.getSession(false) != null)
241         {
242             Principal sessionUser = getUserFromSession(request);
243             if (sessionUser != null)
244             {
245 	            if (dbg)
246     	        {
247         	        log.debug("Session found; BUT user doesn't exist");
248             	}            	
249              	return sessionUser;
250             }
251         }
252         else
253         {
254             Principal cookieUser = getUserFromCookie(request, response);
255             if (cookieUser != null) return cookieUser;
256             log.debug("Cannot log user in via a cookie");
257         }
258 
259         if (RedirectUtils.isBasicAuthentication(request, authType))
260         {
261             Principal basicAuthUser = getUserFromBasicAuthentication(request, response);
262             if (basicAuthUser != null) return basicAuthUser;
263         }
264 
265 		if (dbg)
266 		{
267 			log.debug("User not logged in.");
268 		}
269 
270         return null;
271     }
272 
273     /**
274      * Extracts the username and password from the cookie and calls login to authenticate, and if successful store the
275      * token in the session.
276      *
277      * @return a Principal object for the user if successful, otherwise null
278      */
279     protected Principal getUserFromCookie(HttpServletRequest request, HttpServletResponse response)
280     {
281         final boolean dbg = log.isDebugEnabled();
282         final String cookieName = getLoginCookieKey();
283         final Cookie cookie = CookieFactory.getCookieHandler().getCookie(request, cookieName);
284         if (cookie == null)
285         {
286             return null;
287         }
288 
289         final String cookieValue = cookie.getValue();
290         if (dbg)
291         {
292             log.debug("Found cookie : '" + cookieName + "' with value : '" + cookieValue + "'");
293         }
294         final String[] values = decodeCookie(cookieValue);
295         if (values == null)
296         {
297             if (dbg)
298             {
299                 log.debug("Unable to decode " + cookieName + " cookie with value : '" + cookieValue + "'");
300             }
301             return null;
302         }
303 
304         final String username = values[0];
305         final String password = values[1];
306         if (dbg)
307         {
308             log.debug("Got username : '" + username + "' and password from cookie, attempting to authenticate user");
309         }
310 
311         try
312         {
313             if (!login(request, response, username, password, false))
314             {
315                 return null;
316             }
317         }
318         catch (Exception e)
319         {
320             log.warn("Cookie login for user : '" + username + "' failed with exception: " + e, e);
321             return null;
322         }
323 
324         if (dbg)
325         {
326             log.debug("Logged user : '" + username + "' in via a cookie");
327         }
328         return getUserFromSession(request);
329     }
330 
331     /**
332      * <p>
333      * Tries to get a logged in user from the session.
334      * </p>
335      *
336      * @param request the current {@link HttpServletRequest}
337      * @return the logged in user in the session. <code>null</code> if there is no logged in user in the session, or
338      *         the {@link #LOGGED_OUT_KEY} is set because the user has logged out.
339      */
340     protected Principal getUserFromSession(HttpServletRequest request)
341     {
342         final boolean dbg = log.isDebugEnabled();
343         try
344         {
345             if (request.getSession().getAttribute(LOGGED_OUT_KEY) != null)
346             {
347             	if (dbg)
348             	{
349                 	log.debug("Session found; user has already logged out");
350                 }
351                 return null;
352             }
353             if (request.getSession().getAttribute(LOGGED_IN_KEY) == null)
354             {
355                 return null;
356             }
357             final Principal principal = (Principal) request.getSession().getAttribute(LOGGED_IN_KEY);
358             if (dbg)
359             {
360                 if (principal == null)
361                 {
362                     log.debug("Session found; BUT it has no Principal in it");
363                 }
364                 else
365                 {
366                     log.debug("Session found; user : '" + principal.getName() + "' already logged in");
367                 }
368             }
369             return principal;
370         }
371         catch (Exception e)
372         {
373             log.warn("Exception when retrieving user from session: " + e, e);
374             return null;
375         }
376     }
377 
378     /**
379      * Checks the Authorization header to see whether basic auth token is provided. If it is, decode it, login and
380      * return the valid user. If it isn't, basic auth is still required, so return a 401 Authorization Required header
381      * in the response.
382      *
383      * @param response a response object that <i>will</i> be modified if no token found
384      */
385     protected Principal getUserFromBasicAuthentication(HttpServletRequest request, HttpServletResponse response)
386     {
387         final boolean dbg = log.isDebugEnabled();
388         String header = request.getHeader("Authorization");
389 
390         if (header != null && header.startsWith("Basic "))
391         {
392             if (dbg)
393             {
394                 log.debug("Looking in Basic Auth headers");
395             }
396             String base64Token = header.substring(6);
397             String token = new String(Base64.decode(base64Token.getBytes()));
398 
399             String username = "";
400             String password = "";
401 
402             int delim = token.indexOf(":");
403 
404             if (delim != -1)
405             {
406                 username = token.substring(0, delim);
407                 password = token.substring(delim + 1);
408             }
409 
410             try
411             {
412                 if (login(request, response, username, password, false))
413                 {
414                     if (dbg)
415                     {
416                         log.debug("Logged in user : '" + username + "' via basic auth");
417                     }
418                     return getUser(username);
419                 }
420             }
421             catch (AuthenticatorException e)
422             {
423                 log.warn("Exception trying to login user : '" + username + "' via basic auth:" + e, e);
424             }
425             try
426             {
427                 response.sendError(401);
428             }
429             catch (IOException e)
430             {
431                 log.warn("Exception trying to send basic auth failed error: " + e, e);
432             }
433             return null;
434         }
435 
436         if (response == null)
437         {
438             return null;
439         }
440 
441         response.setStatus(401);
442         response.setHeader("WWW-Authenticate", "BASIC realm=\"protected-area\"");
443         return null;
444     }
445 
446     /**
447      * Root the login cookie at the same location as the webapp. <p/> Anyone wanting a different cookie path policy can
448      * override the authenticator and provide one.
449      */
450     protected String getCookiePath(HttpServletRequest request)
451     {
452         if (getLoginCookiePath() != null)
453             return getLoginCookiePath();
454 
455         String path = request.getContextPath();
456         if (path == null || path.equals(""))
457         {
458             return "/";
459         }
460 
461         // The spec says this should never happen, but just to be sure...
462         if (!path.startsWith("/"))
463         {
464             return "/" + path;
465         }
466 
467         return path;
468     }
469 
470     protected String getLoginCookieKey()
471     {
472         return loginCookieKey;
473     }
474 
475     public String getAuthType()
476     {
477         return authType;
478     }
479 
480     protected List getLogoutInterceptors()
481     {
482         return getConfig().getInterceptors(LogoutInterceptor.class);
483     }
484 
485     protected String encodeCookie(final String username, final String password)
486     {
487         return CookieFactory.getCookieEncoder().encodePasswordCookie(username, password, getConfig().getCookieEncoding());
488     }
489 
490     protected String[] decodeCookie(final String value)
491     {
492         return CookieFactory.getCookieEncoder().decodePasswordCookie(value, getConfig().getCookieEncoding());
493     }
494 
495     protected String getLoginCookiePath()
496     {
497         return loginCookiePath;
498     }
499 }