1 package com.atlassian.seraph.service;
2
3 import com.atlassian.seraph.SecurityService;
4 import com.atlassian.seraph.config.SecurityConfig;
5 import com.atlassian.seraph.util.CachedPathMapper;
6
7 import org.apache.commons.collections.map.LRUMap;
8 import org.apache.log4j.Category;
9 import org.w3c.dom.Document;
10 import org.w3c.dom.Element;
11 import org.w3c.dom.NodeList;
12 import org.xml.sax.SAXException;
13
14 import com.opensymphony.util.ClassLoaderUtil;
15
16 import java.io.IOException;
17 import java.net.URL;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.StringTokenizer;
24
25 import javax.servlet.http.HttpServletRequest;
26 import javax.xml.parsers.DocumentBuilderFactory;
27 import javax.xml.parsers.ParserConfigurationException;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49 public class WebworkService implements SecurityService
50 {
51 private static final Category log = Category.getInstance(WebworkService.class);
52 private static final String ROLES_REQUIRED_ATTR = "roles-required";
53 private static final String ACTION_EXTENSION_INIT_PARAM = "action.extension";
54 private static final String ACTIONS_XML_FILE_INIT_PARAM = "actions.xml.file";
55
56
57 private static final String ACTION_EXTENSION_DEFAULT = "action";
58
59
60 private static final String ACTIONS_XML_FILE_DEFAULT = "actions";
61
62
63
64
65
66 private final CachedPathMapper actionMapper = new CachedPathMapper(Collections.synchronizedMap(new LRUMap(2000)), Collections.synchronizedMap(new LRUMap(10)));
67
68
69 private final Map rolesMap = Collections.synchronizedMap(new HashMap());
70
71 public void init(final Map params, final SecurityConfig config)
72 {
73 try
74 {
75
76 String extension = (String) params.get(ACTION_EXTENSION_INIT_PARAM);
77 if (extension == null)
78 {
79 extension = ACTION_EXTENSION_DEFAULT;
80 }
81
82
83
84 String actionsXmlFile = (String) params.get(ACTIONS_XML_FILE_INIT_PARAM);
85 if (actionsXmlFile == null)
86 {
87 actionsXmlFile = ACTIONS_XML_FILE_DEFAULT;
88 }
89
90 configureActionMapper(extension, actionsXmlFile);
91 }
92 catch (final RuntimeException e)
93 {
94 log.error("Failed to initialise WebworkService", e);
95 }
96 }
97
98 private void configureActionMapper(final String extension, final String actionsXmlFile)
99 {
100 final Document doc = parseActionsXmlFile(actionsXmlFile);
101
102
103 final NodeList actions = doc.getElementsByTagName("action");
104
105 final String rootRolesRequired = overrideRoles(null, doc.getDocumentElement());
106
107 final Map
108
109
110 for (int i = 0; i < actions.getLength(); i++)
111 {
112 final Element action = (Element) actions.item(i);
113 final String actionName = action.getAttribute("name");
114 final String actionAlias = action.getAttribute("alias");
115 final String actionRolesRequired = overrideRoles(rootRolesRequired, action);
116
117 if (actionRolesRequired != null)
118 {
119
120 if (actionAlias != null)
121 {
122 pathMaps.put(actionAlias, "/" + actionAlias + "." + extension);
123 rolesMap.put(actionAlias, actionRolesRequired);
124 pathMaps.put(actionAlias + "!*", "/" + actionAlias + "!*." + extension);
125 rolesMap.put(actionAlias + "!*", actionRolesRequired);
126 }
127
128 if (actionName != null)
129 {
130 pathMaps.put(actionName, "/" + actionName + "." + extension);
131 rolesMap.put(actionName, actionRolesRequired);
132 pathMaps.put(actionName + "!*", "/" + actionName + "!*." + extension);
133 rolesMap.put(actionName + "!*", actionRolesRequired);
134 }
135 }
136
137
138 final NodeList commands = action.getElementsByTagName("command");
139 for (int j = 0; j < commands.getLength(); j++)
140 {
141 final Element command = (Element) commands.item(j);
142 final String cmdRolesRequired = overrideRoles(actionRolesRequired, command);
143
144 final String commandAlias = command.getAttribute("alias");
145
146 if ((commandAlias != null) && (cmdRolesRequired != null))
147 {
148 pathMaps.put(commandAlias, "/" + commandAlias + "." + extension);
149 rolesMap.put(commandAlias, cmdRolesRequired);
150 }
151 }
152 }
153 actionMapper.set(pathMaps);
154 }
155
156 private Document parseActionsXmlFile(final String actionsXmlFile)
157 {
158 final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
159 URL fileUrl = ClassLoaderUtil.getResource(actionsXmlFile + ".xml", this.getClass());
160
161 if (fileUrl == null)
162 {
163 fileUrl = ClassLoaderUtil.getResource("/" + actionsXmlFile + ".xml", this.getClass());
164 }
165
166 if (fileUrl == null)
167 {
168 throw new IllegalArgumentException("No such XML file:/" + actionsXmlFile + ".xml");
169 }
170
171 try
172 {
173 return factory.newDocumentBuilder().parse(fileUrl.toString());
174 }
175 catch (final SAXException e)
176 {
177 throw new RuntimeException(e);
178 }
179 catch (final IOException e)
180 {
181 throw new RuntimeException(e);
182 }
183 catch (final ParserConfigurationException e)
184 {
185 throw new RuntimeException(e);
186 }
187 }
188
189
190
191
192 private String overrideRoles(final String rolesRequired, final Element action)
193 {
194 if (action.hasAttribute(ROLES_REQUIRED_ATTR))
195 {
196 return action.getAttribute(ROLES_REQUIRED_ATTR);
197 }
198 else
199 {
200 return rolesRequired;
201 }
202 }
203
204 public void destroy()
205 {}
206
207 public Set getRequiredRoles(final HttpServletRequest request)
208 {
209 final Set requiredRoles = new HashSet();
210
211 final String currentURL = request.getRequestURI();
212
213 final int lastSlash = currentURL.lastIndexOf('/');
214 String targetURL;
215
216
217 if (lastSlash > -1)
218 {
219 targetURL = currentURL.substring(lastSlash);
220 }
221 else
222 {
223 targetURL = currentURL;
224 }
225
226 final String actionMatch = actionMapper.get(targetURL);
227
228 if (actionMatch != null)
229 {
230 final String rolesStr = (String) rolesMap.get(actionMatch);
231
232 final StringTokenizer st = new StringTokenizer(rolesStr, ", ");
233 while (st.hasMoreTokens())
234 {
235 requiredRoles.add(st.nextToken());
236 }
237 }
238
239 return Collections.unmodifiableSet(requiredRoles);
240 }
241 }