UrbanCode Deploy

UrbanCode Deploy 10 Minute Tip: Documenting the Permissions Model

When you set out to define a permissions model for IBM UrbanCode Deploy it’s often a requirement to document the model for discussions/approvals… 

While UCD provides tools to build and maintain the permissions model it doesn’t give you a ‘single view’ that can be used in workshops and other forums to discuss the operation of the model and the specifics of what permissions are granted to each role.

Often as part of the process of adopting UCD, it is seen as desirable to have the model presented in a documented form for sign-off and also for discussions around future change.  It can also be needed for training purposes so that each role in the organization clearly knows what their responsibilities are.

It would also help if there was an easy way to spot anomalies where a permission isn’t granted to any role.  This might be an oversight or perhaps intentional, but it would help if these were easy to spot and justify.

UCD has over 160 permissions at the time of writing spread across 18 different categories or object classes.  This also changes over time as UCD evolves and more permissions are added / old ones changed in their scope.

So, wouldn’t it be handy if you could just develop the permissions model in UCD itself and then extract what you had set up for documentation and discussion purposes?  That way you would only have one place to maintain the model and, in keeping with general best practices, only have a single source of truth – UCD itself!!

What I’m presenting here is a small groovy script that will do just that for you.  It will enumerate all the permissions in UCD across the 18 different object classes and include variations in permissions for each resource role or ‘Type’, against all of the UCD roles that you have defined.  The output is a tab-separated list which can be easily imported into a spreadsheet for analysis.

The script will also warn you if there are any permissions in your model that aren’t assigned to any role-resource role combination meaning that the UCD operation it represents cannot be performed by anyone.

The tool is executed via a shell script and you will need a copy of the uDeployRestClient.jar file in the same directory.  You can find this jar in any of the UCD plugins that provide services to automate UCD itself.

You will also need to setup the environment variables JAVA_HOME pointing at your JRE and also GROOVY_HOME pointing at a groovy installation.  (UCD agents have one of these in their opt sub-directory.)

The script takes three parameters, -user, -password and -weburl.  You should redirect standard output to a file to receive the extracted permissions information which you will later import into a spreadsheet.

./extractPermissions.sh -user admin -password admin -weburl https://localhost:8443 > permissons.out

The application title and any other information is presented on standard error.  This includes a warning about permissions assigned to no role.   The following is an example output.  In my case, I’ve upgraded a UCD server with a version that has new permissions that have yet to be assigned to any role.

UCD Role Permissions Extractor V3.0 December 2020
 The permission 'Create External Approvals' of Class 'External Approval' was not assigned to any role
 The permission 'Delete' of Class 'External Approval' was not assigned to any role
 The permission 'Edit Basic Settings' of Class 'External Approval' was not assigned to any role
 The permission 'Manage Properties' of Class 'External Approval' was not assigned to any role
 The permission 'Manage Teams' of Class 'External Approval' was not assigned to any role
 The permission 'Manage Maintenance Mode' of Class 'Server Configuration' was not assigned to any role
 The permission 'Manage Tags' of Class 'Server Configuration' was not assigned to any role
 The permission 'Configuration Tab' of Class 'Web UI' was not assigned to any role
Extract Complete

This is the kind of thing you would see once you import the output into a spreadsheet.  Being able to generate this automatically represents a significant saving in time and improvement in accuracy not to speak of saving a lot of hair!!!

So this is the shell script to invoke the script in a nice friendly manner

#!/bin/sh
if [ -n "$GROOVY_HOME" ]; then
    # change the dir to the root of the client directory
    SHELL_NAME="$0"
    SHELL_PATH=`dirname "${SHELL_NAME}"`
    
    if [ "." = "$SHELL_PATH" ]
    then
       SHELL_PATH=`pwd`
    fi

    groovycmd="$GROOVY_HOME/bin/groovy"
    jarfile="$SHELL_PATH/uDeployRestClient.jar"
    
    if [ -r "$jarfile" ]; then
	"$groovycmd" -cp "$jarfile" extractPermissions.groovy "$@"
    else
        echo "Didn't find $jarfile in directory ${SHELL_PATH}"
        exit 1
    fi
else
    echo You must have GROOVY_HOME set in your environment to use the extractPermissions tool.
    exit 1
fi

Finally, the most important bit, the script itself

import java.net.URI;
import java.util.LinkedHashMap;
import com.urbancode.ud.client.UDRestClient;
import org.codehaus.jettison.json.JSONArray;
import org.codehaus.jettison.json.JSONObject;
import org.codehaus.jettison.json.JSONException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.DefaultHttpClient;

userid="";
password="";
weburl="";

System.err << "UCD Role Permissions Extractor V3.0 December 2020\n"

public class extensionsClient extends UDRestClient
{
    public extensionsClient(final URI url, final String clientUser, final String clientPassword) {
        super(url, clientUser, clientPassword);
    }
    public extensionsClient(final URI url, final String clientUser, final String clientPassword, final boolean trustAllCerts)
    {
        super(url, clientUser, clientPassword, trustAllCerts);
    }
    public extensionsClient(final URI url, final DefaultHttpClient client) {
        super(url, client);
    }

    // fetch the action mappings for a given role.  This maps permissions to roles including user defined types
    JSONArray getSecurityRoleActionMappings(final String roleID)
    {
	String uri = this.url.toString() + "/security/role/${roleID}/actionMappings";
	final HttpGet method = new HttpGet(uri);
	final CloseableHttpResponse response = invokeMethod((HttpRequestBase)method);
	final String body =  this.getBody(response) ;
	return new JSONArray(body);
    }
    JSONArray getSecurityRoles()
    {
	String uri = this.url.toString() + "/security/role";
	final HttpGet method = new HttpGet(uri);
	final CloseableHttpResponse response = invokeMethod((HttpRequestBase)method);
	final String body =  this.getBody(response) ;
	return new JSONArray(body);
    }
    JSONArray getSecurityClassPermissions(final String resourceClass)
    {
	String uri = this.url.toString() + "/security/resourceType/${resourceClass}/actions";
	final HttpGet method = new HttpGet(uri);
	final CloseableHttpResponse response = invokeMethod((HttpRequestBase)method);
	final String body =  this.getBody(response) ;
	return new JSONArray(body);
    }
    JSONArray getResourceRolesforClass(final String resourceClass)
    {
	String uri = this.url.toString() + "/security/resourceType/${resourceClass}/resourceRoles";
	final HttpGet method = new HttpGet(uri);
	final CloseableHttpResponse response = invokeMethod((HttpRequestBase)method);
	final String body =  this.getBody(response) ;
	return new JSONArray(body);
    }
    JSONArray getSecurityResourceTypes()
    {
	String uri = this.url.toString() + "/security/resourceType";
	final HttpGet method = new HttpGet(uri);
	final CloseableHttpResponse response = invokeMethod((HttpRequestBase)method);
	final String body =  this.getBody(response) ;
	return new JSONArray(body);
    }
}

ParseCommandLine(this.args);
myClient = new extensionsClient( new URI(weburl), userid , password, true);

// Permissions Extraction
// OK, lets start the exercise by getting the list of role permissions classifications eg, agent, agent pool, application and so on.
// Map contains the permission classification and its ID
LinkedHashMap <String, String> Role_Class_Info = getNameAndID(myClient.getSecurityResourceTypes());
Role_Class_Info.sort{it}

// Now build a map of the roles and ID's
LinkedHashMap <String, String> Role_Info = getNameAndID(myClient.getSecurityRoles());

// print static part of column header line
print "Permission Category\tPermission Name\tPermission Description\tResource Role"
NumRoles=Role_Class_Info.size();

// Output the names of all the roles
Role_Info.each {print "\t" + it.key };

// Terminate column headings line
println  "";

// Now get the permissions map for all of the roles against each role there is a JSON array of permissions
LinkedHashMap<String, JSONArray>  RoleMaps = getRoleMaps(Role_Info);

// Now process each of the role permission classifications
Role_Class_Info.each 
{
	// Get values for loop
	EncodedClassName = it.key.replace(' ', '%20');
	CLASS_NAME = it.key;
	CLASS_ID = it.value;

	// Get the permissions details for each permission in the class category
	JSONArray ClassPermissions = myClient.getSecurityClassPermissions(EncodedClassName);

	// get the names of the resourcetypes available for the category 
	// So we will end up with lists of all of the types for each permission category and also the IDs of those
	JSONArray resourceRoles = myClient.getResourceRolesforClass(EncodedClassName);

	// see if there are any additional to the standard type
	RESOURCE_ROLE_NAMES= ["standard"];
	RESOURCE_ROLE_IDS = ["NA"];
	if ( resourceRoles.length() > 0 )
	{
		for (int i= 0 ; i< resourceRoles.length() ; ++i)
		{
			JSONObject propObject = (JSONObject)resourceRoles.get(i);
			RESOURCE_ROLE_NAMES = RESOURCE_ROLE_NAMES << propObject.get("name");
			RESOURCE_ROLE_IDS = RESOURCE_ROLE_IDS << propObject.get("id");
		}
	}

	lastPermID=""
	lastPermName=""
	permCount=0
	// Now iterate over each permission/type combination and find out what roles have it 
        for (int i = 0; i < ClassPermissions.length(); ++i) 
	{
		final JSONObject propObject = (JSONObject)ClassPermissions.get(i);
		PERM_NAME = propObject.get("name");
		PERM_ID = propObject.get("id");
		PERM_DESC = propObject.get("description");

		for (int j=0; j<RESOURCE_ROLE_NAMES.size(); ++j)
		{
			// Print the static data for each permission
			print "${CLASS_NAME}\t${PERM_NAME}\t${PERM_DESC}\t"+RESOURCE_ROLE_NAMES[j]

			// Check to see if a permissions wasn't assigned to any role
			if ( lastPermID != PERM_ID)
			{
				if (lastPermID != "")
					if ( permCount == 0 ) System.err << " The permission \'${lastPermName}\' of Class \'${CLASS_NAME}\' was not assigned to any role\n"
				lastPermID= PERM_ID
				lastPermName = PERM_NAME
				permCount=0	
			}
				
			// Add the assigned permission information to the output line
			Role_Info.each
			{
				ROLE = it.key;
				JSONArray MapForRole = RoleMaps[ROLE];
				assigned =  searchJSONArrayforKey(MapForRole, PERM_ID, RESOURCE_ROLE_NAMES[j]);
				if (assigned.contains("X")) permCount++
				print assigned
			}
			println "";
		}
	}
}
System.err << "Extract Complete\n"

// This method searches the action map for a role looking to see if the role has the permission we're interested in
// It returns either an 'X' for present or blank for missing
String searchJSONArrayforKey (JSONArray actionsMap, String id , String resourceRole)
{
	// Search the JSON array given for an oject containing the specific value of a given key
	for ( int i = 0 ; i < actionsMap.length(); ++i)
	{
		JSONObject obj = actionsMap.get(i);
		try
		{
			if ( resourceRole == "standard" )
				if ( obj.find {it.action.id == id} != null ) return "\tX";
			else
				if (obj.find {it.resourceRole.name == resourceRole}  != null ) return "\tX";
		}
		catch (Exception JSONException)
		{
		}
	}
	return "\t";
}

// This method parses the command options to extract connection details
void ParseCommandLine(args)
{
	def idx=0
	def arg=""
	while ( idx < args.size())
	{
		arg = args[idx].toLowerCase();
		switch (arg)
		{
			case "-user":
			case "--user":
				userid=args[++idx];
				break;
			case "-password":
			case "--password":
				password=args[++idx];
				break;
			case "-weburl":
			case "--weburl":
				weburl=args[++idx];
				break;
			default:
				System.err << "Unexpected argument ${arg}\n";
				System.err << "Expected command line -user <ucdd admin username> -passsword <password for admin user> -weburl https://<host>:<port> \n"
				System.exit(1);
		}
		idx++;
	}
}
LinkedHashMap<String, String> getNameAndID(JSONArray roleInfo) throws IOException, JSONException 
{
        final Map<String, String> result = new LinkedHashMap<String, String>();
        for (int i = 0; i < roleInfo.length(); ++i) 
	{
            final JSONObject propObject = (JSONObject)roleInfo.get(i);
            result.put((String)propObject.get("name"), (String)propObject.get("id"));
        }
        return result;
}
LinkedHashMap<String, JSONArray> getRoleMaps(LinkedHashMap <String, String> RoleInfo )
{
	final Map<String, JSONArray> result = new LinkedHashMap<String, JSONArray>();
	RoleInfo.each
	{
		result.put((String)it.key, (JSONArray) myClient.getSecurityRoleActionMappings(it.value));
	}
	return result;
}

I hope you find this script useful and that it saves you some time (and hair!).


Alan Murphy is an IBM services consultant who has worked with clients to help them adopt new tools and processes for the last 20 years. UrbanCode Deploy and DevOps has been his focus for the last 5+ years. He also develops tools to assist clients in tool adoption and blogs on an occasional basis.

Tags: