e501902793cc438187c56175c4caeb990c38b083
[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 session attribute holding the map of configurations.
48      */
49     private static final String CONFIGURATIONS_ATTRIBUTE = "GUAC_CONFIGS";
50     
51     /**
52      * The session attribute holding the credentials authorizing this session.
53      */
54     private static final String CREDENTIALS_ATTRIBUTE = "GUAC_CREDS";
55     
56     /**
57      * The AuthenticationProvider to use to authenticate all requests.
58      */
59     private AuthenticationProvider authProvider;
60
61     @Override
62     public void init() throws ServletException {
63
64         // Get auth provider instance
65         try {
66             authProvider = GuacamoleProperties.getRequiredProperty(BasicGuacamoleProperties.AUTH_PROVIDER);
67         }
68         catch (GuacamoleException e) {
69             logger.error("Error getting authentication provider from properties.", e);
70             throw new ServletException(e);
71         }
72
73     }
74
75     /**
76      * Notifies all listeners in the given collection that authentication has
77      * failed.
78      * 
79      * @param listeners A collection of all listeners that should be notified.
80      * @param credentials The credentials associated with the authentication
81      *                    request that failed.
82      */
83     private void notifyFailed(Collection listeners, Credentials credentials) {
84         
85         // Build event for auth failure
86         AuthenticationFailureEvent event = new AuthenticationFailureEvent(credentials);
87         
88         // Notify all listeners
89         for (Object listener : listeners) {
90             try {
91                 if (listener instanceof AuthenticationFailureListener)
92                     ((AuthenticationFailureListener) listener).authenticationFailed(event);
93             }
94             catch (GuacamoleException e) {
95                 logger.error("Error notifying AuthenticationFailureListener.", e);
96             }
97         }
98         
99     }
100
101     /**
102      * Notifies all listeners in the given collection that authentication was
103      * successful.
104      * 
105      * @param listeners A collection of all listeners that should be notified.
106      * @param credentials The credentials associated with the authentication
107      *                    request that succeeded.
108      * @return true if all listeners are allowing the authentication success,
109      *         or if there are no listeners, and false if any listener is
110      *         canceling the authentication success. Note that once one
111      *         listener cancels, no other listeners will run.
112      * @throws GuacamoleException If any listener throws an error while being
113      *                            notified. Note that if any listener throws an
114      *                            error, the success is canceled, and no other
115      *                            listeners will run.
116      */
117     private boolean notifySuccess(Collection listeners, Credentials credentials)
118             throws GuacamoleException {
119         
120         // Build event for auth success
121         AuthenticationSuccessEvent event = new AuthenticationSuccessEvent(credentials);
122         
123         // Notify all listeners
124         for (Object listener : listeners) {
125             if (listener instanceof AuthenticationSuccessListener) {
126
127                 // Cancel immediately if hook returns false
128                 if (!((AuthenticationSuccessListener) listener).authenticationSucceeded(event))
129                     return false;
130                 
131             }
132         }
133
134         return true;
135         
136     }
137   
138     /**
139      * Sends a predefined, generic error message to the user, along with a
140      * "403 - Forbidden" HTTP status code in the response.
141      * 
142      * @param response The response to send the error within.
143      * @throws IOException If an error occurs while sending the error.
144      */
145     private void failAuthentication(HttpServletResponse response) throws IOException {
146         response.sendError(HttpServletResponse.SC_FORBIDDEN);
147     }
148     
149     /**
150      * Returns the credentials associated with the given session.
151      * 
152      * @param session The session to retrieve credentials from.
153      * @return The credentials associated with the given session.
154      */
155     protected Credentials getCredentials(HttpSession session) {
156         return (Credentials) session.getAttribute(CREDENTIALS_ATTRIBUTE);
157     }
158
159     /**
160      * Returns the configurations associated with the given session.
161      * 
162      * @param session The session to retrieve configurations from.
163      * @return The configurations associated with the given session.
164      */
165     protected Map<String, GuacamoleConfiguration> getConfigurations(HttpSession session) {
166         return (Map<String, GuacamoleConfiguration>) session.getAttribute(CONFIGURATIONS_ATTRIBUTE);
167     }
168     
169     @Override
170     protected void service(HttpServletRequest request, HttpServletResponse response)
171     throws IOException, ServletException {
172
173         HttpSession httpSession = request.getSession(true);
174
175         // Try to get configs from session
176         Map<String, GuacamoleConfiguration> configs = getConfigurations(httpSession);
177
178         // If no configs, try to authenticate the user to get the configs using
179         // this request.
180         if (configs == null) {
181
182             SessionListenerCollection listeners;
183             try {
184                 listeners = new SessionListenerCollection(httpSession);
185             }
186             catch (GuacamoleException e) {
187                 logger.error("Failed to retrieve listeners. Authentication canceled.", e);
188                 failAuthentication(response);
189                 return;
190             }
191             
192             // Retrieve username and password from parms
193             String username = request.getParameter("username");
194             String password = request.getParameter("password");
195
196             // Build credentials object
197             Credentials credentials = new Credentials();
198             credentials.setSession(httpSession);
199             credentials.setRequest(request);
200             credentials.setUsername(username);
201             credentials.setPassword(password);
202
203             // Get authorized configs
204             try {
205                 configs = authProvider.getAuthorizedConfigurations(credentials);
206             }
207
208
209             /******** HANDLE FAILED AUTHENTICATION ********/
210             
211             // If error retrieving configs, fail authentication, notify listeners
212             catch (GuacamoleException e) {
213                 logger.error("Error retrieving configuration(s) for user \"{}\".",
214                         credentials.getUsername(), e);
215
216                 notifyFailed(listeners, credentials);
217                 failAuthentication(response);
218                 return;
219             }
220             
221             // If no configs, fail authentication, notify listeners
222             if (configs == null) {
223                 logger.warn("Authentication attempt from {} for user \"{}\" failed.",
224                         request.getRemoteAddr(), credentials.getUsername());
225                 
226                 notifyFailed(listeners, credentials);
227                 failAuthentication(response);
228                 return;
229             }
230
231
232             /******** HANDLE SUCCESSFUL AUTHENTICATION ********/
233             
234             try {
235
236                 // Otherwise, authentication has been succesful
237                 logger.info("User \"{}\" successfully authenticated from {}.",
238                         credentials.getUsername(), request.getRemoteAddr());
239
240                 // Notify of success, cancel if requested
241                 if (!notifySuccess(listeners, credentials)) {
242                     logger.info("Successful authentication canceled by hook.");
243                     failAuthentication(response);
244                     return;
245                 }
246                 
247             }
248             catch (GuacamoleException e) {
249                 
250                 // Cancel authentication success if hook throws exception
251                 logger.error("Successful authentication canceled by error in hook.", e);
252                 failAuthentication(response);
253                 return;
254                 
255             }
256
257             // Associate configs and credentials with session
258             httpSession.setAttribute(CONFIGURATIONS_ATTRIBUTE, configs);
259             httpSession.setAttribute(CREDENTIALS_ATTRIBUTE,    credentials);
260
261
262         }
263
264         // Allow servlet to run now that authentication has been validated
265         authenticatedService(configs, request, response);
266
267     }
268
269     protected abstract void authenticatedService(
270             Map<String, GuacamoleConfiguration> configs,
271             HttpServletRequest request, HttpServletResponse response)
272             throws ServletException, IOException;
273
274 }