fix NPE
[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.xml.util.Pair;
31 import org.springframework.context.ApplicationContext;
32
33 import edu.internet2.middleware.shibboleth.common.config.BaseReloadableService;
34 import edu.internet2.middleware.shibboleth.common.profile.AbstractErrorHandler;
35 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandler;
36 import edu.internet2.middleware.shibboleth.common.profile.ProfileHandlerManager;
37 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractRequestURIMappedProfileHandler;
38 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
39 import edu.internet2.middleware.shibboleth.idp.authn.LoginContext;
40
41 /**
42  * Implementation of a {@link ProfileHandlerManager} that maps the request path, without the servlet context, to a
43  * profile handler and adds support for authentication handlers.
44  */
45 public class IdPProfileHandlerManager extends BaseReloadableService implements ProfileHandlerManager {
46
47     /** Class logger. */
48     private final Logger log = Logger.getLogger(IdPProfileHandlerManager.class);
49
50     /** Handler used for errors. */
51     private AbstractErrorHandler errorHandler;
52
53     /** Map of request paths to profile handlers. */
54     private Map<String, AbstractRequestURIMappedProfileHandler> profileHandlers;
55
56     /** Map of authentication methods to authentication handlers. */
57     private Map<String, LoginHandler> authenticationHandlers;
58
59     /**
60      * Constructor. Configuration resources are not monitored for changes.
61      * 
62      * @param configurations configuration resources for this service
63      */
64     public IdPProfileHandlerManager(List<Resource> configurations) {
65         super(configurations);
66         profileHandlers = new HashMap<String, AbstractRequestURIMappedProfileHandler>();
67         authenticationHandlers = new HashMap<String, LoginHandler>();
68     }
69
70     /**
71      * Constructor.
72      * 
73      * @param timer timer resource polling tasks are scheduled with
74      * @param configurations configuration resources for this service
75      * @param pollingFrequency the frequency, in milliseconds, to poll the policy resources for changes, must be greater
76      *            than zero
77      */
78     public IdPProfileHandlerManager(List<Resource> configurations, Timer timer, long pollingFrequency) {
79         super(timer, configurations, pollingFrequency);
80         profileHandlers = new HashMap<String, AbstractRequestURIMappedProfileHandler>();
81         authenticationHandlers = new HashMap<String, LoginHandler>();
82     }
83
84     /** {@inheritDoc} */
85     public AbstractErrorHandler getErrorHandler() {
86         return errorHandler;
87     }
88
89     /**
90      * Sets the error handler.
91      * 
92      * @param handler error handler
93      */
94     public void setErrorHandler(AbstractErrorHandler handler) {
95         if (handler == null) {
96             throw new IllegalArgumentException("Error handler may not be null");
97         }
98         errorHandler = handler;
99     }
100
101     /** {@inheritDoc} */
102     public ProfileHandler getProfileHandler(ServletRequest request) {
103         ProfileHandler handler;
104
105         String requestPath = ((HttpServletRequest) request).getPathInfo();
106         if (log.isDebugEnabled()) {
107             log.debug(getId() + ": Looking up profile handler for request path: " + requestPath);
108         }
109         Lock readLock = getReadWriteLock().readLock();
110         readLock.lock();
111         handler = profileHandlers.get(requestPath);
112         readLock.unlock();
113
114         if (handler != null) {
115             if (log.isDebugEnabled()) {
116                 log.debug(getId() + ": Located profile handler of the following type for request path "
117                         + requestPath + ": " + handler.getClass().getName());
118             }
119         } else {
120             if (log.isDebugEnabled()) {
121                 log.debug(getId() + ": No profile handler registered for request path " + requestPath);
122             }
123         }
124         return handler;
125     }
126
127     /**
128      * Gets the registered profile handlers.
129      * 
130      * @return registered profile handlers
131      */
132     public Map<String, AbstractRequestURIMappedProfileHandler> getProfileHandlers() {
133         return profileHandlers;
134     }
135
136     /**
137      * Gets the authentication handler appropriate for the given loging context. The mechanism used to determine the
138      * "appropriate" handler is implementation specific.
139      * 
140      * @param loginContext current login context
141      * 
142      * @return authentication method URI and handler appropriate for given login context
143      */
144     public Pair<String, LoginHandler> getAuthenticationHandler(LoginContext loginContext) {
145         if (loginContext == null) {
146             return null;
147         }
148         
149         if (log.isDebugEnabled()) {
150             log.debug(getId() + ": Looking up authentication method for relying party "
151                     + loginContext.getRelyingPartyId());
152         }
153         List<String> requestedMethods = loginContext.getRequestedAuthenticationMethods();
154         if (requestedMethods != null) {
155             LoginHandler candidateHandler;
156             for (String requestedMethod : requestedMethods) {
157                 if (log.isDebugEnabled()) {
158                     log.debug(getId() + ": Checking for authentication handler for method " + requestedMethod
159                             + " which was requested for relying party " + loginContext.getRelyingPartyId());
160                 }
161                 candidateHandler = authenticationHandlers.get(requestedMethod);
162                 if (candidateHandler != null) {
163                     if (log.isDebugEnabled()) {
164                         log.debug(getId() + ": Authentication handler for method " + requestedMethod
165                                 + " for relying party " + loginContext.getRelyingPartyId()
166                                 + " found.  Checking if it meets othe criteria.");
167                     }
168                     if(loginContext.getPassiveAuth() && !candidateHandler.supportsPassive()){
169                         if (log.isDebugEnabled()) {
170                             log.debug(getId() + ": Authentication handler for method " + requestedMethod
171                                     + " for relying party " + loginContext.getRelyingPartyId()
172                                     + " does not meet required support for passive auth.  Skipping it");
173                         }
174                         continue;
175                     }
176                     
177                     if (log.isDebugEnabled()) {
178                         log.debug(getId() + ": Authentication handler for method " + requestedMethod
179                                 + " for relying party " + loginContext.getRelyingPartyId()
180                                 + " meets all requirements, using it.");
181                     }
182                     return new Pair<String, LoginHandler>(requestedMethod, candidateHandler);
183                 }
184             }
185         } else {
186             log.error(getId() + ": No requested authentication methods for relying party "
187                     + loginContext.getRelyingPartyId());
188         }
189
190         return null;
191     }
192
193     /**
194      * Gets the registered authentication handlers.
195      * 
196      * @return registered authentication handlers
197      */
198     public Map<String, LoginHandler> getAuthenticationHandlers() {
199         return authenticationHandlers;
200     }
201
202     /** {@inheritDoc} */
203     protected void newContextCreated(ApplicationContext newServiceContext) {
204         if (log.isDebugEnabled()) {
205             log.debug(getId() + ": Loading new configuration into service");
206         }
207         Lock writeLock = getReadWriteLock().writeLock();
208         writeLock.lock();
209         loadNewErrorHandler(newServiceContext);
210         loadNewProfileHandlers(newServiceContext);
211         loadNewAuthenticationHandlers(newServiceContext);
212         writeLock.unlock();
213     }
214
215     /**
216      * Reads the new error handler from the newly created application context and loads it into this manager.
217      * 
218      * @param newServiceContext newly created application context
219      */
220     protected void loadNewErrorHandler(ApplicationContext newServiceContext) {
221         String[] errorBeanNames = newServiceContext.getBeanNamesForType(AbstractErrorHandler.class);
222         if (log.isDebugEnabled()) {
223             log.debug(getId() + ": Loading " + errorBeanNames.length + " new error handler.");
224         }
225
226         errorHandler = (AbstractErrorHandler) newServiceContext.getBean(errorBeanNames[0]);
227         if (log.isDebugEnabled()) {
228             log.debug(getId() + ": Loaded new error handler of type: " + errorHandler.getClass().getName());
229         }
230     }
231
232     /**
233      * Reads the new profile handlers from the newly created application context and loads it into this manager.
234      * 
235      * @param newServiceContext newly created application context
236      */
237     protected void loadNewProfileHandlers(ApplicationContext newServiceContext) {
238         String[] profileBeanNames = newServiceContext.getBeanNamesForType(AbstractRequestURIMappedProfileHandler.class);
239         if (log.isDebugEnabled()) {
240             log.debug(getId() + ": Loading " + profileBeanNames.length + " new profile handlers.");
241         }
242
243         profileHandlers.clear();
244         AbstractRequestURIMappedProfileHandler<?,?> profileHandler;
245         for (String profileBeanName : profileBeanNames) {
246             profileHandler = (AbstractRequestURIMappedProfileHandler) newServiceContext.getBean(profileBeanName);
247             for (String requestPath : profileHandler.getRequestPaths()) {
248                 profileHandlers.put(requestPath, profileHandler);
249                 if (log.isDebugEnabled()) {
250                     log.debug(getId() + ": Loaded profile handler of type "
251                                     + profileHandler.getClass().getName() + " handling requests to request path "
252                                     + requestPath);
253                 }
254             }
255         }
256     }
257
258     /**
259      * Reads the new authentication handlers from the newly created application context and loads it into this manager.
260      * 
261      * @param newServiceContext newly created application context
262      */
263     protected void loadNewAuthenticationHandlers(ApplicationContext newServiceContext) {
264         String[] authnBeanNames = newServiceContext.getBeanNamesForType(LoginHandler.class);
265         if (log.isDebugEnabled()) {
266             log.debug(getId() + ": Loading " + authnBeanNames.length + " new authentication handlers.");
267         }
268
269         authenticationHandlers.clear();
270         LoginHandler authnHandler;
271         for (String authnBeanName : authnBeanNames) {
272             authnHandler = (LoginHandler) newServiceContext.getBean(authnBeanName);
273             if (log.isDebugEnabled()) {
274                 log.debug(getId() + ": Loading authentication handler of type "
275                         + authnHandler.getClass().getName() + " supporting authentication methods: "
276                         + authnHandler.getSupportedAuthenticationMethods());
277             }
278             for (String authnMethod : authnHandler.getSupportedAuthenticationMethods()) {
279                 authenticationHandlers.put(authnMethod, authnHandler);
280             }
281         }
282     }
283 }