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 try
262 {
263 final NodeList elementList = rootEl.getElementsByTagName(tagname);
264
265 if (elementList.getLength() > 0)
266 {
267 final Element authEl = (Element) elementList.item(0);
268 final String clazz = authEl.getAttribute("class");
269
270 final Initable initable;
271 try
272 {
273 initable = (Initable) ClassLoaderUtil.loadClass(clazz, owner.getClass()).newInstance();
274 }
275 catch (InstantiationException e)
276 {
277
278 throw new InstantiationException("Unable to instantiate class '" + clazz + "'");
279 }
280
281 initable.init(getInitParameters(authEl), owner);
282 return initable;
283 }
284 return null;
285 }
286 catch (final Exception e)
287 {
288 throw new ConfigurationException("Could not create: " + tagname, e);
289 }
290 }
291
292
293 private List<SecurityService> configureServices(final Element rootEl) throws ConfigurationException
294 {
295 final NodeList nl = rootEl.getElementsByTagName("services");
296 final List<SecurityService> result = new ArrayList<SecurityService>();
297
298 if ((nl != null) && (nl.getLength() > 0))
299 {
300 final Element servicesEl = (Element) nl.item(0);
301 final NodeList serviceList = servicesEl.getElementsByTagName("service");
302
303 for (int i = 0; i < serviceList.getLength(); i++)
304 {
305 final Element serviceEl = (Element) serviceList.item(i);
306 final String serviceClazz = serviceEl.getAttribute("class");
307
308 if ((serviceClazz == null) || "".equals(serviceClazz))
309 {
310 throw new ConfigurationException("Service element with bad class attribute");
311 }
312
313 try
314 {
315 SecurityConfigImpl.log.debug("Adding seraph service of class: " + serviceClazz);
316 final SecurityService service = (SecurityService) ClassLoaderUtil.loadClass(serviceClazz, getClass()).newInstance();
317
318 service.init(getInitParameters(serviceEl), this);
319
320 result.add(service);
321 }
322 catch (final Exception e)
323 {
324 throw new ConfigurationException("Could not getRequest service: " + serviceClazz, e);
325 }
326 }
327 }
328 return result;
329 }
330
331 private void configureInterceptors(final Element rootEl) throws ConfigurationException
332 {
333 final NodeList nl = rootEl.getElementsByTagName("interceptors");
334
335 if ((nl != null) && (nl.getLength() > 0))
336 {
337 final Element interceptorsEl = (Element) nl.item(0);
338 final NodeList interceptorList = interceptorsEl.getElementsByTagName("interceptor");
339
340 for (int i = 0; i < interceptorList.getLength(); i++)
341 {
342 final Element interceptorEl = (Element) interceptorList.item(i);
343 final String interceptorClazz = interceptorEl.getAttribute("class");
344
345 if ((interceptorClazz == null) || "".equals(interceptorClazz))
346 {
347 throw new ConfigurationException("Interceptor element with bad class attribute");
348 }
349
350 try
351 {
352 SecurityConfigImpl.log.debug("Adding interceptor of class: " + interceptorClazz);
353 final Interceptor interceptor = (Interceptor) ClassLoaderUtil.loadClass(interceptorClazz, getClass()).newInstance();
354
355 interceptor.init(getInitParameters(interceptorEl), this);
356
357 interceptors.add(interceptor);
358 }
359 catch (final Exception e)
360 {
361 throw new ConfigurationException("Could not getRequest service: " + interceptorClazz, e);
362 }
363 }
364 }
365 }
366
367
368
369
370
371
372
373
374
375 private static Map<String, String> getInitParameters(final Element el)
376 {
377 final Map<String, String> params = new HashMap<String, String>();
378 final NodeList nl = el.getElementsByTagName("init-param");
379
380 for (int i = 0; i < nl.getLength(); i++)
381 {
382 final Node initParam = nl.item(i);
383 final String paramName = XMLUtils.getContainedText(initParam, "param-name");
384 final String paramValue = XMLUtils.getContainedText(initParam, "param-value");
385 params.put(paramName, paramValue);
386 }
387 return Collections.unmodifiableMap(params);
388 }
389
390 public void destroy()
391 {
392 for (final Object element : services)
393 {
394 final SecurityService securityService = (SecurityService) element;
395 securityService.destroy();
396 }
397
398 for (final Object element : interceptors)
399 {
400 ((Interceptor) element).destroy();
401 }
402 }
403
404
405
406
407
408
409 public void addInterceptor(final Interceptor interceptor)
410 {
411 interceptors.add(interceptor);
412 }
413
414 public List<SecurityService> getServices()
415 {
416 return services;
417 }
418
419 public String getLoginURL()
420 {
421 return loginUrlStrategy.getLoginURL(this, loginURL);
422 }
423
424 public String getLinkLoginURL()
425 {
426 return loginUrlStrategy.getLinkLoginURL(this, linkLoginURL);
427 }
428
429 public String getLogoutURL()
430 {
431 return loginUrlStrategy.getLogoutURL(this, logoutURL);
432 }
433
434 public String getOriginalURLKey()
435 {
436 return originalURLKey;
437 }
438
439 public Authenticator getAuthenticator()
440 {
441 return authenticator;
442 }
443
444 public AuthenticationContext getAuthenticationContext()
445 {
446 return new AuthenticationContextImpl();
447 }
448
449 public SecurityController getController()
450 {
451 return controller;
452 }
453
454 public RoleMapper getRoleMapper()
455 {
456 return roleMapper;
457 }
458
459 public RedirectPolicy getRedirectPolicy()
460 {
461 return redirectPolicy;
462 }
463
464 public <T extends Interceptor> List<T> getInterceptors(final Class<T> desiredInterceptorClass)
465 {
466 final List<T> result = new ArrayList<T>();
467 for (final Interceptor interceptor : interceptors)
468 {
469 if (desiredInterceptorClass.isAssignableFrom(interceptor.getClass()))
470 {
471 result.add(desiredInterceptorClass.cast(interceptor));
472 }
473 }
474 return Collections.unmodifiableList(result);
475 }
476
477 public String getCookieEncoding()
478 {
479 return cookieEncoding;
480 }
481
482 public String getLoginCookiePath()
483 {
484 return loginCookiePath;
485 }
486
487 public String getLoginCookieKey()
488 {
489 return loginCookieKey;
490 }
491
492 public String getAuthType()
493 {
494 return authType;
495 }
496
497 public boolean isInsecureCookie()
498 {
499 return insecureCookie;
500 }
501
502 public int getAutoLoginCookieAge()
503 {
504 return autoLoginCookieAge;
505 }
506
507 public ElevatedSecurityGuard getElevatedSecurityGuard()
508 {
509 return elevatedSecurityGuard;
510 }
511
512
513
514
515 public RememberMeService getRememberMeService()
516 {
517 return ApplicationServicesRegistry.getRememberMeService();
518 }
519
520 public boolean isInvalidateSessionOnLogin()
521 {
522 return invalidateSessionOnLogin;
523 }
524
525 public List<String> getInvalidateSessionExcludeList()
526 {
527 return invalidateSessionExcludeList;
528 }
529 }