View Javadoc

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   import com.atlassian.seraph.util.XMLUtils;
7   
8   import org.apache.commons.collections.map.LRUMap;
9   import org.apache.log4j.Category;
10  import org.w3c.dom.Document;
11  import org.w3c.dom.Element;
12  import org.w3c.dom.NodeList;
13  
14  import com.opensymphony.util.ClassLoaderUtil;
15  
16  import java.net.URL;
17  import java.util.Collection;
18  import java.util.Collections;
19  import java.util.HashMap;
20  import java.util.HashSet;
21  import java.util.Iterator;
22  import java.util.Map;
23  import java.util.Set;
24  import java.util.StringTokenizer;
25  
26  import javax.servlet.http.HttpServletRequest;
27  import javax.xml.parsers.DocumentBuilderFactory;
28  
29  /**
30   * Configures Seraph to require certain roles to access certain URL paths.
31   * <p>
32   * Single init-param 'config.file' which is the location of the XML config file. Default value is '/seraph-paths.xml' (loaded from classpath - usually
33   * in /WEB-INF/classes)
34   * <p>
35   * Here's a sample of the XML config file. Path names must be unique
36   * <p>
37   * 
38   * <pre>
39   * &lt;seraph-paths&gt;
40   *     &lt;path name=&quot;admin&quot;&gt;
41   *         &lt;url-pattern&gt;/secure/admin/*&lt;/url-pattern&gt;
42   *         &lt;role-name&gt;administrators&lt;/role-name&gt;
43   *     &lt;/path&gt;
44   *     &lt;path name=&quot;secured&quot;&gt;
45   *         &lt;url-pattern&gt;/secure/*&lt;/url-pattern&gt;
46   *         &lt;role-name&gt;users&lt;/role-name&gt;
47   *     &lt;/path&gt;
48   * &lt;/seraph-paths&gt;
49   * </pre>
50   */
51  public class PathService implements SecurityService
52  {
53      private static final Category log = Category.getInstance(PathService.class);
54      static String CONFIG_FILE_PARAM_KEY = "config.file";
55  
56      String configFileLocation = "seraph-paths.xml";
57  
58      // maps url patterns to path names
59      private final CachedPathMapper pathMapper = new CachedPathMapper(Collections.synchronizedMap(new LRUMap(10)), Collections.synchronizedMap(new LRUMap(10)));
60  
61      // maps roles to path names
62      private final Map /* <String, String[]> */paths = Collections.synchronizedMap(new HashMap());
63  
64      /**
65       * Init the service - configure it from the config file
66       */
67      public void init(final Map params, final SecurityConfig config)
68      {
69          if (params.get(CONFIG_FILE_PARAM_KEY) != null)
70          {
71              configFileLocation = (String) params.get(CONFIG_FILE_PARAM_KEY);
72          }
73  
74          configurePathMapper();
75      }
76  
77      /**
78       * Configure the path mapper
79       */
80      private void configurePathMapper()
81      {
82          try
83          {
84              final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
85              URL fileUrl = ClassLoaderUtil.getResource(configFileLocation, this.getClass());
86  
87              if (fileUrl == null)
88              {
89                  fileUrl = ClassLoaderUtil.getResource("/" + configFileLocation, this.getClass());
90              }
91  
92              if (fileUrl == null)
93              {
94                  return;
95              }
96  
97              // Parse document
98              final Document doc = factory.newDocumentBuilder().parse(fileUrl.toString());
99  
100             // Get list of actions
101             final Element root = doc.getDocumentElement();
102             final NodeList pathNodes = root.getElementsByTagName("path");
103 
104             final Map /* <String, String> */pathMaps = new HashMap();
105             // Build list of views
106             for (int i = 0; i < pathNodes.getLength(); i++)
107             {
108                 final Element path = (Element) pathNodes.item(i);
109 
110                 final String pathName = path.getAttribute("name");
111                 final String roleNames = XMLUtils.getContainedText(path, "role-name");
112                 final String urlPattern = XMLUtils.getContainedText(path, "url-pattern");
113 
114                 if ((roleNames != null) && (urlPattern != null))
115                 {
116                     final String[] rolesArr = parseRoles(roleNames);
117                     paths.put(pathName, rolesArr);
118                     pathMaps.put(pathName, urlPattern);
119                 }
120             }
121             pathMapper.set(pathMaps);
122         }
123         catch (final Exception ex)
124         {
125             log.error(ex);
126         }
127     }
128 
129     protected String[] parseRoles(final String roleNames)
130     {
131         final StringTokenizer st = new StringTokenizer(roleNames, ",; \t\n", false);
132         final String[] roles = new String[st.countTokens()];
133         int i = 0;
134         while (st.hasMoreTokens())
135         {
136             roles[i] = st.nextToken();
137             i++;
138         }
139         return roles;
140     }
141 
142     public void destroy()
143     {}
144 
145     public Set getRequiredRoles(final HttpServletRequest request)
146     {
147         final String servletPath = request.getServletPath();
148         return getRequiredRoles(servletPath);
149     }
150 
151     public Set getRequiredRoles(final String servletPath)
152     {
153         final Set requiredRoles = new HashSet();
154 
155         // then check path mappings first and add any required roles
156         final Collection constraintMatches = pathMapper.getAll(servletPath);
157 
158         if (constraintMatches == null)
159         {
160             throw new RuntimeException("No constraints matched for path " + servletPath);
161         }
162         for (final Iterator iterator = constraintMatches.iterator(); iterator.hasNext();)
163         {
164             final String constraint = (String) iterator.next();
165 
166             final String[] rolesForPath = (String[]) paths.get(constraint);
167             for (int i = 0; i < rolesForPath.length; i++)
168             {
169                 if (!requiredRoles.contains(rolesForPath[i])) // since requiredRoles is a set, isn't this useless?
170                 {
171                     requiredRoles.add(rolesForPath[i]);
172                 }
173             }
174         }
175         return Collections.unmodifiableSet(requiredRoles);
176     }
177 }