Cache credentials in session, provide getters for retrieving configs and credentials...
[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     /**
148      * Returns the credentials associated with the given session.
149      * 
150      * @param session The session to retrieve credentials from.
151      * @return The credentials associated with the given session.
152      */
153     protected Credentials getCredentials(HttpSession session) {
154         return (Credentials) session.getAttribute("GUAC_CREDS");
155     }
156
157     /**
158      * Returns the configurations associated with the given session.
159      * 
160      * @param session The session to retrieve configurations from.
161      * @return The configurations associated with the given session.
162      */
163     protected Map<String, GuacamoleConfiguration> getConfigurations(HttpSession session) {
164         return (Map<String, GuacamoleConfiguration>) session.getAttribute("GUAC_CONFIGS");
165     }
166     
167     @Override
168     protected void service(HttpServletRequest request, HttpServletResponse response)
169     throws IOException, ServletException {
170
171         HttpSession httpSession = request.getSession(true);
172
173         // Try to get configs from session
174         Map<String, GuacamoleConfiguration> configs = getConfigurations(httpSession);
175
176         // If no configs, try to authenticate the user to get the configs using
177         // this request.
178         if (configs == null) {
179
180             SessionListenerCollection listeners;
181             try {
182                 listeners = new SessionListenerCollection(httpSession);
183             }
184             catch (GuacamoleException e) {
185                 logger.error("Failed to retrieve listeners. Authentication canceled.", e);
186                 failAuthentication(response);
187                 return;
188             }
189             
190             // Retrieve username and password from parms
191             String username = request.getParameter("username");
192             String password = request.getParameter("password");
193
194             // Build credentials object
195             Credentials credentials = new Credentials();
196             credentials.setSession(httpSession);
197             credentials.setRequest(request);
198             credentials.setUsername(username);
199             credentials.setPassword(password);
200
201             // Get authorized configs
202             try {
203                 configs = authProvider.getAuthorizedConfigurations(credentials);
204             }
205
206
207             /******** HANDLE FAILED AUTHENTICATION ********/
208             
209             // If error retrieving configs, fail authentication, notify listeners
210             catch (GuacamoleException e) {
211                 logger.error("Error retrieving configuration(s) for user \"{}\".",
212                         credentials.getUsername());
213
214                 notifyFailed(listeners, credentials);
215                 failAuthentication(response);
216                 return;
217             }
218             
219             // If no configs, fail authentication, notify listeners
220             if (configs == null) {
221                 logger.warn("Authentication attempt from {} for user \"{}\" failed.",
222                         request.getRemoteAddr(), credentials.getUsername());
223                 
224                 notifyFailed(listeners, credentials);
225                 failAuthentication(response);
226                 return;
227             }
228
229
230             /******** HANDLE SUCCESSFUL AUTHENTICATION ********/
231             
232             try {
233
234                 // Otherwise, authentication has been succesful
235                 logger.info("User \"{}\" successfully authenticated from {}.",
236                         credentials.getUsername(), request.getRemoteAddr());
237
238                 // Notify of success, cancel if requested
239                 if (!notifySuccess(listeners, credentials)) {
240                     logger.info("Successful authentication canceled by hook.");
241                     failAuthentication(response);
242                     return;
243                 }
244                 
245             }
246             catch (GuacamoleException e) {
247                 
248                 // Cancel authentication success if hook throws exception
249                 logger.error("Successful authentication canceled by error in hook.");
250                 failAuthentication(response);
251                 return;
252                 
253             }
254
255             // Associate configs and credentials with session
256             httpSession.setAttribute("GUAC_CONFIGS", configs);
257             httpSession.setAttribute("GUAC_CREDS",   credentials);
258
259
260         }
261
262         // Allow servlet to run now that authentication has been validated
263         authenticatedService(configs, request, response);
264
265     }
266
267     protected abstract void authenticatedService(
268             Map<String, GuacamoleConfiguration> configs,
269             HttpServletRequest request, HttpServletResponse response)
270             throws ServletException, IOException;
271
272 }