CRM 2011 Validate User Security Roles using JavaScript/REST

This example I’m sharing today is demonstrating a few techniques which promote JavaScript and REST, along with using namespaces and creating a library of functions/properties. This example declares a library Security.UserInRole and defines a number of properties and functions. The general idea is to allow the data access via REST to be asynchronous which then offers a valid and invalid function callback option to be defined to handle the outcome. You can specify an array of security roles to check against the current user and then with the callback functions you can perform the actions that you require.

Ideally I want to call a function that is easy to use and it will look like this.
Security.UserInRole.checkUserInRole(
["System Administrator", "System Customizer", "Custom Role Name"],
function(){alert("valid"); // The user is in one of the specifed roles.
},
function(){alert("invalid"); // The user is not in one of the specifed roles.
}
}

To define the library namespace and object we use
//If the Security namespace object is not defined, create it.
if (typeof (Security) == "undefined")
{ Security = {}; }
// Create Namespace container for functions in this library;
if (typeof (Security.UserInRole) == "undefined") {
Security.UserInRole = {
__namespace: true
};
}

 

The library functions and properties declared include the following
Security.UserInRole = {
isInRole: null,
roleIdValues: [],
validFunction: null,
invalidFunction: null,
checkRoles: [],
checkUserInRole: function (roles, validFunc, invalidFunc) {},
getAllowedSecurityRoleIds: function () {},
validateSecurityRoles: function () {},
querySecurityRoles: function (queryString) {},
__namespace: true
};

 

The entire library is implemented so that you call a function, it performs the processing asynchronously and then gives you the outcome to handle the response. Whether you want to show/hide form elements or disable fields etc, you can handle this in the callback function parameters validFunc and invalidFunc defined in the checkUserInRole function. The entire library content can be placed in a CRM webresource and added to a form. The full library is as shown below.

//If the Security namespace object is not defined, create it.
if (typeof (Security) == "undefined")
{ Security = {}; }
// Create Namespace container for functions in this library;
if (typeof (Security.UserInRole) == "undefined") {
Security.UserInRole = {
isInRole: null,
roleIdValues: [],
validFunction: null,
invalidFunction: null,
checkRoles: [],
checkUserInRole: function (roles, validFunc, invalidFunc) {
validFunction = validFunc;
invalidFunction = invalidFunc;
checkRoles = roles;
Security.UserInRole.getAllowedSecurityRoleIds();
},
getAllowedSecurityRoleIds: function () {
var filter = "";
for (var i = 0; i < checkRoles.length; i++) {
if(filter == "") {
filter = "Name eq '" + checkRoles[i] + "'";
}
else {
filter += " or Name eq '" + checkRoles[i] + "'";
}
}
Security.UserInRole.querySecurityRoles("?$select=RoleId,Name&$filter=" + filter);
},
validateSecurityRoles: function () {
switch (Security.UserInRole.isInRole) {
//If the user has already been discovered in role then call validFunc
case true:
validFunction.apply(this, []);
break;
default:
var userRoles = Xrm.Page.context.getUserRoles();
for (var i = 0; i < userRoles.length; i++) {
var userRole = userRoles[i];
for (var n = 0; n < Security.UserInRole.roleIdValues.length; n++) {
var role = Security.UserInRole.roleIdValues[n];
if (userRole.toLowerCase() == role.toLowerCase()) {
Security.UserInRole.isInRole = true;
// Call function when role match found
validFunction.apply(this, []);
return true;
}
}
}
// Call function when no match found
invalidFunction.apply(this, []);
break;
}
},
querySecurityRoles: function (queryString) {
var req = new XMLHttpRequest();
var url = "";
// Try getClientUrl first (available post Rollup 12)
if (Xrm.Page.context.getClientUrl) {
url = Xrm.Page.context.getClientUrl();
}
else {
url = Xrm.Page.context.getServerUrl();
}
req.open("GET", url + "/XRMServices/2011/OrganizationData.svc/RoleSet" + queryString, true);
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.onreadystatechange = function () {
if (this.readyState == 4 /* complete */) {
req.onreadystatechange = null; //Addresses memory leak issue with IE.
if (this.status == 200) {
var returned = window.JSON.parse(this.responseText).d;
for (var i = 0; i < returned.results.length; i++) {
Security.UserInRole.roleIdValues.push(returned.results[i].RoleId);
}
if (returned.__next != null) {
//In case more than 50 results are returned.
// This will occur if an organization has more than 16 business units
var queryOptions = returned.__next.substring((url + "/XRMServices/2011/OrganizationData.svc/RoleSet").length);
Security.UserInRole.querySecurityRoles(queryOptions);
}
else {
//Now that the roles have been retrieved, try again.
Security.UserInRole.validateSecurityRoles();
}
}
else {
var errorText;
if (this.status == 12029)
{ errorText = "The attempt to connect to the server failed."; }
if (this.status == 12007)
{ errorText = "The server name could not be resolved."; }
try {
errorText = window.JSON.parse(this.responseText).error.message.value;
}
catch (e)
{ errorText = this.responseText }
}
}
};
req.send();
},
__namespace: true
};
}

 

To use the library in the onload event of an entity form simply add the Security.UserInRole library webresource to the form and create a another JavaScript web resource to hold the onload function. The onload function may look like this. You define the roles to check as an array and pass this to the checkUserInRole function along with the valid and invalid callback functions. You don’t have to define the functions as anonymous functions like my example below but it can sometimes feel cleaner.

function onload()
{
Security.UserInRole.checkUserInRole(
["System Administrator", "System Customizer", "Custom Role Name"],
function(){alert("valid"); // The user is in one of the specifed roles.
},
function(){alert("invalid"); // The user is not in one of the specifed roles.
}
);
}

 

I would like to mention Jim Daly from the MS CRM Team for his examples of namespace and library structure along with his REST query code as this example I have created is derived from his outstanding work.

Happy Coding..

Silverlight + XRM Book by David Yack MVP

Building the next generation of business applications using Microsoft Silverlight and Dynamics CRM 2011

Fellow MVP David Yack has produced the perfect guide to Silverlight with Dynamics CRM 2011. I highly recommend this book for people new to Silverlight or Dynamics CRM but I also think  those already working with either Silverlight or Dynamics CRM should check it out. I’ve been reading through it this past week and have been learning key things that will help during the design and implementation of my latest projects. I wish I had this book earlier, as my CRM tools on Codeplex could have really benefited from the details in this book too. It shows the knowledge and understanding David has of Microsoft’s latest incarnation of CRM along with Silverlight and how to integrate them.

David Yack MVP is the CTO of Colorado Technology Consultants based in Colorado. He is a Microsoft Regional Director and a Microsoft MVP for Silverlight. This guys knows his stuff…

For an overview of the book’s chapters check it out here, and here is a quick summary of chapter titles.

Chapter 1 – Using Silverlight with CRM 2011 
Chapter 2 – Getting Started
Chapter 3 – XAML 101 and Basic Layout
Chapter 4 – Silverlight Controls
Chapter 5 –Data Binding Basics
Chapter 6 – Application Navigation
Chapter 7 – Out of Browser Support
Chapter 8 – Application Composition with MEF
Chapter 9 – Other Silverlight Business Application Features
Chapter 10 – Enhancing the User Experience
Chapter 11 – Discovering SketchFlow
Chapter 12 –Interacting with CRM Form
Chapter 13 –OData Basics
Chapter 14 –OData Beyond the Basics
Chapter 15 – Using the WCF Service
Chapter 16 – Application Architectures
Chapter 17 –Silverlight Debugging

There is now no excuse for not producing high quality Silverlight apps when developing for Dynamics CRM.

CRM 2011 Discovery and Web Service URLs

CRM 2011 Discovery and Web Service URL’s to use, based on the Developer Resources Page in CRM.

Here is the information that you need:

For CRM Online customers:

The following URLs should be used to access the discovery service (use the appropriate URL for your location):

https://dev.crm.dynamics.com/XRMServices/2011/Discovery.svc (North America)
https://dev.crm4.dynamics.com/XRMServices/2011/Discovery.svc (EMEA)
https://dev.crm5.dynamics.com/XRMServices/2011/Discovery.svc (APAC)

The following URLs should be used to access the Organization service(SOAP endpoint):

https://{Organization Name}.api.crm.dynamics.com/XrmServices/2011/Organization.svc (North America)
https://{Organization Name}.api.crm4.dynamics.com/XrmServices/2011/Organization.svc (EMEA)
https://{Organization Name}.api.crm5.dynamics.com/XrmServices/2011/Organization.svc (APAC)

Where {Organization Name} refers to the Organization that you specify in the URL when accessing the Web application. For example, for Contoso.crm.dynamics.com, the {Organization Name} is Contoso.

The following URLs should be used to access the Organization Data service(OData REST endpoint)

https://{Organization Name}.api.crm.dynamics.com/XrmServices/2011/OrganizationData.svc (North America)
https://{Organization Name}.api.crm4.dynamics.com/XrmServices/2011/OrganizationData.svc (EMEA)
https://{Organization Name}.api.crm5.dynamics.com/XrmServices/2011/OrganizationData.svc (APAC)

For CRM On-premise
s customers:
http://{server}/XRMServices/2011/Discovery.svc for the Discovery service endpoint
http://{server}/{OrgName}/XRMServices/2011/Organization.svc for the Organization Service endpoint (SOAP)
http://{server}/{OrgName}/XRMServices/2011/OrganizationData.svc for the Organization Data Service endpoint (REST)
http://{server}/XRMDeployment/2011/Deployment.svc for the Deployment Service endpoint