Lots more authentication code cleaning
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / authn / AuthenticationEngine.java
1 /*
2  * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.authn;
18
19 import java.io.IOException;
20 import java.net.InetAddress;
21 import java.net.UnknownHostException;
22 import java.util.List;
23
24 import javax.servlet.RequestDispatcher;
25 import javax.servlet.ServletException;
26 import javax.servlet.http.HttpServlet;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29 import javax.servlet.http.HttpSession;
30
31 import org.apache.log4j.Logger;
32 import org.joda.time.DateTime;
33 import org.opensaml.xml.util.DatatypeHelper;
34 import org.opensaml.xml.util.Pair;
35
36 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandlerManager;
37 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
38 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
39 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
40 import edu.internet2.middleware.shibboleth.idp.session.Session;
41 import edu.internet2.middleware.shibboleth.idp.session.impl.AuthenticationMethodInformationImpl;
42 import edu.internet2.middleware.shibboleth.idp.session.impl.ServiceInformationImpl;
43
44 /**
45  * Manager responsible for handling authentication requests.
46  */
47 public class AuthenticationEngine extends HttpServlet {
48
49     /** Class logger. */
50     private static final Logger LOG = Logger.getLogger(AuthenticationEngine.class);
51
52     /**
53      * Gets the manager used to retrieve handlers for requests.
54      * 
55      * @return manager used to retrieve handlers for requests
56      */
57     public ProfileHandlerManager getProfileHandlerManager() {
58         return (ProfileHandlerManager) getServletContext().getAttribute("handlerManager");
59     }
60
61     /**
62      * Gets the session manager to be used.
63      * 
64      * @return session manager to be used
65      */
66     @SuppressWarnings("unchecked")
67     public SessionManager<Session> getSessionManager() {
68         return (SessionManager<Session>) getServletContext().getAttribute("sessionManager");
69     }
70
71     /**
72      * Gets the authentication handler manager used by this engine.
73      * 
74      * @return authentication handler manager used by this engine
75      */
76     public AuthenticationHandlerManager getAuthenticationHandlerManager() {
77         return (AuthenticationHandlerManager) getServletContext().getAttribute("authenticationHandlerManager");
78     }
79
80     /**
81      * Returns control back to the authentication engine.
82      * 
83      * @param httpRequest current http request
84      * @param httpResponse current http response
85      */
86     public static void returnToAuthenticationEngine(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
87         if (LOG.isDebugEnabled()) {
88             LOG.debug("Returning control to authentication engine");
89         }
90         HttpSession httpSession = httpRequest.getSession();
91         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
92         forwardRequest(loginContext.getAuthenticationEngineURL(), httpRequest, httpResponse);
93     }
94
95     /**
96      * Returns control back to the profile handler that invoked the authentication engine.
97      * 
98      * @param loginContext current login context
99      * @param httpRequest current http request
100      * @param httpResponse current http response
101      */
102     public static void returnToProfileHandler(LoginContext loginContext, HttpServletRequest httpRequest,
103             HttpServletResponse httpResponse) {
104         if (LOG.isDebugEnabled()) {
105             LOG.debug("Returning control to profile handler at: " + loginContext.getProfileHandlerURL());
106         }
107         forwardRequest(loginContext.getProfileHandlerURL(), httpRequest, httpResponse);
108     }
109
110     /**
111      * Forwards a request to the given path.
112      * 
113      * @param forwardPath path to forward the request to
114      * @param httpRequest current HTTP request
115      * @param httpResponse current HTTP response
116      */
117     protected static void forwardRequest(String forwardPath, HttpServletRequest httpRequest,
118             HttpServletResponse httpResponse) {
119         try {
120             RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(forwardPath);
121             dispatcher.forward(httpRequest, httpResponse);
122         } catch (IOException e) {
123             LOG.fatal("Unable to return control back to authentication engine", e);
124         } catch (ServletException e) {
125             LOG.fatal("Unable to return control back to authentication engine", e);
126         }
127     }
128
129     /** {@inheritDoc} */
130     @SuppressWarnings("unchecked")
131     protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException,
132             IOException {
133         if (LOG.isDebugEnabled()) {
134             LOG.debug("Processing incoming request");
135         }
136
137         HttpSession httpSession = httpRequest.getSession();
138         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
139         if (loginContext == null) {
140             LOG.error("Incoming request does not have attached login context");
141             throw new ServletException("Incoming request does not have attached login context");
142         }
143
144         if (!loginContext.getAuthenticationAttempted()) {
145             String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
146             Session shibSession = getSessionManager().getSession(shibSessionId);
147
148             if (shibSession != null) {
149                 AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(
150                         loginContext, shibSession);
151                 if (authenticationMethod != null) {
152                     if (LOG.isDebugEnabled()) {
153                         LOG.debug("An active authentication method is applicable for relying party.  "
154                                 + "Using authentication method " + authenticationMethod.getAuthenticationMethod()
155                                 + " as authentication method to relying party without re-authenticating user.");
156                     }
157                     authenticateUserWithActiveMethod(httpRequest, httpResponse, authenticationMethod);
158                 }
159             }
160
161             if (LOG.isDebugEnabled()) {
162                 LOG.debug("No active authentication method is applicable for relying party.  "
163                         + "Authenticating user with to be determined method.");
164             }
165             authenticateUserWithoutActiveMethod1(httpRequest, httpResponse);
166         } else {
167             if (LOG.isDebugEnabled()) {
168                 LOG.debug("Request returned from authentication handler, completing authentication process.");
169             }
170             authenticateUserWithoutActiveMethod2(httpRequest, httpResponse);
171         }
172     }
173
174     /**
175      * Completes the authentication request using an existing, active, authentication method for the current user.
176      * 
177      * @param httpRequest current HTTP request
178      * @param httpResponse current HTTP response
179      * @param authenticationMethod authentication method to use to complete the request
180      */
181     protected void authenticateUserWithActiveMethod(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
182             AuthenticationMethodInformation authenticationMethod) {
183         HttpSession httpSession = httpRequest.getSession();
184
185         String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
186         Session shibSession = getSessionManager().getSession(shibSessionId);
187
188         if (LOG.isDebugEnabled()) {
189             LOG.debug("Populating login context with existing session and authentication method information.");
190         }
191         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
192         loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
193         loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
194         loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
195         loginContext.setPrincipalAuthenticated(true);
196         loginContext.setPrincipalName(shibSession.getPrincipalName());
197
198         ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
199                 authenticationMethod);
200         shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
201
202         returnToProfileHandler(loginContext, httpRequest, httpResponse);
203     }
204
205     /**
206      * Performs the first part of user authentication. An authentication handler is determined, the login context is
207      * populated with some initial information, and control is forward to the selected handler so that it may
208      * authenticate the user.
209      * 
210      * @param httpRequest current HTTP request
211      * @param httpResponse current HTTP response
212      */
213     protected void authenticateUserWithoutActiveMethod1(HttpServletRequest httpRequest, 
214             HttpServletResponse httpResponse) {
215         HttpSession httpSession = httpRequest.getSession();
216         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
217
218         if (LOG.isDebugEnabled()) {
219             LOG.debug("Selecting appropriate authentication method for request.");
220         }
221         Pair<String, AuthenticationHandler> handler = getAuthenticationHandlerManager().getAuthenticationHandler(
222                 loginContext);
223
224         if (handler == null) {
225             loginContext.setPrincipalAuthenticated(false);
226             loginContext.setAuthenticationFailureMessage("No AuthenticationHandler satisfys the request from: "
227                             + loginContext.getRelyingPartyId());
228             LOG.error("No AuthenticationHandler satisfys the request from relying party: "
229                     + loginContext.getRelyingPartyId());
230             returnToProfileHandler(loginContext, httpRequest, httpResponse);
231         }
232
233         if (LOG.isDebugEnabled()) {
234             LOG.debug("Authentication method " + handler.getFirst() + " will be used to authenticate user.");
235         }
236         loginContext.setAuthenticationAttempted();
237         loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
238         loginContext.setAuthenticationMethod(handler.getFirst());
239         loginContext.setAuthenticationEngineURL(httpRequest.getRequestURI());
240
241         if (LOG.isDebugEnabled()) {
242             LOG.debug("Transferring control to authentication handler of type: "
243                     + handler.getSecond().getClass().getName());
244         }
245         handler.getSecond().login(httpRequest, httpResponse);
246     }
247
248     /**
249      * Performs the second part of user authentication. The principal name set by the authentication handler is
250      * retrieved and pushed in to the login context, a Shibboleth session is created if needed, information indicating
251      * that the user has logged into the service is recorded and finally control is returned back to the profile
252      * handler.
253      * 
254      * @param httpRequest current HTTP request
255      * @param httpResponse current HTTP response
256      */
257     protected void authenticateUserWithoutActiveMethod2(HttpServletRequest httpRequest, 
258             HttpServletResponse httpResponse) {
259         HttpSession httpSession = httpRequest.getSession();
260
261         String principalName = (String) httpRequest.getAttribute(AuthenticationHandler.PRINCIPAL_NAME_KEY);
262         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
263         if (DatatypeHelper.isEmpty(principalName)) {
264             loginContext.setPrincipalAuthenticated(false);
265             loginContext.setAuthenticationFailureMessage("No principal name returned from authentication handler.");
266             LOG.error("No principal name returned from authentication method: "
267                     + loginContext.getAuthenticationMethod());
268             returnToProfileHandler(loginContext, httpRequest, httpResponse);
269         }
270         loginContext.setPrincipalName(principalName);
271
272         String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
273         Session shibSession = getSessionManager().getSession(shibSessionId);
274
275         if (shibSession == null) {
276             if (LOG.isDebugEnabled()) {
277                 LOG.debug("Creating shibboleth session for principal " + principalName);
278             }
279
280             InetAddress addr;
281             try {
282                 addr = InetAddress.getByName(httpRequest.getRemoteAddr());
283             } catch (UnknownHostException ex) {
284                 addr = null;
285             }
286
287             shibSession = (Session) getSessionManager().createSession(addr, loginContext.getPrincipalName());
288             loginContext.setSessionID(shibSession.getSessionID());
289             httpSession.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, shibSession.getSessionID());
290         }
291
292         if (LOG.isDebugEnabled()) {
293             LOG.debug("Recording authentication and service information in Shibboleth session for principal: "
294                     + principalName);
295         }
296         AuthenticationMethodInformation authnMethodInfo = new AuthenticationMethodInformationImpl(loginContext
297                 .getAuthenticationMethod(), new DateTime(), loginContext.getAuthenticationDuration());
298         shibSession.getAuthenticationMethods().put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo);
299
300         ServiceInformation serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(),
301                 authnMethodInfo);
302         shibSession.getServicesInformation().put(serviceInfo.getEntityID(), serviceInfo);
303
304         shibSession.setLastActivityInstant(new DateTime());
305
306         returnToProfileHandler(loginContext, httpRequest, httpResponse);
307     }
308
309     /**
310      * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
311      * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
312      * user must be authenticated.
313      * 
314      * @param loginContext user login context
315      * @param shibSession user's shibboleth session
316      * 
317      * @return active authentication method that meets authentication requirements or null
318      */
319     protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
320             Session shibSession) {
321         if (loginContext.getForceAuth() || shibSession == null) {
322             return null;
323         }
324
325         List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
326
327         if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
328             for (AuthenticationMethodInformation authnMethod : shibSession.getAuthenticationMethods().values()) {
329                 if (!authnMethod.isExpired()) {
330                     return authnMethod;
331                 }
332             }
333         } else {
334             for (String preferredAuthnMethod : preferredAuthnMethods) {
335                 if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
336                     AuthenticationMethodInformation authnMethodInfo = shibSession.getAuthenticationMethods().get(
337                             preferredAuthnMethod);
338                     if (!authnMethodInfo.isExpired()) {
339                         return authnMethodInfo;
340                     }
341                 }
342             }
343         }
344
345         return null;
346     }
347 }