More work on SSO, now with basic unit tests (which don't work quite yet, but close)
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / IdPProfileHandlerManager.java
1 /*
2  * Copyright [2007] [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.profile;
18
19 import java.util.HashMap;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Timer;
23 import java.util.concurrent.locks.Lock;
24
25 import javax.servlet.ServletRequest;
26 import javax.servlet.http.HttpServletRequest;
27
28 import org.apache.log4j.Logger;
29 import org.opensaml.util.resource.Resource;
30 import org.opensaml.util.resource.ResourceException;
31 import org.opensaml.xml.util.Pair;
32 import org.springframework.context.ApplicationContext;
33
34 import edu.internet2.middleware.shibboleth.common.config.BaseReloadableService;
35 import edu.internet2.middleware.shibboleth.common.profile.AbstractErrorHandler;
36 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
37 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandlerManager;
38 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractRequestURIMappedProfileHandler;
39 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationHandler;
40 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
41
42 /**
43  * Implementation of a {@link ProfileHandlerManager} that maps the request path, without the servlet context, to a
44  * profile handler and adds support for authentication handlers.
45  */
46 public class IdPProfileHandlerManager extends BaseReloadableService implements ProfileHandlerManager {
47
48     /** URI of the default authentication method. */
49     public static final String DEFAULT_AUTHEN_METHOD_URI = "urn:mace:shibboleth:2.0:idp:authentication:method:default";
50
51     /** Class logger. */
52     private final Logger log = Logger.getLogger(IdPProfileHandlerManager.class);
53
54     /** Handler used for errors. */
55     private AbstractErrorHandler errorHandler;
56
57     /** Map of request paths to profile handlers. */
58     private Map<String, AbstractRequestURIMappedProfileHandler> profileHandlers;
59
60     /** Map of authentication methods to authentication handlers. */
61     private Map<String, AuthenticationHandler> authenticationHandlers;
62
63     /**
64      * Constructor. Configuration resources are not monitored for changes.
65      * 
66      * @param configurations configuration resources for this service
67      */
68     public IdPProfileHandlerManager(List<Resource> configurations) {
69         super(configurations);
70         profileHandlers = new HashMap<String, AbstractRequestURIMappedProfileHandler>();
71         authenticationHandlers = new HashMap<String, AuthenticationHandler>();
72     }
73
74     /**
75      * Constructor.
76      * 
77      * @param timer timer resource polling tasks are scheduled with
78      * @param configurations configuration resources for this service
79      * @param pollingFrequency the frequency, in milliseconds, to poll the policy resources for changes, must be greater
80      *            than zero
81      */
82     public IdPProfileHandlerManager(List<Resource> configurations, Timer timer, long pollingFrequency) {
83         super(timer, configurations, pollingFrequency);
84         profileHandlers = new HashMap<String, AbstractRequestURIMappedProfileHandler>();
85         authenticationHandlers = new HashMap<String, AuthenticationHandler>();
86     }
87
88     /** {@inheritDoc} */
89     public AbstractErrorHandler getErrorHandler() {
90         return errorHandler;
91     }
92
93     /**
94      * Sets the error handler.
95      * 
96      * @param handler error handler
97      */
98     public void setErrorHandler(AbstractErrorHandler handler) {
99         if (handler == null) {
100             throw new IllegalArgumentException("Error handler may not be null");
101         }
102         errorHandler = handler;
103     }
104
105     /** {@inheritDoc} */
106     public ProfileHandler getProfileHandler(ServletRequest request) {
107         ProfileHandler handler;
108
109         String requestPath = ((HttpServletRequest) request).getPathInfo();
110         if (log.isDebugEnabled()) {
111             log.debug("Looking up profile handler for request path: " + requestPath);
112         }
113         Lock readLock = getReadWriteLock().readLock();
114         readLock.lock();
115         handler = profileHandlers.get(requestPath);
116         readLock.unlock();
117
118         if (handler != null) {
119             if (log.isDebugEnabled()) {
120                 log.debug("Located profile handler of the following type for request path " + requestPath + ": "
121                         + handler.getClass().getName());
122             }
123         } else {
124             if (log.isDebugEnabled()) {
125                 log.debug("No profile handler registered for request path " + requestPath);
126             }
127         }
128         return handler;
129     }
130
131     /**
132      * Gets the registered profile handlers.
133      * 
134      * @return registered profile handlers
135      */
136     public Map<String, AbstractRequestURIMappedProfileHandler> getProfileHandlers() {
137         return profileHandlers;
138     }
139
140     /**
141      * Gets the authentication handler appropriate for the given loging context. The mechanism used to determine the
142      * "appropriate" handler is implementation specific.
143      * 
144      * @param loginContext current login context
145      * 
146      * @return authentication method URI and handler appropriate for given login context
147      */
148     public Pair<String, AuthenticationHandler> getAuthenticationHandler(LoginContext loginContext) {
149         List<String> requestedMethods = loginContext.getRequestedAuthenticationMethods();
150         if (requestedMethods != null) {
151             AuthenticationHandler candidateHandler;
152             for (String requestedMethod : requestedMethods) {
153                 candidateHandler = authenticationHandlers.get(requestedMethod);
154                 if (candidateHandler != null) {
155                     if (loginContext.getPassiveAuth() && candidateHandler.supportsPassive()) {
156                         return new Pair<String, AuthenticationHandler>(requestedMethod, candidateHandler);
157                     }
158                 }
159             }
160         }
161
162         Pair<String, AuthenticationHandler> authenticationHandler = new Pair<String, AuthenticationHandler>(
163                 DEFAULT_AUTHEN_METHOD_URI, authenticationHandlers.get(DEFAULT_AUTHEN_METHOD_URI));
164         return authenticationHandler;
165     }
166
167     /**
168      * Gets the registered authentication handlers.
169      * 
170      * @return registered authentication handlers
171      */
172     public Map<String, AuthenticationHandler> getAuthenticationHandlers() {
173         return authenticationHandlers;
174     }
175
176     /** {@inheritDoc} */
177     protected void newContextCreated(ApplicationContext newServiceContext) throws ResourceException {
178         if (log.isDebugEnabled()) {
179             log.debug("Loading new configuration into service");
180         }
181         String[] errorBeanNames = newServiceContext.getBeanNamesForType(AbstractErrorHandler.class);
182         String[] profileBeanNames = newServiceContext.getBeanNamesForType(AbstractRequestURIMappedProfileHandler.class);
183         String[] authnBeanNames = newServiceContext.getBeanNamesForType(AuthenticationHandler.class);
184
185         Lock writeLock = getReadWriteLock().writeLock();
186         writeLock.lock();
187
188         errorHandler = (AbstractErrorHandler) newServiceContext.getBean(errorBeanNames[0]);
189         if (log.isDebugEnabled()) {
190             log.debug("Loaded new error handler of type: " + errorHandler.getClass().getName());
191         }
192
193         profileHandlers.clear();
194         if (log.isDebugEnabled()) {
195             log.debug(profileBeanNames.length + " profile handlers loaded");
196         }
197         AbstractRequestURIMappedProfileHandler profileHandler;
198         for (String profileBeanName : profileBeanNames) {
199             profileHandler = (AbstractRequestURIMappedProfileHandler) newServiceContext.getBean(profileBeanName);
200             for (String requestPath : profileHandler.getRequestPaths()) {
201                 profileHandlers.put(requestPath, profileHandler);
202                 if (log.isDebugEnabled()) {
203                     log.debug("Request path " + requestPath + " mapped to profile handler of type: "
204                             + profileHandler.getClass().getName());
205                 }
206             }
207         }
208
209         authenticationHandlers.clear();
210         if (log.isDebugEnabled()) {
211             log.debug(authnBeanNames.length + " authentication handlers loaded");
212         }
213         AuthenticationHandler authnHandler;
214         for (String authnBeanName : authnBeanNames) {
215             authnHandler = (AuthenticationHandler) newServiceContext.getBean(authnBeanName);
216             for (String authnMethod : authnHandler.getSupportedAuthenticationMethods()) {
217                 authenticationHandlers.put(authnMethod, authnHandler);
218                 if (log.isDebugEnabled()) {
219                     log.debug("Authentication method " + authnMethod + " mapped to authentication handler of type: "
220                             + authnHandler.getClass().getName());
221                 }
222             }
223         }
224
225         writeLock.unlock();
226     }
227 }