Added calls to any authentication success/fail hooks.
[guacamole.git] / src / main / java / net / sourceforge / guacamole / net / basic / AuthenticatingHttpServlet.java
1
2 package net.sourceforge.guacamole.net.basic;
3
4 import java.io.IOException;
5 import java.util.Collection;
6 import java.util.Map;
7 import javax.servlet.ServletException;
8 import javax.servlet.http.HttpServlet;
9 import javax.servlet.http.HttpServletRequest;
10 import javax.servlet.http.HttpServletResponse;
11 import javax.servlet.http.HttpSession;
12 import net.sourceforge.guacamole.GuacamoleException;
13 import net.sourceforge.guacamole.net.auth.AuthenticationProvider;
14 import net.sourceforge.guacamole.net.auth.Credentials;
15 import net.sourceforge.guacamole.net.basic.event.SessionListenerCollection;
16 import net.sourceforge.guacamole.net.basic.properties.BasicGuacamoleProperties;
17 import net.sourceforge.guacamole.net.event.AuthenticationFailureEvent;
18 import net.sourceforge.guacamole.net.event.AuthenticationSuccessEvent;
19 import net.sourceforge.guacamole.net.event.listener.AuthenticationFailureListener;
20 import net.sourceforge.guacamole.net.event.listener.AuthenticationSuccessListener;
21 import net.sourceforge.guacamole.properties.GuacamoleProperties;
22 import net.sourceforge.guacamole.protocol.GuacamoleConfiguration;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 /**
27  * Abstract servlet which provides an authenticatedService() function that
28  * is only called if the HTTP request is authenticated, or the current
29  * HTTP session has already been authenticated.
30  * 
31  * Authorized configurations are retrieved using the authentication provider
32  * defined in guacamole.properties. The authentication provider has access
33  * to the request and session, in addition to any submitted username and
34  * password, in order to authenticate the user.
35  * 
36  * All authorized configurations will be stored in the current HttpSession.
37  * 
38  * Success and failure are logged.
39  * 
40  * @author Michael Jumper
41  */
42 public abstract class AuthenticatingHttpServlet extends HttpServlet {
43
44     private Logger logger = LoggerFactory.getLogger(AuthenticatingHttpServlet.class);
45     
46     /**
47      * The error message to be provided to the client user if authentication
48      * fails for ANY REASON.
49      */
50     private static final String AUTH_ERROR_MESSAGE = 
51             "User not logged in or authentication failed.";
52     
53     /**
54      * The AuthenticationProvider to use to authenticate all requests.
55      */
56     private AuthenticationProvider authProvider;
57
58     @Override
59     public void init() throws ServletException {
60
61         // Get auth provider instance
62         try {
63             authProvider = GuacamoleProperties.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
64         }
65         catch (GuacamoleException e) {
66             logger.error("Error getting authentication provider from properties.", e);
67             throw new ServletException(e);
68         }
69
70     }
71
72     /**
73      * Notifies all listeners in the given collection that authentication has
74      * failed.
75      * 
76      * @param listeners A collection of all listeners that should be notified.
77      * @param credentials The credentials associated with the authentication
78      *                    request that failed.
79      */
80     private void notifyFailed(Collection listeners, Credentials credentials) {
81         
82         // Build event for auth failure
83         AuthenticationFailureEvent event = new AuthenticationFailureEvent(credentials);
84         
85         // Notify all listeners
86         for (Object listener : listeners) {
87             try {
88                 if (listener instanceof AuthenticationFailureListener)
89                     ((AuthenticationFailureListener) listener).authenticationFailed(event);
90             }
91             catch (GuacamoleException e) {
92                 logger.error("Error notifying AuthenticationFailureListener.", e);
93             }
94         }
95         
96     }
97
98     /**
99      * Notifies all listeners in the given collection that authentication was
100      * successful.
101      * 
102      * @param listeners A collection of all listeners that should be notified.
103      * @param credentials The credentials associated with the authentication
104      *                    request that succeeded.
105      * @return true if all listeners are allowing the authentication success,
106      *         or if there are no listeners, and false if any listener is
107      *         canceling the authentication success. Note that once one
108      *         listener cancels, no other listeners will run.
109      * @throws GuacamoleException If any listener throws an error while being
110      *                            notified. Note that if any listener throws an
111      *                            error, the success is canceled, and no other
112      *                            listeners will run.
113      */
114     private boolean notifySuccess(Collection listeners, Credentials credentials)
115             throws GuacamoleException {
116         
117         // Build event for auth success
118         AuthenticationSuccessEvent event = new AuthenticationSuccessEvent(credentials);
119         
120         // Notify all listeners
121         for (Object listener : listeners) {
122             if (listener instanceof AuthenticationSuccessListener) {
123
124                 // Cancel immediately if hook returns false
125                 if (!((AuthenticationSuccessListener) listener).authenticationSucceeded(event))
126                     return false;
127                 
128             }
129         }
130
131         return true;
132         
133     }
134   
135     /**
136      * Sends a predefined, generic error message to the user, along with a
137      * "403 - Forbidden" HTTP status code in the response.
138      * 
139      * @param response The response to send the error within.
140      * @throws IOException If an error occurs while sending the error.
141      */
142     private void failAuthentication(HttpServletResponse response) throws IOException {
143         response.setHeader("X-Guacamole-Error-Message", AUTH_ERROR_MESSAGE);
144         response.sendError(HttpServletResponse.SC_FORBIDDEN);
145     }
146     
147     @Override
148     protected void service(HttpServletRequest request, HttpServletResponse response)
149     throws IOException, ServletException {
150
151         HttpSession httpSession = request.getSession(true);
152
153         // Try to get configs from session
154         Map<String, GuacamoleConfiguration> configs =
155                 (Map<String, GuacamoleConfiguration>) httpSession.getAttribute("GUAC_CONFIGS");
156
157         // If no configs, try to authenticate the user to get the configs using
158         // this request.
159         if (configs == null) {
160
161             SessionListenerCollection listeners;
162             try {
163                 listeners = new SessionListenerCollection(httpSession);
164             }
165             catch (GuacamoleException e) {
166                 logger.error("Failed to retrieve listeners. Authentication canceled.", e);
167                 failAuthentication(response);
168                 return;
169             }
170             
171             // Retrieve username and password from parms
172             String username = request.getParameter("username");
173             String password = request.getParameter("password");
174
175             // Build credentials object
176             Credentials credentials = new Credentials ();
177             credentials.setSession(httpSession);
178             credentials.setRequest(request);
179             credentials.setUsername(username);
180             credentials.setPassword(password);
181             
182             // Get authorized configs
183             try {
184                 configs = authProvider.getAuthorizedConfigurations(credentials);
185             }
186
187
188             /******** HANDLE FAILED AUTHENTICATION ********/
189             
190             // If error retrieving configs, fail authentication, notify listeners
191             catch (GuacamoleException e) {
192                 logger.error("Error retrieving configuration(s) for user \"{}\".", username);
193
194                 notifyFailed(listeners, credentials);
195                 failAuthentication(response);
196                 return;
197             }
198             
199             // If no configs, fail authentication, notify listeners
200             if (configs == null) {
201                 logger.warn("Authentication attempt from {} for user \"{}\" failed.",
202                         request.getRemoteAddr(), username);
203                 
204                 notifyFailed(listeners, credentials);
205                 failAuthentication(response);
206                 return;
207             }
208
209
210             /******** HANDLE SUCCESSFUL AUTHENTICATION ********/
211             
212             try {
213
214                 // Otherwise, authentication has been succesful
215                 logger.info("User \"{}\" successfully authenticated from {}.",
216                         username, request.getRemoteAddr());
217
218                 // Notify of success, cancel if requested
219                 if (!notifySuccess(listeners, credentials)) {
220                     logger.info("Successful authentication canceled by hook.");
221                     failAuthentication(response);
222                     return;
223                 }
224                 
225             }
226             catch (GuacamoleException e) {
227                 
228                 // Cancel authentication success if hook throws exception
229                 logger.error("Successful authentication canceled by error in hook.");
230                 failAuthentication(response);
231                 return;
232                 
233             }
234
235             // Associate configs with session
236             httpSession.setAttribute("GUAC_CONFIGS", configs);
237
238
239         }
240
241         // Allow servlet to run now that authentication has been validated
242         authenticatedService(configs, request, response);
243
244     }
245
246     protected abstract void authenticatedService(
247             Map<String, GuacamoleConfiguration> configs,
248             HttpServletRequest request, HttpServletResponse response)
249             throws ServletException, IOException;
250
251 }