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