View Javadoc

1   package com.atlassian.seraph.service;
2   
3   import com.atlassian.seraph.util.PathMapper;
4   import com.atlassian.seraph.util.CachedPathMapper;
5   import com.atlassian.seraph.SecurityService;
6   import com.atlassian.seraph.config.SecurityConfig;
7   import com.opensymphony.util.ClassLoaderUtil;
8   import org.apache.log4j.Category;
9   import org.apache.commons.collections.map.LRUMap;
10  import org.w3c.dom.Element;
11  import org.w3c.dom.NodeList;
12  import org.xml.sax.SAXException;
13  
14  import javax.servlet.http.HttpServletRequest;
15  import javax.xml.parsers.DocumentBuilderFactory;
16  import javax.xml.parsers.ParserConfigurationException;
17  import java.net.URL;
18  import java.util.*;
19  import java.io.IOException;
20  
21  /**
22   * Configures Seraph based on WebWork 1.x configuration file actions.xml.
23   * <p/>
24   * Takes two optional init parameters:
25   * <dl>
26   * <dt>action.extension (default: "action")</dt>
27   * <dd>The extension of action URLs which are intercepted by this service.
28   * If this parameter is not specified, the default is the WebWork default 'action'.
29   * (This should match your servlet mapping in web.xml.)</dd>
30   * <dt>actions.xml.file (default: "actions")</dt>
31   * <dd>The location on the classpath of your actions.xml file (WebWork 1.x style).
32   * If this parameter is not specified, the default is the WebWork 1.x default 'actions'.
33   * (This should match the value configured for 'webwork.configuration.xml' in
34   * webwork.properties.)</dd>
35   * </dl>
36   * In actions.xml (or other actions XML file, as specified by the init-param) specify
37   * roles required per action or command:
38   * <pre>
39   *   &lt;action name="project.ViewProject" alias="ViewProject" roles-required="RoleFoo, RoleBar"&gt;
40   * or
41   *   &lt;command name="Delete" alias="DeleteProject" roles-required="RoleBat"&gt;
42   * </pre>
43   * Roles can be separated by commas and spaces.
44   */
45  public class WebworkService implements SecurityService
46  {
47      private static final Category log = Category.getInstance(WebworkService.class);
48      private static final String ROLES_REQUIRED_ATTR = "roles-required";
49      private static final String ACTION_EXTENSION_INIT_PARAM = "action.extension";
50      private static final String ACTIONS_XML_FILE_INIT_PARAM = "actions.xml.file";
51  
52      // Same as the WebWork default (i.e. action URLs end with '.action')
53      private static final String ACTION_EXTENSION_DEFAULT = "action";
54  
55      // Same as the WebWork 1.x default (i.e. actions.xml is the mapping file)
56      private static final String ACTIONS_XML_FILE_DEFAULT = "actions";
57  
58      // used to check which actions match the current path
59      // This class only uses the "get:" method of the pathmapper, so initialise the map
60      // that caches its results to a large value. The getAll method of the PathMapper is not used
61      // by this class so make the second caching map small.
62      private PathMapper actionMapper = new CachedPathMapper(new LRUMap(500), new LRUMap(10));
63  
64      // maps current action to roles required
65      private Map rolesMap = new HashMap();
66  
67      public void init(Map params, SecurityConfig config)
68      {
69          try
70          {
71              // the extension of webwork actions (should match web.xml servlet mapping)
72              String extension = (String) params.get(ACTION_EXTENSION_INIT_PARAM);
73              if (extension == null) extension = ACTION_EXTENSION_DEFAULT;
74  
75              // the XML file containing the action mappings (should match 'webwork.configuration.xml' property)
76  
77              String actionsXmlFile = (String) params.get(ACTIONS_XML_FILE_INIT_PARAM);
78              if (actionsXmlFile == null) actionsXmlFile = ACTIONS_XML_FILE_DEFAULT;
79  
80              configureActionMapper(extension, actionsXmlFile);
81          }
82          catch (RuntimeException e)
83          {
84              log.error("Failed to initialise WebworkService", e);
85          }
86      }
87  
88      private void configureActionMapper(String extension, String actionsXmlFile)
89      {
90          org.w3c.dom.Document doc = parseActionsXmlFile(actionsXmlFile);
91  
92          // Get list of actions
93          NodeList actions = doc.getElementsByTagName("action");
94  
95          String rootRolesRequired = overrideRoles(null, doc.getDocumentElement());
96  
97          // Build list of views
98          for (int i = 0; i < actions.getLength(); i++)
99          {
100             Element action = (Element) actions.item(i);
101             String actionName = action.getAttribute("name");
102             String actionAlias = action.getAttribute("alias");
103             final String actionRolesRequired = overrideRoles(rootRolesRequired, action);
104 
105             if (actionRolesRequired != null)
106             {
107 
108                 if (actionAlias != null)
109                 {
110                     actionMapper.put(actionAlias, "/" + actionAlias + "." + extension);
111                     rolesMap.put(actionAlias, actionRolesRequired);
112                     actionMapper.put(actionAlias + "!*", "/" + actionAlias + "!*." + extension);
113                     rolesMap.put(actionAlias + "!*", actionRolesRequired);
114                 }
115 
116                 if (actionName != null)
117                 {
118                     actionMapper.put(actionName, "/" + actionName + "." + extension);
119                     rolesMap.put(actionName, actionRolesRequired);
120                     actionMapper.put(actionName + "!*", "/" + actionName + "!*." + extension);
121                     rolesMap.put(actionName + "!*", actionRolesRequired);
122                 }
123             }
124 
125             // Get list of commands
126             NodeList commands = action.getElementsByTagName("command");
127             for (int j = 0; j < commands.getLength(); j++)
128             {
129                 Element command = (Element) commands.item(j);
130                 String cmdRolesRequired = overrideRoles(actionRolesRequired, command);
131 
132                 String commandAlias = command.getAttribute("alias");
133 
134                 if (commandAlias != null && cmdRolesRequired != null)
135                 {
136                     actionMapper.put(commandAlias, "/" + commandAlias + "." + extension);
137                     rolesMap.put(commandAlias, cmdRolesRequired);
138                 }
139             }
140         }
141     }
142 
143     private org.w3c.dom.Document parseActionsXmlFile(String actionsXmlFile)
144     {
145         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
146         URL fileUrl = ClassLoaderUtil.getResource(actionsXmlFile + ".xml", this.getClass());
147 
148         if (fileUrl == null)
149             fileUrl = ClassLoaderUtil.getResource("/" + actionsXmlFile + ".xml", this.getClass());
150 
151         if (fileUrl == null)
152             throw new IllegalArgumentException("No such XML file:/" + actionsXmlFile + ".xml");
153 
154         try
155         {
156             return factory.newDocumentBuilder().parse(fileUrl.toString());
157         }
158         catch (SAXException e)
159         {
160             throw new RuntimeException(e);
161         }
162         catch (IOException e)
163         {
164             throw new RuntimeException(e);
165         }
166         catch (ParserConfigurationException e)
167         {
168             throw new RuntimeException(e);
169         }
170     }
171 
172     /**
173      * Returns newRolesRequired if it isn't empty, and rolesRequired otherwise.
174      */
175     private String overrideRoles(String rolesRequired, Element action)
176     {
177         if (action.hasAttribute(ROLES_REQUIRED_ATTR))
178         {
179             return action.getAttribute(ROLES_REQUIRED_ATTR);
180         }
181         else
182         {
183             return rolesRequired;
184         }
185     }
186 
187     public void destroy()
188     {
189     }
190 
191     public Set getRequiredRoles(HttpServletRequest request)
192     {
193         Set requiredRoles = new HashSet();
194 
195         String currentURL = request.getRequestURI();
196 
197         int lastSlash = currentURL.lastIndexOf('/');
198         String targetURL;
199 
200         // then check webwork mappings
201         if (lastSlash > -1)
202         {
203             targetURL = currentURL.substring(lastSlash);
204         }
205         else
206         {
207             targetURL = currentURL;
208         }
209 
210         String actionMatch = actionMapper.get(targetURL);
211 
212         if (actionMatch != null)
213         {
214             String rolesStr = (String) rolesMap.get(actionMatch);
215 
216             StringTokenizer st = new StringTokenizer(rolesStr, ", ");
217             while (st.hasMoreTokens())
218             {
219                 requiredRoles.add(st.nextToken());
220             }
221         }
222 
223         return requiredRoles;
224     }
225 }