Ensuring Users & Managing Permissions with ECMAScript in SharePoint 2010

When SharePoint 2013 came out lot of great user object functionality came to the ECMAScript client object model (or Javascript to everyone else), unfortunatly I had naively assumed when reading the specification that this functionality was also available in SharePoint 2010.

I had marched forward with an assumption that for a customised content dashboard, managing users and their permissions would be easy. I suddenly found the functions I wanted to use didn't actually exist where I thought they existed namely SP.Web.get_siteUsers(). As I result I decided to cobble together my own helper library to allow me to ensure users and also assign them various permissions to list/library items.

The purpose of this component of the application was to automatically give read access to approvers and reviewers of a document in an inheritance broken and permission controlled list. The end users were non-technical and had trouble using basic SharePoint functionality let alone the permission controls; my job was to make it simple, quick and idiot proof (not a small ask).

My complete application was a CanJS based application, however for the purpose of this example the only prerequisite here is jQuery. This example makes use of the incredibly useful $.Deffered() object which looks after the status of the XMLHttp requests and provides you with associated events to deal with the outcome.

The first step for my helper was to be able to make SharePoint aware of users that previously did not exist. In order to do this users needed to be ensured, i.e. added to the coresponding site collections user catalog. Just as a precursor, this method assumes you know the users username which could either be manually entered or gleamed from the People.asmx web service (see SPWidgets, it's a great jQuery plugin).

So consider the follow code:

var ensured_user = null;
function ensure_user(user_name){
	// Construsts a new deferred object, this will be what we return
	var deferred = $.Deferred();
	var context = new SP.ClientContext.get_current(); // Context of the site you are in
	var web = context.get_web(); // Get the current web

	// Ensure the users against the web
	// this is the important bit, SharePoint is now aware
	// of this user and they will be given a internal ID
	var user = web.ensureUser(user_name); 
	context.load(user); // Load the returned user, we might need it for other things

	// Execute the query
	context.executeQueryAsync( 
	    function(sender, args) { 
		if(!user) {
		    // Fail the object, the username provide was not found anywhere
		    deferred.reject({message: "Unable to locate user " + user_name + ":"+ args.get_message(), error: args});
		}
		else {
		    // Excellent, user resolved and standing by
		    // the returned value is an SP.User object
		    // see http://msdn.microsoft.com/en-us/library/office/ff410544(v=office.14).aspx
		    deferred.resolve(user); 
		}
	    }, 
	    function(sender, args) {
		// Fail the deferred object and return a JSON object with some details
		deferred.reject({message: "Unable to check user details for " + user_name + ":"+ args.get_message(), error: args});
	    } 
	);

	// This is the magic bit, the deferred object promise is returned to the 
	// requesting function where we have various methods such as done() and fail() availaible to us
	return deferred.promise()
}

// This method can then be invoked with the following call
var deferred = ensure_user("jonhay\\jhay");
deferred.done(function(user){
    //Hurrah, we have a fully ensured user
    console.log(user);
    ensured_user = user;
}).fail(function(err){
	console.log(err.message);
})

With this codeb you are able ensure users and get a SP.User object in return, with this SP.User object we can now assign permissions. In my example I only wanted to assign read access to approvers and reviewers, I also needed to be able to remove these permissions as well.

Consider the following code (assume the ensured_user variable is still available from the above code):


function apply_permissions(params){  
    console.log("Applying permissions for " + params.sp_user.get_loginName() + " to " + params.list_name + " on item " + params.item_id);
    var deferred = $.Deferred(); // Construct deferred object
    var clientContext = SP.ClientContext.get_current();
    var oList = clientContext.get_web().get_lists().getByTitle(params.list_name); // Get the list we want to work with

    var oListItem = oList.getItemById(params.item_id); // Find the item we want to set permissions on

    oListItem.breakRoleInheritance(false); // Make sure permission inheritance is broken on the list item
    
    var collRoleDefinitionBinding = SP.RoleDefinitionBindingCollection.newObject(clientContext); // contsruct a new Role Defitinon Binding
    
    // Set the type of role we are using. This value is described by the SP.RoleType class
    // Other available types are: administrator, contributor, editor, guest, none, reader, webDesigne, registerEnum
    // e.g. SP.RoleType.administrator
    collRoleDefinitionBinding.add(clientContext.get_web().get_roleDefinitions().getByType(params.permission_type)); 

    // Determine if we are removing this permission, if not assume we are adding
    if(action == "remove")
        oListItem.get_roleAssignments().getByPrincipal(params.sp_user).deleteObject();
    else
        oListItem.get_roleAssignments().add(params.sp_user, collRoleDefinitionBinding);

    clientContext.load(params.sp_user); // Load the user (just if we need it). Returns a SP.User object
    clientContext.load(oListItem); // Load the list item (again, just if we need it)

    clientContext.executeQueryAsync(
        function (sender, args) {
            
            // Success, resolve the deferred job return the list item field values (optional)                
            deferred.resolve(oListItem.get_fieldValues())
            if(action == "remove")
                console.log('Role assignment removed for ' + params.sp_user.get_loginName());
            else
                console.log('Role inheritance broken for item ' + params.item_id + ' and new role assignment for ' + params.sp_user.get_loginName());
        },
        function (sender, args) {
            // Failure, return what when wrong and reject the deferred job.
            deferred.reject({message: 'Assigning permissions failed for ' + params.sp_user.get_loginName() + '. ' + args.get_message() + '\n' + args.get_stackTrace(), error: args});
        }
    );
    
    return deferred;
}

// Assign all our required variables as a JSON object, far more flexible
var params = { "list_name": "Documents", "item_id": 1, "sp_user": ensured_user, "permission_type": SP.RoleType.reader, "action": "add"}

// Apply the permission change to the user on the respective list and list item
var permission_deferred = apply_permissions(params);

Fantastic! We can now ensure users from a user name and assign that user whatever permissions we wish to a list or library item. But what if we only know the users SharePoint ID and no other information?

Lets pull this together into a more comprehensive helper library with one additional method to grab the users details from their SharePoint ID which will include their username. Once we have the username we can turn it into an SP.User object and perform the actions above exactly as before.

Here is the helper in full with an additional method to get user details from a SharePoint User ID (I have omitted the comments from the previously discussed methods to save space):

$.sphelper = {
    apply_permissions: function(params){  
        console.log("Applying permissions for " + params.sp_user.get_loginName() + " to " + params.list_name + " on item " + params.item_id);
        var deferred = $.Deferred(); // Construct deferred object
        var clientContext = SP.ClientContext.get_current();
        var oList = clientContext.get_web().get_lists().getByTitle(params.list_name);
    
        var oListItem = oList.getItemById(params.item_id); 
    
        oListItem.breakRoleInheritance(false); 
        
        var collRoleDefinitionBinding = SP.RoleDefinitionBindingCollection.newObject(clientContext);
        collRoleDefinitionBinding.add(clientContext.get_web().get_roleDefinitions().getByType(params.permission_type)); 

        if(action == "remove")
            oListItem.get_roleAssignments().getByPrincipal(params.sp_user).deleteObject();
        else
            oListItem.get_roleAssignments().add(params.sp_user, collRoleDefinitionBinding);
    
        clientContext.load(params.sp_user);
        clientContext.load(oListItem);
    
        clientContext.executeQueryAsync(
            function (sender, args) {     
                deferred.resolve(oListItem.get_fieldValues())
                if(action == "remove")
                    console.log('Role assignment removed for ' + params.sp_user.get_loginName());
                else
                    console.log('Role inheritance broken for item ' + params.item_id + ' and new role assignment for ' + params.sp_user.get_loginName());
            },
            function (sender, args) {
                deferred.reject({message: 'Assigning permissions failed for ' + params.sp_user.get_loginName() + '. ' + args.get_message() + '\n' + args.get_stackTrace(), error: args});
            }
        );
        
        return deferred;
    },
    ensure_user: function(user_name){
        var deferred = $.Deferred();
        var context = new SP.ClientContext.get_current();
        var web = context.get_web();

        var user = web.ensureUser(user_name); 
        context.load(user); 
    
        context.executeQueryAsync( 
            function(sender, args) { 
            if(!user) {
                deferred.reject({message: "Unable to locate user " + user_name + ":"+ args.get_message(), error: args});
            }
            else {
                deferred.resolve(user); 
            }
            }, 
            function(sender, args) {
                deferred.reject({message: "Unable to check user details for " + user_name + ":"+ args.get_message(), error: args});
            } 
        );
        return deferred.promise()
    },  
    // This method allows us to find user information from an ID, there are other ways to do this but I've done it
    // this way to be consistent with the previous methods
    get_user_details: function(user_id) {
        var deferred = $.Deferred();
        var clientContext = new SP.ClientContext.get_current();
        var web = clientContext.get_web();
    
        // Access the user catalog list, we assume the user is known to SharePoint or no results will be returned
        var userInfoList = web.get_siteUserInfoList();
         
        var camlQuery = new SP.CamlQuery();
        camlQuery.set_viewXml("" + user_id + "1");
    
        var colListItem = userInfoList.getItems(camlQuery); // Execute the query
        clientContext.load(colListItem); // Load the user item
         
        clientContext.executeQueryAsync(
            function (sender, args) {
                deferred.resolve(colListItem.itemAt(0)); // User has been found, resolve the deferred job
            },
            function (sender, args) {
                //Failure, reject the deferred job and return the error details
                deferred.reject({message: "Unable to locate user ID " + user_id + ":"+ args.get_message(), error: args});
                return null;
        });
        
        return deferred.promise()
    }  
}

So how is this used? Here are some example of how you can use these helpers.

Scenario 1 - we know the username

var ensure_deferred = $.sphelper.ensure_user("jonhay\\jhay");
ensure_deferred.done(function(ensured_user){
  
    var params = { "list_name": "Documents", "item_id": 1, "sp_user": ensured_user, "permission_type": SP.RoleType.reader, "action": "add"}
    var permission_deferred = $.sphelper.apply_permissions(params);
    permission_deferred.done(function(list_item){  
        console.log("Permission setting finished on item " + list_item.ID);        
    }).fail(function(err){
        console.log(err.message);
    })
    
}).fail(function(err){
    console.log(err.message);
})

Scenario 2 - we only know the SharePoint User ID

var user_details_deferred = $.sphelper.get_user_details(1) // 1 = User ID
user_details_deferred.done(function(user_details){

    // Login name is availiable from the users details object
    var ensure_deferred = $.sphelper.ensure_user(user_details.get_fieldValues().Name); 
    ensure_deferred.done(function(ensured_user){
  
        var params = { 
                        "list_name": "Documents",
                        "item_id": 1,
                        "sp_user": ensured_user,
                        "permission_type": SP.RoleType.reader,
                        "action": "add"
                     }
        var permission_deferred = $.sphelper.apply_permissions(params);
        permission_deferred.done(function(list_item){  
            console.log("Permission setting finished on item " + list_item.ID);        
        }).fail(function(err){
            console.log(err.message);
        })
        
    }).fail(function(err){
        console.log(err.message);
    })
        
}).fail(function(err){
    console.log(err.message)
})

I hope these code example are useful to someone with a similar problem. Please let me know via a comment below if I have made any mistakes; I was mostly writing from memory!

If you are interested in the final component I ended up with to manage permissions on a given list or library item I have added some screenshots below of it in action.


Comments

There are no comments

Post a comment

The red asterisk (*) indicates a mandatory field. Please ensure these fields are completed.