1   package com.atlassian.seraph.filter;
2   
3   import com.atlassian.seraph.auth.AuthenticatorException;
4   import com.atlassian.seraph.auth.LoginReason;
5   import static com.atlassian.seraph.auth.LoginReason.AUTHENTICATION_DENIED;
6   import com.atlassian.seraph.elevatedsecurity.ElevatedSecurityGuard;
7   import com.atlassian.seraph.interceptor.LoginInterceptor;
8   import org.apache.log4j.Category;
9   
10  import java.util.List;
11  import javax.servlet.http.HttpServletRequest;
12  import javax.servlet.http.HttpServletResponse;
13  
14  /**
15   * This is a base filter that logs the user in based on the given username and password. It is designed to be extended
16   * to support schemes that pass username and password one way or another.
17   * <p/>
18   * For further info see superclass.
19   *
20   * @see com.atlassian.seraph.filter.BaseLoginFilter
21   */
22  public abstract class PasswordBasedLoginFilter extends BaseLoginFilter
23  {
24      static final Category log = Category.getInstance(PasswordBasedLoginFilter.class);
25  
26      /**
27       * This implements the login method defined in {@link com.atlassian.seraph.filter.BaseLoginFilter#login(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
28       * and uses extracted user name and password to perform the check
29       * 
30       * @param request the HTTP request in play
31       * @param response the HTTP respone in play
32       *
33       * @return the login status string.
34       *         <p/>
35       * The possible statuses are:
36       * <ul>
37       *  <li> LoginFilter.LOGIN_SUCCESS - the login was processed, and user was logged in</li>
38       *  <li> LoginFilter.LOGIN_FAILURE - the login was processed, the user gave a bad username or password</li>
39       *  <li> LoginFilter.LOGIN_ERROR - the login was processed, an exception occurred trying to log the user in</li>
40       *  <li> LoginFilter.LOGIN_NOATTEMPT - the login was no processed, no form parameters existed</li>
41       * </ul>
42       */
43      public String login(final HttpServletRequest request, final HttpServletResponse response)
44      {
45          final String METHOD = "login : ";
46          final boolean dbg = log.isDebugEnabled();
47  
48          String loginStatus = LOGIN_NOATTEMPT;
49  
50          // check for parameters
51          final UserPasswordPair userPair = extractUserPasswordPair(request);
52          if (userPair == null || userPair.userName == null || userPair.password == null)
53          {
54              if (dbg)
55              {
56                  log.debug(METHOD + "No user name or password was returned. No authentication attempt will be made.  User may still be found via a SecurityFilter later.");
57              }
58              return loginStatus;
59          }
60  
61          if (dbg)
62          {
63              log.debug(METHOD + "'" + userPair.userName + "' and password provided - remember me : " + userPair.persistentLogin + " - attempting login request");
64          }
65  
66          final List<LoginInterceptor> interceptors = getSecurityConfig().getInterceptors(LoginInterceptor.class);
67          try
68          {
69              runBeforeLoginInterceptors(request, response, userPair, interceptors);
70  
71              loginStatus = runAuthentication(request, response, userPair);
72          }
73          catch (final AuthenticatorException e)
74          {
75              if (dbg)
76              {
77                  log.debug(METHOD + "An exception occurred authenticating : '" + userPair.userName + "'", e);
78              }
79              loginStatus = LOGIN_FAILED;
80          }
81          finally
82          {
83              runAfterLoginInterceptors(request, response, loginStatus, userPair, interceptors);
84          }
85          return loginStatus;
86      }
87  
88      /**
89       * This will check to see if an elevated security check is required and then perform it before then asking for the
90       * authenticator and using it to authenticate/authorise the user.
91       *
92       * @param httpServletRequest  the HTTP servlet request in play
93       * @param httpServletResponse the HTTP servlet respone in play
94       * @param userPair            the {@link com.atlassian.seraph.filter.PasswordBasedLoginFilter.UserPasswordPair} in
95       *                            play
96       *
97       * @return the status of the login
98       *
99       * @throws AuthenticatorException if something goes wrong in the authenticator
100      */
101     private String runAuthentication(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final UserPasswordPair userPair) throws AuthenticatorException
102     {
103         final String METHOD = "runAuthentication : ";
104         final boolean dbg = log.isDebugEnabled();
105 
106         ElevatedSecurityGuard securityGuard = getElevatedSecurityGuard();
107 
108         if (! securityGuard.performElevatedSecurityCheck(httpServletRequest, userPair.userName))
109         {
110             if (dbg)
111             {
112                 log.debug(METHOD + "'" + userPair.userName + "' failed elevated security check");
113             }
114             AUTHENTICATION_DENIED.stampRequestResponse(httpServletRequest,httpServletResponse);
115             securityGuard.onFailedLoginAttempt(httpServletRequest, userPair.userName);
116             return LOGIN_FAILED;
117         }
118         if (dbg)
119         {
120             log.debug(METHOD + "'" + userPair.userName + "' does not require elevated security check.  Attempting authentication...");
121         }
122 
123         // ask the authenticator to try to log in the user the us
124         final boolean loggedIn = getAuthenticator().login(httpServletRequest, httpServletResponse, userPair.userName, userPair.password, userPair.persistentLogin);
125         if (dbg)
126         {
127             log.debug(METHOD + "'" + userPair.userName + "' was " + (loggedIn? "successfully" : "UNSUCCESSFULLY") + " authenticated");
128         }
129 
130         if (loggedIn)
131         {
132             LoginReason.OK.stampRequestResponse(httpServletRequest,httpServletResponse);
133             securityGuard.onSuccessfulLoginAttempt(httpServletRequest, userPair.userName);
134         }
135         else
136         {
137             LoginReason.AUTHENTICATED_FAILED.stampRequestResponse(httpServletRequest,httpServletResponse);
138             securityGuard.onFailedLoginAttempt(httpServletRequest, userPair.userName);
139         }
140         return loggedIn ? LOGIN_SUCCESS : LOGIN_FAILED;
141     }
142 
143     private void runBeforeLoginInterceptors(final HttpServletRequest request, final HttpServletResponse response, final UserPasswordPair userPair, final List<LoginInterceptor> interceptors)
144     {
145         for (final LoginInterceptor loginInterceptor : interceptors)
146         {
147             loginInterceptor.beforeLogin(request, response, userPair.userName, userPair.password, userPair.persistentLogin);
148         }
149     }
150 
151     private void runAfterLoginInterceptors(final HttpServletRequest request, final HttpServletResponse response, final String status, final UserPasswordPair userPair, final List<LoginInterceptor> interceptors)
152     {
153         for (final LoginInterceptor loginInterceptor : interceptors)
154         {
155             loginInterceptor.afterLogin(request, response, userPair.userName, userPair.password, userPair.persistentLogin, status);
156         }
157     }
158 
159     /**
160      * Returns a username password pair for this request. If this request does not contain user credentials - returns
161      * null;
162      *
163      * @param request the HTTP request in play
164      * @return user credentials or null
165      */
166     abstract UserPasswordPair extractUserPasswordPair(HttpServletRequest request);
167 
168     /**
169      * Represents a username password pair of user credentials.
170      */
171     public static final class UserPasswordPair
172     {
173         final String userName;
174         final String password;
175         final boolean persistentLogin;
176 
177         UserPasswordPair(final String user, final String password, final boolean persistentLogin)
178         {
179             userName = user;
180             this.password = password;
181             this.persistentLogin = persistentLogin;
182         }
183     }
184 }