1 package com.atlassian.seraph.config;
2
3 import com.atlassian.seraph.Initable;
4 import com.atlassian.seraph.SecurityService;
5 import com.atlassian.seraph.auth.AuthenticationContext;
6 import com.atlassian.seraph.auth.AuthenticationContextImpl;
7 import com.atlassian.seraph.auth.Authenticator;
8 import com.atlassian.seraph.auth.RoleMapper;
9 import com.atlassian.seraph.controller.SecurityController;
10 import com.atlassian.seraph.elevatedsecurity.ElevatedSecurityGuard;
11 import com.atlassian.seraph.elevatedsecurity.NoopElevatedSecurityGuard;
12 import com.atlassian.seraph.interceptor.Interceptor;
13 import com.atlassian.seraph.ioc.ApplicationServicesRegistry;
14 import com.atlassian.seraph.service.rememberme.RememberMeService;
15 import com.atlassian.seraph.util.XMLUtils;
16 import com.opensymphony.util.ClassLoaderUtil;
17 import org.apache.log4j.Logger;
18 import org.w3c.dom.Document;
19 import org.w3c.dom.Element;
20 import org.w3c.dom.Node;
21 import org.w3c.dom.NodeList;
22 import org.xml.sax.SAXException;
23
24 import java.io.IOException;
25 import java.io.Serializable;
26 import java.net.URL;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.concurrent.CopyOnWriteArrayList;
34 import javax.xml.parsers.DocumentBuilderFactory;
35 import javax.xml.parsers.ParserConfigurationException;
36
37
38
39
40
41
42 public class SecurityConfigImpl implements Serializable, SecurityConfig
43 {
44 private static final Logger log = Logger.getLogger(SecurityConfigImpl.class);
45
46 public static final String DEFAULT_CONFIG_LOCATION = "seraph-config.xml";
47
48 private static final int YEAR_IN_SECONDS = 365 * 24 * 60 * 60;
49
50 private final Authenticator authenticator;
51 private final ElevatedSecurityGuard elevatedSecurityGuard;
52 private final RoleMapper roleMapper;
53 private final SecurityController controller;
54 private final List<SecurityService> services;
55 private final List<Interceptor> interceptors = new CopyOnWriteArrayList<Interceptor>();
56
57 private final String loginURL;
58 private final String logoutURL;
59 private final String originalURLKey;
60 private final String cookieEncoding;
61 private final String loginCookieKey;
62 private final String linkLoginURL;
63 private final String authType;
64 private RedirectPolicy redirectPolicy;
65
66 private boolean insecureCookie;
67 private final boolean invalidateSessionOnLogin;
68 private final List<String> invalidateSessionExcludeList;
69
70
71 private final int autoLoginCookieAge;
72
73 private final LoginUrlStrategy loginUrlStrategy;
74 private final String loginCookiePath;
75
76 public SecurityConfigImpl(String configFileLocation) throws ConfigurationException
77 {
78 if (configFileLocation != null)
79 {
80 if (SecurityConfigImpl.log.isDebugEnabled())
81 {
82 SecurityConfigImpl.log.debug("Config file location passed. Location: " + configFileLocation);
83 }
84 }
85 else
86 {
87 configFileLocation = "seraph-config.xml";
88 if (SecurityConfigImpl.log.isDebugEnabled())
89 {
90 SecurityConfigImpl.log.debug("Initialising securityConfig using default configFile: " + configFileLocation);
91 }
92 }
93
94 try
95 {
96 final Element rootEl = loadConfigXml(configFileLocation);
97
98 final NodeList nl = rootEl.getElementsByTagName("parameters");
99 final Element parametersEl = (Element) nl.item(0);
100 final Map<String, String> globalParams = getInitParameters(parametersEl);
101
102 loginURL = globalParams.get("login.url");
103 linkLoginURL = globalParams.get("link.login.url");
104 logoutURL = globalParams.get("logout.url");
105 cookieEncoding = globalParams.get("cookie.encoding");
106 loginCookiePath = globalParams.get("login.cookie.path");
107 authType = globalParams.get("authentication.type");
108 insecureCookie = "true".equals(globalParams.get("insecure.cookie"));
109
110 if (globalParams.get("original.url.key") != null)
111 {
112 originalURLKey = globalParams.get("original.url.key");
113 }
114 else
115 {
116 originalURLKey = "seraph_originalurl";
117 }
118
119 if (globalParams.get("login.cookie.key") != null)
120 {
121 loginCookieKey = globalParams.get("login.cookie.key");
122 }
123 else
124 {
125 loginCookieKey = "seraph.os.cookie";
126 }
127
128 if (globalParams.get("autologin.cookie.age") != null)
129 {
130 autoLoginCookieAge = Integer.parseInt(globalParams.get("autologin.cookie.age"));
131 }
132 else
133 {
134 autoLoginCookieAge = SecurityConfigImpl.YEAR_IN_SECONDS;
135 }
136
137 if (globalParams.get("invalidate.session.on.login") != null)
138 {
139 invalidateSessionOnLogin = "true".equalsIgnoreCase(globalParams.get("invalidate.session.on.login"));
140 if (globalParams.get("invalidate.session.exclude.list") != null)
141 {
142 final String[] excludes = globalParams.get("invalidate.session.exclude.list").split(",");
143 invalidateSessionExcludeList = Arrays.asList(excludes);
144 }
145 else
146 {
147 invalidateSessionExcludeList = Collections.emptyList();
148 }
149 }
150 else
151 {
152 invalidateSessionOnLogin = false;
153 invalidateSessionExcludeList = Collections.emptyList();
154 }
155
156
157
158
159 authenticator = configureAuthenticator(rootEl);
160 controller = configureController(rootEl);
161 roleMapper = configureRoleMapper(rootEl);
162 services = Collections.unmodifiableList(configureServices(rootEl));
163 configureInterceptors(rootEl);
164 loginUrlStrategy = configureLoginUrlStrategy(rootEl);
165 configureRedirectPolicy(rootEl);
166 elevatedSecurityGuard = configureElevatedSecurityGuard(rootEl);
167 }
168 catch (final Exception ex)
169 {
170 throw new ConfigurationException("Exception configuring from '" + configFileLocation + "'.", ex);
171 }
172 }
173
174 private Element loadConfigXml(final String configFileLocation)
175 throws SAXException, IOException, ParserConfigurationException
176 {
177 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
178 final URL fileUrl = ClassLoaderUtil.getResource(configFileLocation, getClass());
179
180 if (fileUrl == null)
181 {
182 throw new IllegalArgumentException("No such XML file: " + configFileLocation);
183 }
184
185
186 final Document doc = factory.newDocumentBuilder().parse(fileUrl.toString());
187 return doc.getDocumentElement();
188 }
189
190 protected void configureRedirectPolicy(final Element rootEl) throws ConfigurationException
191 {
192
193 redirectPolicy = (RedirectPolicy) configureClass(rootEl, "redirect-policy", this);
194
195
196 if (redirectPolicy == null)
197 {
198 redirectPolicy = new DefaultRedirectPolicy();
199 }
200 }
201
202 private LoginUrlStrategy configureLoginUrlStrategy(final Element rootEl) throws ConfigurationException
203 {
204 LoginUrlStrategy loginUrlStrategy = (LoginUrlStrategy) configureClass(rootEl, "login-url-strategy", this);
205
206 if (loginUrlStrategy == null)
207 {
208 loginUrlStrategy = new DefaultLoginUrlStrategy();
209 }
210 return loginUrlStrategy;
211 }
212
213 private Authenticator configureAuthenticator(final Element rootEl) throws ConfigurationException
214 {
215 Authenticator authenticator = (Authenticator) configureClass(rootEl, "authenticator", this);
216
217 if (authenticator == null)
218 {
219
220 throw new ConfigurationException("No authenticator implementation was configured in SecurityConfig.");
221 }
222 return authenticator;
223 }
224
225 private ElevatedSecurityGuard configureElevatedSecurityGuard(final Element rootEl) throws ConfigurationException
226 {
227 ElevatedSecurityGuard elevatedSecurityGuard = (ElevatedSecurityGuard) configureClass(rootEl, "elevatedsecurityguard", this);
228 if (elevatedSecurityGuard == null)
229 {
230 elevatedSecurityGuard = NoopElevatedSecurityGuard.INSTANCE;
231 }
232 return elevatedSecurityGuard;
233 }
234
235 private SecurityController configureController(final Element rootEl) throws ConfigurationException
236 {
237 SecurityController controller = (SecurityController) configureClass(rootEl, "controller", this);
238
239 try
240 {
241 if (controller == null)
242 {
243 controller = (SecurityController) ClassLoaderUtil.loadClass(SecurityController.NULL_CONTROLLER, getClass()).newInstance();
244 }
245 }
246 catch (final Exception e)
247 {
248 throw new ConfigurationException("Could not lookup class: " + SecurityController.NULL_CONTROLLER, e);
249 }
250 return controller;
251 }
252
253 private RoleMapper configureRoleMapper(final Element rootEl) throws ConfigurationException
254 {
255 return (RoleMapper) configureClass(rootEl, "rolemapper", this);
256 }
257
258 private static Initable configureClass(final Element rootEl, final String tagname, final SecurityConfig owner)
259 throws ConfigurationException
260 {
261 final NodeList elementList = rootEl.getElementsByTagName(tagname);
262
263 if (elementList.getLength() == 0)
264 {
265 return null;
266 }
267 final Element authEl = (Element) elementList.item(0);
268 final String clazz = authEl.getAttribute("class");
269 if (clazz == null || clazz.trim().length() == 0)
270 {
271 return null;
272 }
273
274 final Initable initable;
275 try
276 {
277 initable = (Initable) ClassLoaderUtil.loadClass(clazz, owner.getClass()).newInstance();
278 }
279 catch (InstantiationException ex)
280 {
281
282 throw new ConfigurationException("Unable to instantiate class '" + clazz + "'", ex);
283 }
284 catch (Exception ex)
285 {
286 final String message = "Unable to load " + tagname + " class '" + clazz + "': " + ex.getMessage();
287
288 log.error(message, ex);
289 throw new ConfigurationException(message, ex);
290 }
291
292 try
293 {
294 initable.init(getInitParameters(authEl), owner);
295 return initable;
296 }
297 catch (Exception ex)
298 {
299 final String message = "Error caught in initialisation of " + tagname + " class '" + clazz + "': " + ex.getMessage();
300
301 log.error(message, ex);
302 throw new ConfigurationException(message, ex);
303 }
304 }
305
306
307 private List<SecurityService> configureServices(final Element rootEl) throws ConfigurationException
308 {
309 final NodeList nl = rootEl.getElementsByTagName("services");
310 final List<SecurityService> result = new ArrayList<SecurityService>();
311
312 if ((nl != null) && (nl.getLength() > 0))
313 {
314 final Element servicesEl = (Element) nl.item(0);
315 final NodeList serviceList = servicesEl.getElementsByTagName("service");
316
317 for (int i = 0; i < serviceList.getLength(); i++)
318 {
319 final Element serviceEl = (Element) serviceList.item(i);
320 final String serviceClazz = serviceEl.getAttribute("class");
321
322 if ((serviceClazz == null) || "".equals(serviceClazz))
323 {
324 throw new ConfigurationException("Service element with bad class attribute");
325 }
326
327 try
328 {
329 SecurityConfigImpl.log.debug("Adding seraph service of class: " + serviceClazz);
330 final SecurityService service = (SecurityService) ClassLoaderUtil.loadClass(serviceClazz, getClass()).newInstance();
331
332 service.init(getInitParameters(serviceEl), this);
333
334 result.add(service);
335 }
336 catch (final Exception e)
337 {
338 throw new ConfigurationException("Could not getRequest service: " + serviceClazz, e);
339 }
340 }
341 }
342 return result;
343 }
344
345 private void configureInterceptors(final Element rootEl) throws ConfigurationException
346 {
347 final NodeList nl = rootEl.getElementsByTagName("interceptors");
348
349 if ((nl != null) && (nl.getLength() > 0))
350 {
351 final Element interceptorsEl = (Element) nl.item(0);
352 final NodeList interceptorList = interceptorsEl.getElementsByTagName("interceptor");
353
354 for (int i = 0; i < interceptorList.getLength(); i++)
355 {
356 final Element interceptorEl = (Element) interceptorList.item(i);
357 final String interceptorClazz = interceptorEl.getAttribute("class");
358
359 if ((interceptorClazz == null) || "".equals(interceptorClazz))
360 {
361 throw new ConfigurationException("Interceptor element with bad class attribute");
362 }
363
364 try
365 {
366 SecurityConfigImpl.log.debug("Adding interceptor of class: " + interceptorClazz);
367 final Interceptor interceptor = (Interceptor) ClassLoaderUtil.loadClass(interceptorClazz, getClass()).newInstance();
368
369 interceptor.init(getInitParameters(interceptorEl), this);
370
371 interceptors.add(interceptor);
372 }
373 catch (final Exception e)
374 {
375 throw new ConfigurationException("Could not getRequest service: " + interceptorClazz, e);
376 }
377 }
378 }
379 }
380
381
382
383
384
385
386
387
388
389 private static Map<String, String> getInitParameters(final Element el)
390 {
391 final Map<String, String> params = new HashMap<String, String>();
392 final NodeList nl = el.getElementsByTagName("init-param");
393
394 for (int i = 0; i < nl.getLength(); i++)
395 {
396 final Node initParam = nl.item(i);
397 final String paramName = XMLUtils.getContainedText(initParam, "param-name");
398 final String paramValue = XMLUtils.getContainedText(initParam, "param-value");
399 params.put(paramName, paramValue);
400 }
401 return Collections.unmodifiableMap(params);
402 }
403
404 public void destroy()
405 {
406 for (final Object element : services)
407 {
408 final SecurityService securityService = (SecurityService) element;
409 securityService.destroy();
410 }
411
412 for (final Object element : interceptors)
413 {
414 ((Interceptor) element).destroy();
415 }
416 }
417
418
419
420
421
422
423 public void addInterceptor(final Interceptor interceptor)
424 {
425 interceptors.add(interceptor);
426 }
427
428 public List<SecurityService> getServices()
429 {
430 return services;
431 }
432
433 public String getLoginURL()
434 {
435 return loginUrlStrategy.getLoginURL(this, loginURL);
436 }
437
438 public String getLinkLoginURL()
439 {
440 return loginUrlStrategy.getLinkLoginURL(this, linkLoginURL);
441 }
442
443 public String getLogoutURL()
444 {
445 return loginUrlStrategy.getLogoutURL(this, logoutURL);
446 }
447
448 public String getOriginalURLKey()
449 {
450 return originalURLKey;
451 }
452
453 public Authenticator getAuthenticator()
454 {
455 return authenticator;
456 }
457
458 public AuthenticationContext getAuthenticationContext()
459 {
460 return new AuthenticationContextImpl();
461 }
462
463 public SecurityController getController()
464 {
465 return controller;
466 }
467
468 public RoleMapper getRoleMapper()
469 {
470 return roleMapper;
471 }
472
473 public RedirectPolicy getRedirectPolicy()
474 {
475 return redirectPolicy;
476 }
477
478 public <T extends Interceptor> List<T> getInterceptors(final Class<T> desiredInterceptorClass)
479 {
480 final List<T> result = new ArrayList<T>();
481 for (final Interceptor interceptor : interceptors)
482 {
483 if (desiredInterceptorClass.isAssignableFrom(interceptor.getClass()))
484 {
485 result.add(desiredInterceptorClass.cast(interceptor));
486 }
487 }
488 return Collections.unmodifiableList(result);
489 }
490
491 public String getCookieEncoding()
492 {
493 return cookieEncoding;
494 }
495
496 public String getLoginCookiePath()
497 {
498 return loginCookiePath;
499 }
500
501 public String getLoginCookieKey()
502 {
503 return loginCookieKey;
504 }
505
506 public String getAuthType()
507 {
508 return authType;
509 }
510
511 public boolean isInsecureCookie()
512 {
513 return insecureCookie;
514 }
515
516 public int getAutoLoginCookieAge()
517 {
518 return autoLoginCookieAge;
519 }
520
521 public ElevatedSecurityGuard getElevatedSecurityGuard()
522 {
523 return elevatedSecurityGuard;
524 }
525
526
527
528
529 public RememberMeService getRememberMeService()
530 {
531 return ApplicationServicesRegistry.getRememberMeService();
532 }
533
534 public boolean isInvalidateSessionOnLogin()
535 {
536 return invalidateSessionOnLogin;
537 }
538
539 public List<String> getInvalidateSessionExcludeList()
540 {
541 return invalidateSessionExcludeList;
542 }
543 }