Sudo for Scripts in Alfresco

sudoToday I ran into the pitfall of Alfresco scripts and permissions again. For all kind of reasons I cannot rewrite code, but run into trouble because a user having a particular role executes a script that modifies permissions. This of course fails if this user has less permissions on a given space or document than expected. The user/group will have more permissions (that make no sense) than needed, and only a little tiny change in the security settings can screw-up the transaction the script is part of. The best approach is of course to get rid of the script approach at all (related to permissions), and implement a decent class running as System. But I cannot.

I remember having seen the sudoUtils in the past, and the improved version after that, created by Fabio Strozzi. This is nice, but not exactly matching my use case/requirements. I have no clue who executes this function. An unknown, in time growing set of groups can execute, but only if it is defined in a particular script. The script limits the access, not the user or group. It would make sense to allow execution of a particular named script (or better, a set of predefined scripts). My idea is that I can define a set of scripts that can be executed as System user. However, to make sure we meet security, these named scripts (in alfresco-global.properties) need to be loaded from classpath, not from repository.

Lets face it, if someone has access to the file system, anything is possible…First, get the script from the classpath, and create a Rhino script object:

        // get the script from the classpath
        ClassLoader cl = this.getClass().getClassLoader();
        InputStreamReader is = new InputStreamReader(cl.getResourceAsStream(path));
        BufferedReader reader = new BufferedReader(is);
        final Script script = cx.compileReader(reader, path, 0, null);

Get current scope and context, needed to run the script in Rhino:

   	// get the current context, needed to execute the script
    	final Context cx = Context.getCurrentContext();

    	// get current scope of the script that calls this class
        final Scriptable scope = getScope();

Create the object that runs the script:

       RunAsWork raw = new RunAsWork() {
            public Object doWork() throws Exception {
            	logger.debug("Just before exec " + path);
                script.exec(cx, scope);
                return null;
            }
        };

And finally, run the script as the system account:

        // actually run the script as the SystemUser
        AuthenticationUtil.runAs(raw, AuthenticationUtil.getSystemUserName());

Well, we do need to secure this feature. If everyone can execute any script as root, we create a security hole. Fabio Strozzi reduced access to executing functions by requiring users to be member of the group SUDOERS (in parallel of Debian Linux). I have no clue to what groups the functionality will be extended, I know only a very limited number of known scripts need to be run as admin or system user.

 

Therefore, when we configure the bean defining this custom javascript functionality, and include the list of script path+name that are allowed to be executed. In the Java class we first read the list of scripts allowed:

    public void setAllowedScripts(List<String> allowedScripts)
    {
        this.allowedScripts = allowedScripts;
    }

Then we can check if the script to execute (variable ‘path’) is allowed to be executed. If not, an Exception is thrown (nothing executed!), and the list of allowed scripts is shown:

       if (!allowedScripts.contains(path)){
    		Iterator<String> scripts = allowedScripts.iterator();
    		while (scripts.hasNext()){
    			list += scripts.next()+"\n";
    		}
    		throw new Exception("The script " + path + "is a not allowed script to sudo... Allowed scripts: \n" + list);
    	}

The bean is defined as (tomcat/shared/classes/alfresco/extension/script-service-context.xml):

    <bean id="SudoScript" parent="baseJavaScriptExtension"
        class="nl.vlc.script.SudoScript">
        <property name="extensionName">
            <value>sudoScript</value>
        </property>

        <property name="allowedScripts">
          <list>
            <value>alfresco/script/test.js</value>
          </list>
        </property>
    </bean>

Find in the end the Java and XML file. Compile the Java, and place the XML, restart your system and…

Create a space structure

/inbox/storage

Allow EVERYONE to add content to ‘inbox’ (I go for Coordinator), remove permission inheritance from ‘storage’ so no one has access to it.

permissions on spaces

Add a rule to the space ‘inbox’, if an object is added to the space a script is executed with the content:

sudoScript.sudo("alfresco/script/test.js");

Add the script on the classpath on disc (tomcat/shared/classes/alfresco/script/test.js) with the content:

var target=space.childByNamePath("storage");
document.move(target);

So, if user mr-nobody adds a document to the space ‘inbox’, the script from classpath is executed and moves the document into the space the user has no access to. Notice that the original user does is recorded with his name in the modifier property.

The proof; rename the script test.js into test2.js, add another document to the space ‘inbox’ and see the exception:

sudo exception

Good luck, have fun!

—-

SudoScript.java

package nl.vlc.script;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.List;

import org.alfresco.repo.jscript.BaseScopableProcessorExtension;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.Scriptable;

public class SudoScript extends BaseScopableProcessorExtension {

	// the list of allowed scripts from bean definition
	private List<String> allowedScripts;
	private static Log logger = LogFactory.getLog(SudoScript.class);
	private String list = "";

	public void setAllowedScripts(List<String> allowedScripts)
    {
        this.allowedScripts = allowedScripts;
    }

    public void sudo(final String path) throws Exception  {
    	logger.debug("Number of scripts allowed: "+ allowedScripts.size());

    	if (!allowedScripts.contains(path)){
    		Iterator<String> scripts = allowedScripts.iterator();
    		while (scripts.hasNext()){
    			list += scripts.next()+"\n";
    		}
    		throw new Exception("The script " + path + " is a not allowed script to sudo... Allowed scripts: \n" + list);
    	}

    	// get the current context, needed to execute the script
    	final Context cx = Context.getCurrentContext();

    	// get current scope of the script that calls this class
        final Scriptable scope = getScope();

        // get the script from the classpath
        ClassLoader cl = this.getClass().getClassLoader();
        InputStreamReader is = new InputStreamReader(cl.getResourceAsStream(path));
        BufferedReader reader = new BufferedReader(is);
        final Script script = cx.compileReader(reader, path, 0, null);

        logger.debug("Got compiled script "+ path);		

        RunAsWork<Object> raw = new RunAsWork<Object>() {
            public Object doWork() throws Exception {
            	logger.debug("Just before exec " + path);
                script.exec(cx, scope);
                return null;
            }
        };

        // actually run the script as the SystemUser
        AuthenticationUtil.runAs(raw, AuthenticationUtil.getSystemUserName());
    }
}

script-services-config.xml

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'>

<beans>
    <bean id="SudoScript" parent="baseJavaScriptExtension" class="nl.vlc.script.SudoScript">

        <property name="extensionName">
            <value>sudoScript</value>
        </property>

        <property name="allowedScripts">
          <list>
            <value>alfresco/script/test.js</value>
          </list>
        </property>

    </bean>
</beans>

[update 20110527: added syntax highlighting]

14 Responses to “Sudo for Scripts in Alfresco”


  1. 1 Fabio Strozzi May 1, 2011 at 01:39

    Hi Tjarda,
    good approach, I like it.
    I was wondering whether another solution would be possible. What do you think about writing a custom action (say “Execute a script as Administrator”) that inherits the default behaviour of “Execute a script” action except for the checks on the script classpath and for the script execution (which is done with the system user privileges as in your code)? I’m not saying this is feasable, but I would try to implement it. This way you won’t have to write an intermediate script that calls sudo.
    Cheers
    Fabio

    • 2 tpeelen May 1, 2011 at 23:03

      Hi Fabio,

      Thanks for your response!
      I like your reasoning. But adding a new action seems adding more complexity. Would it be an acceptable assumption that if a certain script is executed, then always using the system account? (I think it is.) Since the list of allowed scripts is known, it would make more sense to actually tweak the class that executes the script. If the script is listed, run as system, otherwise, run as the current user. That prevents an additional dialog and potential issues with picking the wrong one…

      I did some research, and the RhinoScriptProcessor class is responsible for the execution of the script, the executeScriptImpl method does the work. If you reconfigure script-services-context.xml and tweak the bean named “JavaScriptProcessor”, assign it a custom class.

      Initially i tried to subclass the RhinoScriptProcessor, but that doesn’t work out nice, most methods and constants are private. As a ‘second best’ I try to copy the class definition and modify/add the methods I need. Run into a ValueConverter issue right now.
      Also some work to do to actually recognize scripts defined in the bean definition (xml), to decide to run as System or as used (as-is).

      Work in Progress…

      • 3 nassimaimeur@yahoo.fr May 20, 2012 at 15:56

        please i can’t Compile the Java and i don’t see where i put the file please help me more detail please

        • 4 Tjarda Peelen May 20, 2012 at 20:48

          Hi there,

          That is a very brief response…

          Actually, follow the rules of Java (I consider those known!). The Java above has a package nl.vlc.script, so you need a folder structure of nl/vlc/script to store this file SudoScript.java. Either use Eclipse, sonething similar or the java command-line compiler to compile nl/vlc/script/SudoScript.java. Be sure you include the proper Alfresco jar files in the classpath, otherwise you can’t compile. (See the Alfresco wiki how to setup the SDK.)
          You will get a file nl/vlc/script/SudoScript.class; put this class file in tomcat/shared/classes, or jar it (“jar -cv sudoscript.jar nl*”) and put this jar file into tomcat/webapps/alfresco/WEB-INF/lib

  2. 5 nassimaimeur@yahoo.fr May 22, 2012 at 16:31

    thanks for your answer
    it’s very defecult for me what you have said
    can you send me the java.clas directly
    i speak french excuse me for error.

  3. 6 nassimaimeur@yahoo.fr May 22, 2012 at 18:34

    excuse me my version alfresco 4

    • 7 Tjarda Peelen June 10, 2012 at 12:51

      As you can determine from the the creation date of the blog post, it is more than an year old. So Alfresco 4.0 was not released at that date. I hope you can imagine I do not have the spare time to keep all my blog postings updated for the most current release. It could well be that the 3.3 [my guess] code is not compatible with the 3.4 and 4.0 code lines. It is my intention to show the mechanism, you can take it from there.
      I am afraid I cannot help you with a full course Alfresco customization, nor can I provide precompiled packages for an Alfresco version of choice. Apologies for that, my life, and Alfresco challenges move on too.

  4. 8 nassimaimeur@yahoo.fr May 23, 2012 at 15:14

    That is my log

    Caused by: org.mozilla.javascript.EcmaError: ReferenceError: “sudoScript” n’est pas défini (workspace://SpacesStore/7133ce4f-80fe-478c-8797-e8e8da87ca97#1)
    at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3350)
    at org.mozilla.javascript.ScriptRuntime.constructError(ScriptRuntime.java:3340)
    at org.mozilla.javascript.ScriptRuntime.notFoundError(ScriptRuntime.java:3413)
    at org.mozilla.javascript.ScriptRuntime.name(ScriptRuntime.java:1612)
    at org.mozilla.javascript.gen.c27._c0(workspace://SpacesStore/7133ce4f-80fe-478c-8797-e8e8da87ca97:1)
    at org.mozilla.javascript.gen.c27.call(workspace://SpacesStore/7133ce4f-80fe-478c-8797-e8e8da87ca97)
    at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:393)
    at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:2834)
    at org.mozilla.javascript.gen.c27.call(workspace://SpacesStore/7133ce4f-80fe-478c-8797-e8e8da87ca97)
    at org.mozilla.javascript.gen.c27.exec(workspace://SpacesStore/7133ce4f-80fe-478c-8797-e8e8da87ca97)
    at org.alfresco.repo.jscript.RhinoScriptProcessor.executeScriptImpl(RhinoScriptProcessor.java:483)
    … 84 more

  5. 9 nassimaimeur@yahoo.fr October 12, 2012 at 12:30

    thank you very much it works very well

  6. 10 Mirjana December 5, 2012 at 16:16

    Hi,

    Great post, thank you! 🙂
    We solved the same problem very quickly.

    Best regards,
    Mirjana


  1. 1 Executing named scripts as System in Alfresco « Open Source ECM/WCM Trackback on May 26, 2011 at 10:36
  2. 2 Executing named scripts as System in Alfresco « Open Source ECM/WCM Trackback on May 26, 2011 at 10:36
  3. 3 Alfresco Log Browser « Open Source ECM/WCM Trackback on May 27, 2011 at 22:50
Comments are currently closed.