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