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