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..