Initial rework of authentication code, needs logging and testing
[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.Pair;
34
35 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandlerManager;
36 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
37 import edu.internet2.middleware.shibboleth.idp.session.AuthenticationMethodInformation;
38 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
39 import edu.internet2.middleware.shibboleth.idp.session.Session;
40 import edu.internet2.middleware.shibboleth.idp.session.impl.AuthenticationMethodInformationImpl;
41 import edu.internet2.middleware.shibboleth.idp.session.impl.ServiceInformationImpl;
42
43 /**
44  * Manager responsible for handling authentication requests.
45  */
46 public class AuthenticationEngine extends HttpServlet {
47
48     /** Class logger. */
49     private static final Logger log = Logger.getLogger(AuthenticationEngine.class);
50
51     /**
52      * Gets the manager used to retrieve handlers for requests.
53      * 
54      * @return manager used to retrieve handlers for requests
55      */
56     public ProfileHandlerManager getProfileHandlerManager() {
57         return (ProfileHandlerManager) getServletContext().getAttribute("handlerManager");
58     }
59
60     /**
61      * Gets the session manager to be used.
62      * 
63      * @return session manager to be used
64      */
65     @SuppressWarnings("unchecked")
66     public SessionManager<Session> getSessionManager() {
67         return (SessionManager<Session>) getServletContext().getAttribute("sessionManager");
68     }
69
70     /**
71      * Gets the authentication handler manager used by this engine.
72      * 
73      * @return authentication handler manager used by this engine
74      */
75     public AuthenticationHandlerManager getAuthenticationHandlerManager() {
76         return (AuthenticationHandlerManager) getServletContext().getAttribute("authenticationHandlerManager");
77     }
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      * @param loginContext user login context
86      */
87     public static void returnToAuthenticationEngine(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
88         HttpSession httpSession = httpRequest.getSession();
89
90         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
91         
92         try {
93             RequestDispatcher distpather = httpRequest.getRequestDispatcher(loginContext.getAuthenticationManagerURL());
94             distpather.forward(httpRequest, httpResponse);
95         } catch (IOException e) {
96             log.fatal("Unable to return control back to authentication engine", e);
97         } catch (ServletException e) {
98             log.fatal("Unable to return control back to authentication engine", e);
99         }
100     }
101
102     /** {@inheritDoc} */
103     protected void service(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException,
104             IOException {
105         HttpSession httpSession = httpRequest.getSession();
106
107         LoginContext loginContext = (LoginContext) httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
108         if (loginContext == null) {
109             // TODO error
110         }
111
112         // If authentication has been attempted, don't try it again.
113         if (loginContext.getAuthenticationAttempted()) {
114             handleNewAuthnRequest(loginContext, httpRequest, httpResponse);
115         } else {
116             finishAuthnRequest(loginContext, httpRequest, httpResponse);
117         }
118     }
119
120     /**
121      * Handle a new authentication request.
122      * 
123      * @param loginContext The {@link LoginContext} for the new authentication request
124      * @param httpRequest The servlet request containing the authn request
125      * @param httpResponse The associated servlet response.
126      * 
127      * @throws IOException thrown if there is a problem reading/writting to the HTTP request/response
128      * @throws ServletException thrown if there is a problem transferring control to the authentication handler
129      */
130     protected void handleNewAuthnRequest(LoginContext loginContext, HttpServletRequest httpRequest,
131             HttpServletResponse httpResponse) throws ServletException, IOException {
132
133         HttpSession httpSession = httpRequest.getSession();
134         String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
135         Session shibSession = getSessionManager().getSession(shibSessionId);
136
137         AuthenticationMethodInformation authenticationMethod = getUsableExistingAuthenticationMethod(loginContext,
138                 shibSession);
139         if (authenticationMethod != null) {
140             loginContext.setAuthenticationDuration(authenticationMethod.getAuthenticationDuration());
141             loginContext.setAuthenticationInstant(authenticationMethod.getAuthenticationInstant());
142             loginContext.setAuthenticationMethod(authenticationMethod.getAuthenticationMethod());
143             loginContext.setPrincipalAuthenticated(true);
144             loginContext.setPrincipalName(shibSession.getPrincipalName());
145             finishAuthnRequest(loginContext, httpRequest, httpResponse);
146         } else {
147             Pair<String, AuthenticationHandler> handler = getAuthenticationHandlerManager().getAuthenticationHandler(
148                     loginContext);
149
150             if (handler == null) {
151                 loginContext.setPassiveAuth(false);
152                 loginContext
153                         .setAuthenticationFailureMessage("No installed AuthenticationHandler can satisfy the authentication request.");
154                 log.error("No installed AuthenticationHandler can satisfy the authentication request.");
155                 finishAuthnRequest(loginContext, httpRequest, httpResponse);
156             }
157
158             loginContext.setAuthenticationAttempted();
159             loginContext.setAuthenticationDuration(handler.getSecond().getAuthenticationDuration());
160             loginContext.setAuthenticationMethod(handler.getFirst());
161             loginContext.setAuthenticationManagerURL(httpRequest.getRequestURI());
162
163             httpSession.setAttribute(LoginContext.LOGIN_CONTEXT_KEY, loginContext);
164             handler.getSecond().login(loginContext, httpRequest, httpResponse);
165         }
166     }
167
168     /**
169      * Gets the authentication method, currently active for the user, that also meets the requirements expressed by the
170      * login context. If a method is returned the user does not need to authenticate again, if null is returned then the
171      * user must be authenticated.
172      * 
173      * @param loginContext user login context
174      * @param shibSession user's shibboleth session
175      * 
176      * @return active authentication method that meets authentication requirements or null
177      */
178     protected AuthenticationMethodInformation getUsableExistingAuthenticationMethod(LoginContext loginContext,
179             Session shibSession) {
180         if (loginContext.getForceAuth() || shibSession == null) {
181             return null;
182         }
183
184         List<String> preferredAuthnMethods = loginContext.getRequestedAuthenticationMethods();
185
186         if (preferredAuthnMethods == null || preferredAuthnMethods.size() == 0) {
187             for (AuthenticationMethodInformation authnMethod : shibSession.getAuthenticationMethods().values()) {
188                 if (!authnMethod.isExpired()) {
189                     return authnMethod;
190                 }
191             }
192         } else {
193             for (String preferredAuthnMethod : preferredAuthnMethods) {
194                 if (shibSession.getAuthenticationMethods().containsKey(preferredAuthnMethod)) {
195                     AuthenticationMethodInformation authnMethodInfo = shibSession.getAuthenticationMethods().get(
196                             preferredAuthnMethod);
197                     if (!authnMethodInfo.isExpired()) {
198                         return authnMethodInfo;
199                     }
200                 }
201             }
202         }
203
204         return null;
205     }
206
207     /**
208      * Handle the "return leg" of an authentication request (i.e. clean up after an authentication handler has run).
209      * 
210      * @param loginContext The {@link LoginContext} for the new authentication request
211      * @param httpRequest The servlet request containing the authn request
212      * @param httpResponse The associated servlet response.
213      * 
214      * @throws IOException thrown if there is a problem reading/writting to the HTTP request/response
215      * @throws ServletException thrown if there is a problem transferring control to the authentication profile handler
216      */
217     protected void finishAuthnRequest(LoginContext loginContext, HttpServletRequest httpRequest,
218             HttpServletResponse httpResponse) throws ServletException, IOException {
219
220         HttpSession httpSession = httpRequest.getSession();
221         String shibSessionId = (String) httpSession.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
222         Session shibSession = null;
223         AuthenticationMethodInformation authnMethodInfo = null;
224         ServiceInformation serviceInfo = null;
225
226         if (!loginContext.getAuthenticationAttempted()) {
227             // Authentication wasn't attempted so we're using a previously established authentication method
228             shibSession = getSessionManager().getSession(shibSessionId);
229             authnMethodInfo = shibSession.getAuthenticationMethods().get(loginContext.getAuthenticationMethod());
230         } else {
231             if (shibSessionId == null) {
232                 InetAddress addr;
233                 try {
234                     addr = InetAddress.getByName(httpRequest.getRemoteAddr());
235                 } catch (UnknownHostException ex) {
236                     addr = null;
237                 }
238
239                 shibSession = (Session) getSessionManager().createSession(addr, loginContext.getPrincipalName());
240                 httpSession.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, shibSession.getSessionID());
241
242                 authnMethodInfo = new AuthenticationMethodInformationImpl(loginContext.getAuthenticationMethod(),
243                         new DateTime(), loginContext.getAuthenticationDuration());
244                 shibSession.getAuthenticationMethods().put(authnMethodInfo.getAuthenticationMethod(), authnMethodInfo);
245             }
246         }
247
248         loginContext.setSessionID(shibSession.getSessionID());
249         shibSession.setLastActivityInstant(new DateTime());
250
251         serviceInfo = shibSession.getServicesInformation().get(loginContext.getRelyingPartyId());
252         if (serviceInfo == null) {
253             serviceInfo = new ServiceInformationImpl(loginContext.getRelyingPartyId(), new DateTime(), authnMethodInfo);
254         }
255
256         RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginContext.getProfileHandlerURL());
257         dispatcher.forward(httpRequest, httpResponse);
258     }
259
260     // TODO logout support
261 }