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          try
69          {
70              runBeforeLoginInterceptors(request, response, userPair, interceptors);
71  
72              loginStatus = runAuthentication(request, response, userPair);
73          }
74          catch (final AuthenticatorException e)
75          {
76              if (dbg)
77              {
78                  log.debug(METHOD + "An exception occurred authenticating : '" + userPair.userName + "'", e);
79              }
80              loginStatus = LOGIN_FAILED;
81          }
82          finally
83          {
84              runAfterLoginInterceptors(request, response, loginStatus, userPair, interceptors);
85          }
86          return loginStatus;
87      }
88  
89      /**
90       * This will check to see if an elevated security check is required and then perform it before then asking for the
91       * authenticator and using it to authenticate/authorise the user.
92       *
93       * @param httpServletRequest  the HTTP servlet request in play
94       * @param httpServletResponse the HTTP servlet respone in play
95       * @param userPair            the {@link com.atlassian.seraph.filter.PasswordBasedLoginFilter.UserPasswordPair} in
96       *                            play
97       *
98       * @return the status of the login
99       *
100      * @throws AuthenticatorException if something goes wrong in the authenticator
101      */
102     private String runAuthentication(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final UserPasswordPair userPair) throws AuthenticatorException
103     {
104         final String METHOD = "runAuthentication : ";
105         final boolean dbg = log.isDebugEnabled();
106 
107         ElevatedSecurityGuard securityGuard = getElevatedSecurityGuard();
108 
109         if (! securityGuard.performElevatedSecurityCheck(httpServletRequest, userPair.userName))
110         {
111             if (dbg)
112             {
113                 log.debug(METHOD + "'" + userPair.userName + "' failed elevated security check");
114             }
115             AUTHENTICATION_DENIED.stampRequestResponse(httpServletRequest,httpServletResponse);
116             securityGuard.onFailedLoginAttempt(httpServletRequest, userPair.userName);
117             return LOGIN_FAILED;
118         }
119         if (dbg)
120         {
121             log.debug(METHOD + "'" + userPair.userName + "' does not require elevated security check.  Attempting authentication...");
122         }
123 
124         // ask the authenticator to try to log in the user the us
125         final boolean loggedIn = getAuthenticator().login(httpServletRequest, httpServletResponse, userPair.userName, userPair.password, userPair.persistentLogin);
126         if (dbg)
127         {
128             log.debug(METHOD + "'" + userPair.userName + "' was " + (loggedIn? "successfully" : "UNSUCCESSFULLY") + " authenticated");
129         }
130 
131         if (loggedIn)
132         {
133             OK.stampRequestResponse(httpServletRequest,httpServletResponse);
134             securityGuard.onSuccessfulLoginAttempt(httpServletRequest, userPair.userName);
135         }
136         else
137         {
138             AUTHENTICATED_FAILED.stampRequestResponse(httpServletRequest,httpServletResponse);
139             securityGuard.onFailedLoginAttempt(httpServletRequest, userPair.userName);
140         }
141         return loggedIn ? LOGIN_SUCCESS : LOGIN_FAILED;
142     }
143 
144     private void runBeforeLoginInterceptors(final HttpServletRequest request, final HttpServletResponse response, final UserPasswordPair userPair, final List<LoginInterceptor> interceptors)
145     {
146         for (final LoginInterceptor loginInterceptor : interceptors)
147         {
148             loginInterceptor.beforeLogin(request, response, userPair.userName, userPair.password, userPair.persistentLogin);
149         }
150     }
151 
152     private void runAfterLoginInterceptors(final HttpServletRequest request, final HttpServletResponse response, final String status, final UserPasswordPair userPair, final List<LoginInterceptor> interceptors)
153     {
154         for (final LoginInterceptor loginInterceptor : interceptors)
155         {
156             loginInterceptor.afterLogin(request, response, userPair.userName, userPair.password, userPair.persistentLogin, status);
157         }
158     }
159 
160     /**
161      * Returns a username password pair for this request. If this request does not contain user credentials - returns
162      * null;
163      *
164      * @param request the HTTP request in play
165      * @return user credentials or null
166      */
167     abstract UserPasswordPair extractUserPasswordPair(HttpServletRequest request);
168 
169     /**
170      * Represents a username password pair of user credentials.
171      */
172     public static final class UserPasswordPair
173     {
174         final String userName;
175         final String password;
176         final boolean persistentLogin;
177 
178         UserPasswordPair(final String user, final String password, final boolean persistentLogin)
179         {
180             userName = user;
181             this.password = password;
182             this.persistentLogin = persistentLogin;
183         }
184     }
185 }