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