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.cookie.CookieFactory;
11 import com.atlassian.seraph.elevatedsecurity.ElevatedSecurityGuard;
12 import com.atlassian.seraph.elevatedsecurity.NoopElevatedSecurityGuard;
13 import com.atlassian.seraph.interceptor.Interceptor;
14 import com.atlassian.seraph.ioc.ComponentLocator;
15 import com.atlassian.seraph.ioc.NoopComponentLocator;
16 import com.atlassian.seraph.service.rememberme.NoopRememberMeService;
17 import com.atlassian.seraph.service.rememberme.RememberMeService;
18 import com.atlassian.seraph.util.XMLUtils;
19 import com.opensymphony.util.ClassLoaderUtil;
20 import org.apache.log4j.Logger;
21 import org.w3c.dom.Document;
22 import org.w3c.dom.Element;
23 import org.w3c.dom.Node;
24 import org.w3c.dom.NodeList;
25 import org.xml.sax.SAXException;
26
27 import java.io.IOException;
28 import java.io.Serializable;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.concurrent.CopyOnWriteArrayList;
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.parsers.ParserConfigurationException;
38
39
40
41
42
43
44 public class SecurityConfigImpl implements Serializable, SecurityConfig
45 {
46 private static final Logger log = Logger.getLogger(SecurityConfigImpl.class);
47
48 public static final String DEFAULT_CONFIG_LOCATION = "seraph-config.xml";
49
50 private static final int YEAR_IN_SECONDS = 365 * 24 * 60 * 60;
51
52 private final Authenticator authenticator;
53 private final ElevatedSecurityGuard elevatedSecurityGuard;
54 private final ComponentLocator componentLocator;
55 private final RoleMapper roleMapper;
56 private final SecurityController controller;
57 private final List<SecurityService> services;
58 private final List<Interceptor> interceptors = new CopyOnWriteArrayList<Interceptor>();
59
60 private final String loginURL;
61 private final String logoutURL;
62 private final String originalURLKey;
63 private final String cookieEncoding;
64 private final String loginCookieKey;
65 private final String linkLoginURL;
66 private final String authType;
67 private RedirectPolicy redirectPolicy;
68
69 private boolean insecureCookie;
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
138
139
140 authenticator = configureAuthenticator(rootEl);
141 controller = configureController(rootEl);
142 roleMapper = configureRoleMapper(rootEl);
143 services = Collections.unmodifiableList(configureServices(rootEl));
144 configureInterceptors(rootEl);
145 loginUrlStrategy = configureLoginUrlStrategy(rootEl);
146 configureRedirectPolicy(rootEl);
147 CookieFactory.init(this);
148 elevatedSecurityGuard = configureElevatedSecurityGuard(rootEl);
149 componentLocator = configureComponentLocator(rootEl);
150
151 }
152 catch (final Exception e)
153 {
154 throw new ConfigurationException("Exception configuring from '" + configFileLocation, e);
155 }
156 }
157
158 private Element loadConfigXml(final String configFileLocation)
159 throws SAXException, IOException, ParserConfigurationException
160 {
161 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
162 final URL fileUrl = ClassLoaderUtil.getResource(configFileLocation, getClass());
163
164 if (fileUrl == null)
165 {
166 throw new IllegalArgumentException("No such XML file: " + configFileLocation);
167 }
168
169
170 final Document doc = factory.newDocumentBuilder().parse(fileUrl.toString());
171 return doc.getDocumentElement();
172 }
173
174 protected void configureRedirectPolicy(final Element rootEl) throws ConfigurationException
175 {
176
177 redirectPolicy = (RedirectPolicy) configureClass(rootEl, "redirect-policy", this);
178
179
180 if (redirectPolicy == null)
181 {
182 redirectPolicy = new DefaultRedirectPolicy();
183 }
184 }
185
186 private LoginUrlStrategy configureLoginUrlStrategy(final Element rootEl) throws ConfigurationException
187 {
188 LoginUrlStrategy loginUrlStrategy = (LoginUrlStrategy) configureClass(rootEl, "login-url-strategy", this);
189
190 if (loginUrlStrategy == null)
191 {
192 loginUrlStrategy = new DefaultLoginUrlStrategy();
193 }
194 return loginUrlStrategy;
195 }
196
197 private Authenticator configureAuthenticator(final Element rootEl) throws ConfigurationException
198 {
199 Authenticator authenticator = (Authenticator) configureClass(rootEl, "authenticator", this);
200
201 try
202 {
203 if (authenticator == null)
204 {
205 authenticator = (Authenticator) ClassLoaderUtil.loadClass(Authenticator.DEFAULT_AUTHENTICATOR, getClass()).newInstance();
206 authenticator.init(Collections.<String, String>emptyMap(), this);
207 }
208 }
209 catch (final Exception e)
210 {
211 throw new ConfigurationException("Could not lookup class: " + Authenticator.DEFAULT_AUTHENTICATOR, e);
212 }
213 return authenticator;
214 }
215
216 private ElevatedSecurityGuard configureElevatedSecurityGuard(final Element rootEl) throws ConfigurationException
217 {
218 ElevatedSecurityGuard elevatedSecurityGuard = (ElevatedSecurityGuard) configureClass(rootEl, "elevatedsecurityguard", this);
219 if (elevatedSecurityGuard == null)
220 {
221 elevatedSecurityGuard = NoopElevatedSecurityGuard.INSTANCE;
222 }
223 return elevatedSecurityGuard;
224 }
225
226 private ComponentLocator configureComponentLocator(final Element rootEl) throws ConfigurationException
227 {
228 ComponentLocator componentLocator = (ComponentLocator) configureClass(rootEl, "componentlocator", this);
229 if (componentLocator == null)
230 {
231 componentLocator = NoopComponentLocator.INSTANCE;
232 }
233 return componentLocator;
234 }
235
236 private SecurityController configureController(final Element rootEl) throws ConfigurationException
237 {
238 SecurityController controller = (SecurityController) configureClass(rootEl, "controller", this);
239
240 try
241 {
242 if (controller == null)
243 {
244 controller = (SecurityController) ClassLoaderUtil.loadClass(SecurityController.NULL_CONTROLLER, getClass()).newInstance();
245 }
246 }
247 catch (final Exception e)
248 {
249 throw new ConfigurationException("Could not lookup class: " + SecurityController.NULL_CONTROLLER, e);
250 }
251 return controller;
252 }
253
254 private RoleMapper configureRoleMapper(final Element rootEl) throws ConfigurationException
255 {
256 return (RoleMapper) configureClass(rootEl, "rolemapper", this);
257 }
258
259 private static Initable configureClass(final Element rootEl, final String tagname, final SecurityConfig owner)
260 throws ConfigurationException
261 {
262 try
263 {
264 final NodeList elementList = rootEl.getElementsByTagName(tagname);
265
266 if (elementList.getLength() > 0)
267 {
268 final Element authEl = (Element) elementList.item(0);
269 final String clazz = authEl.getAttribute("class");
270
271 final Initable initable = (Initable) ClassLoaderUtil.loadClass(clazz, owner.getClass()).newInstance();
272
273 initable.init(getInitParameters(authEl), owner);
274 return initable;
275 }
276 return null;
277 }
278 catch (final Exception e)
279 {
280 throw new ConfigurationException("Could not create: " + tagname, e);
281 }
282 }
283
284
285 private List<SecurityService> configureServices(final Element rootEl) throws ConfigurationException
286 {
287 final NodeList nl = rootEl.getElementsByTagName("services");
288 final List<SecurityService> result = new ArrayList<SecurityService>();
289
290 if ((nl != null) && (nl.getLength() > 0))
291 {
292 final Element servicesEl = (Element) nl.item(0);
293 final NodeList serviceList = servicesEl.getElementsByTagName("service");
294
295 for (int i = 0; i < serviceList.getLength(); i++)
296 {
297 final Element serviceEl = (Element) serviceList.item(i);
298 final String serviceClazz = serviceEl.getAttribute("class");
299
300 if ((serviceClazz == null) || "".equals(serviceClazz))
301 {
302 throw new ConfigurationException("Service element with bad class attribute");
303 }
304
305 try
306 {
307 SecurityConfigImpl.log.debug("Adding seraph service of class: " + serviceClazz);
308 final SecurityService service = (SecurityService) ClassLoaderUtil.loadClass(serviceClazz, getClass()).newInstance();
309
310 service.init(getInitParameters(serviceEl), this);
311
312 result.add(service);
313 }
314 catch (final Exception e)
315 {
316 throw new ConfigurationException("Could not getRequest service: " + serviceClazz, e);
317 }
318 }
319 }
320 return result;
321 }
322
323 private void configureInterceptors(final Element rootEl) throws ConfigurationException
324 {
325 final NodeList nl = rootEl.getElementsByTagName("interceptors");
326
327 if ((nl != null) && (nl.getLength() > 0))
328 {
329 final Element interceptorsEl = (Element) nl.item(0);
330 final NodeList interceptorList = interceptorsEl.getElementsByTagName("interceptor");
331
332 for (int i = 0; i < interceptorList.getLength(); i++)
333 {
334 final Element interceptorEl = (Element) interceptorList.item(i);
335 final String interceptorClazz = interceptorEl.getAttribute("class");
336
337 if ((interceptorClazz == null) || "".equals(interceptorClazz))
338 {
339 throw new ConfigurationException("Interceptor element with bad class attribute");
340 }
341
342 try
343 {
344 SecurityConfigImpl.log.debug("Adding interceptor of class: " + interceptorClazz);
345 final Interceptor interceptor = (Interceptor) ClassLoaderUtil.loadClass(interceptorClazz, getClass()).newInstance();
346
347 interceptor.init(getInitParameters(interceptorEl), this);
348
349 interceptors.add(interceptor);
350 }
351 catch (final Exception e)
352 {
353 throw new ConfigurationException("Could not getRequest service: " + interceptorClazz, e);
354 }
355 }
356 }
357 }
358
359
360
361
362
363
364
365
366
367 private static Map<String, String> getInitParameters(final Element el)
368 {
369 final Map<String, String> params = new HashMap<String, String>();
370 final NodeList nl = el.getElementsByTagName("init-param");
371
372 for (int i = 0; i < nl.getLength(); i++)
373 {
374 final Node initParam = nl.item(i);
375 final String paramName = XMLUtils.getContainedText(initParam, "param-name");
376 final String paramValue = XMLUtils.getContainedText(initParam, "param-value");
377 params.put(paramName, paramValue);
378 }
379 return Collections.unmodifiableMap(params);
380 }
381
382 public void destroy()
383 {
384 for (final Object element : services)
385 {
386 final SecurityService securityService = (SecurityService) element;
387 securityService.destroy();
388 }
389
390 for (final Object element : interceptors)
391 {
392 ((Interceptor) element).destroy();
393 }
394 }
395
396
397
398
399
400
401 public void addInterceptor(final Interceptor interceptor)
402 {
403 interceptors.add(interceptor);
404 }
405
406 public List<SecurityService> getServices()
407 {
408 return services;
409 }
410
411 public String getLoginURL()
412 {
413 return loginUrlStrategy.getLoginURL(this, loginURL);
414 }
415
416 public String getLinkLoginURL()
417 {
418 return loginUrlStrategy.getLinkLoginURL(this, linkLoginURL);
419 }
420
421 public String getLogoutURL()
422 {
423 return loginUrlStrategy.getLogoutURL(this, logoutURL);
424 }
425
426 public String getOriginalURLKey()
427 {
428 return originalURLKey;
429 }
430
431 public Authenticator getAuthenticator()
432 {
433 return authenticator;
434 }
435
436 public AuthenticationContext getAuthenticationContext()
437 {
438 return new AuthenticationContextImpl();
439 }
440
441 public SecurityController getController()
442 {
443 return controller;
444 }
445
446 public RoleMapper getRoleMapper()
447 {
448 return roleMapper;
449 }
450
451 public RedirectPolicy getRedirectPolicy()
452 {
453 return redirectPolicy;
454 }
455
456 public <T extends Interceptor> List<T> getInterceptors(final Class<T> desiredInterceptorClass)
457 {
458 final List<T> result = new ArrayList<T>();
459 for (final Interceptor interceptor : interceptors)
460 {
461 if (desiredInterceptorClass.isAssignableFrom(interceptor.getClass()))
462 {
463 result.add(desiredInterceptorClass.cast(interceptor));
464 }
465 }
466 return Collections.unmodifiableList(result);
467 }
468
469 public String getCookieEncoding()
470 {
471 return cookieEncoding;
472 }
473
474 public String getLoginCookiePath()
475 {
476 return loginCookiePath;
477 }
478
479 public String getLoginCookieKey()
480 {
481 return loginCookieKey;
482 }
483
484 public String getAuthType()
485 {
486 return authType;
487 }
488
489 public boolean isInsecureCookie()
490 {
491 return insecureCookie;
492 }
493
494 public int getAutoLoginCookieAge()
495 {
496 return autoLoginCookieAge;
497 }
498
499 public ElevatedSecurityGuard getElevatedSecurityGuard()
500 {
501 return elevatedSecurityGuard;
502 }
503
504 public ComponentLocator getComponentLocator()
505 {
506 return componentLocator;
507 }
508
509
510
511
512
513
514
515 public RememberMeService getRememberMeService()
516 {
517 RememberMeService service = null;
518 if (componentLocator != null)
519 {
520 service = componentLocator.getComponent(RememberMeService.class);
521 }
522 if (service == null)
523 {
524 service = NoopRememberMeService.INSTANCE;
525 }
526 return service;
527 }
528 }