Posted by Michael Marth JAN 29, 2009
Posted in acl and jackrabbit Comments 5
Remember Zed Shaw (origin of the "400 reboots a day" meme, author of Mongrel and inspiration to counter-projects)? Zed gave an interesting talk at the CUSEC conference where he discussed how dated the concept of static ACLs is and that static ACLs could not satisfy the requirements of his customer. He goes on to describe his implementation of a document management system where static ACLs have been replaced with Ruby code. (He also discusses steaks and strippers)
I had never thought about it before, but I really liked his idea of ACLs that are implemented in code rather than declared as static rules. So I set out to implement a Jackrabbit-based document management system where the ACLs are dynamic. Since Jackrabbit comes with a WebDAV server I considered the DMS part done for the purpose of this excercise.
Jackrabbit allows access to nodes be controlled by custom implementations of the AccessManager. While this sounds simple enough there is a plethora of names and concepts related to authorization. This ticket has a good overview.
In order to hack my own access manager I looked at the Jackrabbit 1.5.2 branch and copied the org.apache.jackrabbit.core.security.simple.AccessManager into RAccessManager. Really, the only method that needed to be changed was internalIsGranted(Path absPath, int permissions) (the whole file is attached to this post):
private boolean internalIsGranted(Path absPath, int permissions)throws RepositoryException {if (!absPath.isAbsolute()) {throw new RepositoryException("Absolute path expected");}checkInitialized();if (system) {// system has always all permissionsreturn true;} else if (anonymous) {// anonymous is only granted READ premissionsreturn false;}// prepare decent path stringString decentPath = "";for (int i = 1; i < absPath.getElements().length; i++) {String e = ((Element) absPath.getElements()[i]).getString();decentPath += ("/" + e.substring(e.indexOf('}') + 1));}// prepare userString user = "";Iterator pi = subject.getPrincipals().iterator();while (pi.hasNext()) {Principal p = pi.next();user = p.getName();break;}ScriptEngineManager engineMgr = new ScriptEngineManager();ScriptEngine engine = engineMgr.getEngineByName("ECMAScript");try {InputStream is = new BufferedInputStream(new FileInputStream("permissions.js"));Reader reader = new InputStreamReader(is);engine.eval(reader);Invocable invocableEngine = (Invocable) engine;boolean result = (Boolean) invocableEngine.invokeFunction("check",decentPath, user, permissions);return result;} catch (Exception ex) {ex.printStackTrace();}// fallbackreturn false;}
This implementation prepares the user name and the requested node path and passes them to an ECMAScript function check(path, user, permissions) in the file permissions.js. The boolean this function returns will be used to determine access rights. That's all we need: access control done in a script.
One type of access control that would be impossible to implement in the static approach is shown below: all access outside of the office hours is forbidden. Other than that, only resources that have the user's name in their path can be accessed (which has the weird implication that users can access paths where they cannot access the parent path):
function check(path, user, permissions) {var now = new Date();if (now.getHours() <7> 18) {return false;}var exp = new RegExp("/" + user + "/");return exp.test(path);}
In order to get my implementaion of AccessManager to compile into the jackrabbit-standalone server I recompiled the core module and the standalone server. However, since the scripting capabilities utilized above are only available since Java 1.6 I needed to change the JDK settings in the parent pom.xml accordingly. After recompiling the standalone server I configured my new access manager in the repository.xml file:
<AccessManager class="org.apache.jackrabbit.core.security.simple.RAccessManager"> </AccessManager>

