Configuration code for SAML 2 SSO profile handler
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / authn / AuthenticationManager.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.util.List;
22 import java.util.Map;
23 import java.util.concurrent.ConcurrentHashMap;
24
25 import javax.servlet.RequestDispatcher;
26 import javax.servlet.ServletException;
27 import javax.servlet.http.HttpServlet;
28 import javax.servlet.http.HttpServletRequest;
29 import javax.servlet.http.HttpServletResponse;
30 import javax.servlet.http.HttpSession;
31
32 import javolution.util.FastMap;
33
34 import org.apache.log4j.Logger;
35
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.Session;
39 import edu.internet2.middleware.shibboleth.idp.session.impl.AuthenticationMethodInformationImpl;
40
41 /**
42  * Manager responsible for handling authentication requests.
43  */
44 //TODO map needed objects into servlet context information and fetch from there
45 public class AuthenticationManager extends HttpServlet {
46
47     /** log4j. */
48     private final Logger log = Logger.getLogger(AuthenticationManager.class);
49
50     /** SessionManager to be used. */
51     private SessionManager sessionMgr;
52
53     /** Map of URIs onto AuthenticationHandlerInfo. */
54     private Map<String, AuthenticationHandler> handlerMap = new ConcurrentHashMap<String, AuthenticationHandler>();
55
56     /** The default AuthenticationHandler. */
57     private AuthenticationHandler defaultHandler;
58
59     /** The URI for the default AuthenticationHandler. */
60     private String defaultHandlerURI;
61
62     /**
63      * Gets the session manager to be used.
64      * 
65      * @return session manager to be used
66      */
67     public SessionManager getSessionManager() {
68         return sessionMgr;
69     }
70
71     /**
72      * Sets the session manager to be used.
73      * 
74      * @param manager session manager to be used.
75      */
76     public void setSessionManager(final SessionManager manager) {
77         sessionMgr = manager;
78     }
79
80     /**
81      * Get the map of {@link AuthenticationHandlers}.
82      * 
83      * @return The map of AuthenticationHandlers
84      */
85     public Map<String, AuthenticationHandler> getHandlerMap() {
86
87         return new FastMap<String, AuthenticationHandler>(handlerMap);
88     }
89
90     /**
91      * Set the {@link AuthenticationHandler} map.
92      * 
93      * @param handlerMap The Map of URIs to AuthenticationHandlers
94      */
95     public void setHandlerMap(final Map<String, AuthenticationHandler> handlerMap) {
96
97         for (String uri : handlerMap.keySet()) {
98             addHandlerMapping(uri, handlerMap.get(uri));
99         }
100     }
101
102     /**
103      * Add a <code>&lt;String:AuthenticationHandler&gr;</code> mapping to the AuthenticationManager's table. If a
104      * mapping for the URI already exists, it will be overwritten.
105      * 
106      * The URI SHOULD be from the saml-autn-context-2.0-os
107      * 
108      * @param uri A URI identifying the authentcation method.
109      * @param handler The AuthenticationHandler.
110      */
111     public void addHandlerMapping(String uri, AuthenticationHandler handler) {
112
113         if (uri == null || handler == null) {
114             return;
115         }
116
117         log.debug("AuthenticationManager: Registering " + handler.getClass().getName() + " for " + uri);
118
119         handlerMap.put(uri, handler);
120     }
121
122     /**
123      * Register the default {@link AuthenticationHandler}.
124      * 
125      * @param uri The URI of the default authentication handler (from saml-authn-context-2.0-os)
126      * @param handler The default {@link AuthenticationHandler}.
127      */
128     public void setDefaultHandler(String uri, AuthenticationHandler handler) {
129
130         log.debug("AuthenticationManager: Registering default handler " + handler.getClass().getName());
131
132         defaultHandler = handler;
133         defaultHandlerURI = uri;
134     }
135
136     /**
137      * Remove a <String:AuthenticationHandler> mapping from the AuthenticationManager's table.
138      * 
139      * The URI SHOULD be from the saml-authn-context-2.0-os
140      * 
141      * @param uri A URI identifying the authentcation method.
142      */
143     public void removeHandlerMapping(String uri) {
144
145         if (uri == null) {
146             return;
147         }
148
149         log.debug("AuthenticationManager: Unregistering handler for " + uri);
150
151         handlerMap.remove(uri);
152     }
153
154     /**
155      * Primary entrypoint for the AuthnManager.
156      * 
157      * @param req The ServletRequest.
158      * @param resp The ServletResponse.
159      */
160     public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
161
162         if (req == null || resp == null) {
163             log.error("AuthenticationManager: Invalid parameters in AuthenticationManager's doPost().");
164             return;
165         }
166
167         HttpSession httpSession = req.getSession();
168         Object o = httpSession.getAttribute(LoginContext.LOGIN_CONTEXT_KEY);
169
170         if (o == null || !(o instanceof LoginContext)) {
171             log.error("AuthenticationManager: Invalid login context object found in HttpSession.");
172             return;
173         }
174         LoginContext loginContext = (LoginContext) o;
175
176         // If authentication has been attempted, don't try it again.
177         if (loginContext.getAuthenticationAttempted()) {
178             handleNewAuthnRequest(loginContext, req, resp);
179         } else {
180             finishAuthnRequest(loginContext, req, resp);
181         }
182     }
183
184     /**
185      * Handle a new authentication request.
186      * 
187      * @param loginContext The {@link LoginContext} for the new authentication request
188      * @param servletRequest The servlet request containing the authn request
189      * @param servletResponse The associated servlet response.
190      */
191     private void handleNewAuthnRequest(final LoginContext loginContext, final HttpServletRequest servletRequest,
192             final HttpServletResponse servletResponse) throws ServletException, IOException {
193
194         boolean forceAuthN = loginContext.getForceAuth();
195         boolean passiveAuthN = loginContext.getPassiveAuth();
196
197         // set that authentication has been attempted, to prevent processing
198         // loops
199         loginContext.setAuthenticationAttempted();
200
201         // if the profile handler set a list of requested authn methods,
202         // evaluate them. otherwise, evaluate the default handler.
203         List<String> requestedAuthnMethods = loginContext.getRequestedAuthenticationMethods();
204         AuthenticationHandler handler = null;
205
206         if (requestedAuthnMethods == null) {
207
208             // if no authn methods were specified, try the default handler
209
210             if (evaluateHandler(defaultHandler, "default", forceAuthN, passiveAuthN)) {
211                 handler = defaultHandler;
212                 loginContext.setAuthenticationMethod(defaultHandlerURI);
213             }
214
215         } else {
216
217             // evaluate all requested authn methods until we find a match.
218
219             for (String authnMethodURI : requestedAuthnMethods) {
220
221                 AuthenticationHandler candidateHandler = handlerMap.get(authnMethodURI);
222                 if (candidateHandler == null) {
223                     log.debug("AuthenticationManager:  No registered authentication handlers can satisfy the "
224                             + " requested authentication method " + authnMethodURI);
225                     continue;
226                 }
227
228                 if (evaluateHandler(candidateHandler, authnMethodURI, forceAuthN, passiveAuthN)) {
229
230                     // we found a match. stop iterating.
231                     handler = candidateHandler;
232                     log.info("AuthenticationManager: Using authentication handler " + handler.getClass().getName()
233                             + " for authentication method " + authnMethodURI);
234                     loginContext.setAuthenticationMethod(authnMethodURI);
235                     break;
236                 }
237             }
238         }
239
240         // if no acceptable handler was found, abort.
241         if (handler == null) {
242             loginContext.setAuthenticationOK(false);
243             loginContext
244                     .setAuthenticationFailureMessage("No installed AuthenticationHandler can satisfy the authentication request.");
245
246             log.error("AuthenticationManager:  No registered authentication handlers could satisify any requested "
247                     + "authentication methods. Unable to process authentication request.");
248
249             RequestDispatcher dispatcher = servletRequest.getRequestDispatcher(loginContext.getProfileHandlerURL());
250             dispatcher.forward(servletRequest, servletResponse);
251         }
252
253         // otherwise, forward control to the AuthenticationHandler
254         loginContext.setAuthenticationManagerURL(servletRequest.getRequestURI());
255         handler.login(servletRequest, servletResponse, loginContext);
256     }
257
258     /**
259      * Handle the "return leg" of an authentication request (i.e. clean up after an authentication handler has run).
260      * 
261      */
262     private void finishAuthnRequest(final LoginContext loginContext, final HttpServletRequest servletRequest,
263             final HttpServletResponse servletResponse) throws ServletException, IOException {
264
265         // if authentication was successful, the authentication handler should
266         // have updated the LoginContext with additional information. Use that
267         // info to create a Session.
268         if (loginContext.getAuthenticationOK()) {
269
270             AuthenticationMethodInformation authMethodInfo = new AuthenticationMethodInformationImpl(loginContext
271                     .getAuthenticationMethod(), loginContext.getAuthenticationInstant(), loginContext
272                     .getAuthenticationDuration());
273
274             InetAddress addr;
275             try {
276                 addr = InetAddress.getByName(servletRequest.getRemoteAddr());
277             } catch (Exception ex) {
278                 addr = null;
279             }
280
281             Session shibSession = (Session) sessionMgr.createSession(addr, loginContext.getUserID());
282             List<AuthenticationMethodInformation> authMethods = shibSession.getAuthenticationMethods();
283             authMethods.add(authMethodInfo);
284             loginContext.setSessionID(shibSession.getSessionID());
285         }
286
287         RequestDispatcher dispatcher = servletRequest.getRequestDispatcher(loginContext.getProfileHandlerURL());
288         dispatcher.forward(servletRequest, servletResponse);
289     }
290
291     /**
292      * "Stub" method for handling LogoutRequest.
293      */
294     private void handleLogoutRequest(final HttpServletRequest servletRequest, final HttpServletResponse servletResponse)
295             throws ServletException, IOException {
296
297     }
298
299     /**
300      * Evaluate an authenticationhandler against a set of evaluation criteria.
301      * 
302      * @param handler A candiate {@link AuthenticationHandler}
303      * @param description A description of the handler
304      * @param forceAuthN Is (re)authentication forced?
305      * @param passiveAuthN Can the AuthenticationHandler take control of the UI
306      * 
307      * @return <code>true</code> if handler meets the criteria, otherwise <code>false</code>
308      */
309     private boolean evaluateHandler(final AuthenticationHandler handler, String description, boolean forceAuthN,
310             boolean passiveAuthN) {
311
312         if (handler == null) {
313             return false;
314         }
315
316         if (forceAuthN && !handler.supportsForceAuthentication()) {
317             log.debug("AuthenticationManager: The RequestedAuthnContext required forced authentication, " + "but the "
318                     + description + " handler does not support that feature.");
319             return false;
320         }
321
322         if (passiveAuthN && !handler.supportsPassive()) {
323             log.debug("AuthenticationManager:  The RequestedAuthnContext required passive authentication, "
324                     + "but the " + description + " handler does not support that feature.");
325             return false;
326         }
327
328         return true;
329     }
330 }