Finally there: Show and hide fields based on the users role!

Hi all,

I've been posting around showing and hiding fields more often. I won't be talking about that now, instead I'll be discussing the ability to perform JavaScript coding based on the users role.

This solution is very neat and very clean, though unsupported!!! Now... Let's get started and dig into this.

MS CRM has a lot of Javascript codes delivered with the product. One of these codes is located here: "/_controls/RemoteCommands/RemoteCommand.js". This file is being included into every page. One of the functions in this file is RemoteCommand(sObject, sCommand, sUrlBase). You can use this function to connect to CRM webservices.

Via Javascript you can get the userid of the currently logged in user via this RemoteCommand. This is done using this code:


var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();


Another webservice which is interesting is the UserManager webservice. By using this function, you can get all roles of the system. The roles to which the user is assigned are marked with "checked='true'". Here's the code to get this list


var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");
var oResult = command.Execute();


Now if we add this functionality to functions and add some try catches, then you'll end up with this code:


function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue.UserId;
}
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}

function getUserRoles(userId)
{
try
{
var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");

var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue;
}
}
catch(e)
{
alert("Error while retrieving roles.");
}
return null;
}


Now we only have to add functions which checks if the user has a specific role. The final code is:


function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue.UserId;
}
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}

function getUserRoles(userId)
{
try
{
var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + userId + "</guid>");

var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue;
}
}
catch(e)
{
alert("Error while retrieving roles.");
}
return null;
}

function userHasRole(userId, roleName)
{
result = getUserRoles(userId);
if (result != null)
{
var oXml = new ActiveXObject("Microsoft.XMLDOM");
oXml.resolveExternals = false;
oXml.async = false;
oXml.loadXML(result);

roleNode = oXml.selectSingleNode("/roles/role[name='" + roleName + "']");
if (roleNode != null)
{
if (roleNode.selectSingleNode("roleid[@checked='true']") != null)
return true;
}
}

return false;
}

function currentUserHasRole(roleName)
{
userId = getUserId();
return userHasRole(userId, roleName);
}


Now you can check for roles by using the code listed down here:

if(currentUserHasRole('Salesperson')){
alert('true');
}else{
alert('false');
}


You can copy and paste the functions as well as the last code in the form onload event. This is not really user friendly / readable though. If you copy and paste the functions to the global.js script instead, then you can use the last code snippet in the form onload.

Now. To get back to the subject "show and hide fields based on the users role"... If you modify the last code snippet to contain the showing and hiding fields as discussed in this article, instead of alert('true') and alert('false'), then you've got a very good customized system.

Thanks go out to Steven Brom (Qurius Advanced Solutions - NL) for supplying the codes!

Update 1: Thanks to Josh Painter who pointed me to the fact that I forgot to replace the < sign with <> in the codes.

Update 2: I mentioned that this solution was supported, but since we're using webservices from MS CRM other then the regular webservice, this is unsupported anyway... But still it's cool ;)

Update 3: This code doens't work on the outlook client. I'm still thinking about a solution to that.

Update 4: The code above is created for CRM 3.0. For CRM 4.0 look at the following blogs:
http://www.crowehorwath.com/cs/blogs/crm/archive/2008/05/08/hide-show-fields-in-crm-4-0-based-on-security-role.aspx
http://jianwang.blogspot.com/2008/01/crm-40-check-current-users-security.html

53 comments:

RémiW said...

Hi Ronald,

Does it work offline with CRM laptop client for Outlook ?

Great tip! Thanks !

Ronald Lemmen said...

Hi remiw,

This does work with the laptop client, but only if you put all the codes in the form onload event. The modifications to the global.inc are not synced to the laptop. If you want to use a separate .js file, then this will need to be deployed to each laptop manually.

Kind regards,

Ronald

Anonymous said...

Ronald,

Thanks for this post and others related to customizing the IU at runtime.

When I implement the code you posted (pasted it directly in the onload), I always return the "false" alert, even when loggedin as a user with a matching role. Any ideas?

Ronald Lemmen said...

Hi,

Some things to check:
- Are you using the exact role name (case sensitive) ?

- What do you get when checking the userid?
alert(getUserId());

- What do you get when checking the roles?
userId = getUserId();
alert(getUserRoles(userId));

What system are you trying this code on (VPC/Server)? Is this the MS Demo VPC?

Let's see if that helps.

Anonymous said...

Thanks for your quick reply. Ok, here are the results to the questions you asked:

-I'm using the Salesperson role just as the sample code
-The UserId alert returned the correct UserId for the LoggedIn User, but the brackets around the Guid are missing
-The role alert returned a beginning and ending HTML tag with the word "roles" inside each tag. Nothing in between (I could not post the actual text because it was being picked up as an HTML tag within this post)
-I am indeed testing this in the MS Demo VPC

Ronald Lemmen said...

Hi,

I've been testing in the MS Demo VPC now as well. I'm experiencing the same behaviour there. In a fresh install, this behaviour does not show up. I'll have to dig into this to find out the difference.

I'll let you know if I know more.

Anonymous said...

Thanks Ronald...

I'll implement in a fresh install and give it a go.

Anonymous said...

I implemented the code in our production system and the behavior is the same. Anyone get this to work?

Ronald Lemmen said...

Interesting that you can't get it to work yet. I'll need to dig into this. As soon as I find interesting information, I'll post it!

enderC said...

Hello everyone, I think I figured this one out. Looking at the GetUserRoles method on the User Manager webservice (/appwebservices/usermanager.asmx) shows that it accepts multiple <guid/> nodes under the <userIds/> node. I got it to work by wrapping the guid returned from getUserId() function like this:

var command = new RemoteCommand("UserManager", "GetUserRoles");
command.SetParameter("userIds", "<guid>" + getUserId() + "</guid>");
var oResult = command.Execute();

Let me know if that works for you too...

Josh Painter

Ronald Lemmen said...

Hi Josh,

Thanks for your comment. This indeed seems to work!

I'll update the code in the posting.

Ronald

Anonymous said...

Hi Ronald,

Is there anyway to hide fields based on business unit?

Ronald Lemmen said...

I cant find a built in function which offers you this option. You could write your own webservice which accepts a user guid and returns the businessunitname to which the user belongs. You can then use this business unit name in your code. Should work :)

Anonymous said...

Hi ronald.

thnx for posting this code.. and thnx to Josh for the wrapping guid returned code.

Im having an issue with this code.

I am using the "System Administrator" role (exact name - case sensitive)

alert(getUserId()); returns correct Userid

alert(getUserRoles(userId)); returns

role id role name business unit id and business unit name for every single role in the system except for the role that is assigned to the user..

the user is only assigned one role the system administrator role, no others

This is being run on a test network not VPC.


The code im using is exactly as it is in your blog besides the added wrapping guid code witch i ammended from the post from josh
copied and pasted from your blog

I recieve false for a role that the user definately has..

any ideas as to what im doing wrong?

Any help would be greatly appreciated..

Thanks in advance

Liath

Anonymous said...

hi Ronald..

Ignor my last comment..

it was "pilot error"

me being dumb..

Thnx for posting this .. works perfectly....

Liath

Anonymous said...

lo estoy intentando hacer pero como hago para hacerlo directamente desde el crm

Ronald Lemmen said...

Hi,

I do speak Dutch, English and German very well and a little bit Finnish and French. Unfortunately I don't speak Spanish. Could you please translate this for me?

Thanks!

Jim said...

Hi Ronald,

This worked great for the web client, but i had issues getting it to work with the laptop client. It wasn't able to lookup the user roles. Any advice?

Thanks,
Jim

Anonymous said...

I too am having issues with the Laptop client. All code is in OnLoad event. We are "seeing error while retrieving userid, roles,etc" messages.

Advice is appreciated!

Debra

Anonymous said...

I want to populate the Current User's Name in custom fields on the Case form (such as new_InitialResponseBy). I thought I could just add onChange event to the bit field that triggers the value to populate in the text field, but when I display an alert of the oResult, I get the Object and undefined. I must be missing something...do I need more than the onChange event in jScript?

Here is all of the code I inserted:
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();
alert (command+"\n"+command.DataValue+"\n"+oResult+"\n"+oResult.DataValue+"\n"+oResult.ObjectId+"\n"+oResult.id+"\n"+oResult.name);

Thank you!

Anonymous said...

I should have kept digging, but for those as lost as I am, this worked...

var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();
alert (oResult+"\n"+oResult.ReturnValue.UserId);

Ronald Lemmen said...

That was fast :) I even hadn't time to read your message before you posted the solution already!

Anyway great to hear that it is solved and that you could use this script.

Ronald

Anonymous said...

OK...I'm stumped again! I have the GUID but I actually want to populate the name of the current user into my custom field.

Is that possible using only Jscript? I tried to use the SDK to find out more about the RemoteCommand...is it just me or do others have a hard time using .net SDK? My background in college was in Java and much easier to locate methods...I MUST BE DOING SOMETHING WRONG!!!

Signed,
VERY FRUSTRATED SDK USER!

Ronald Lemmen said...

Hi,

You wont find any information in the SDK for the RemoteCommand function because it is fully UNSUPPORTED.

Looking to your requirements, why wouldn't you use a post callout to do this? You wont see the user name on the screen as soon as a case gets opened, but as soon as it gets saved, then you can find out the user name fairly simpel. Just take the name of the owner and store this in the 'new_initialresponse by' field. That is supported and documented in the SDK. You will find that a lot better for your situation.

Hope this helps,

Ronald

Mark K said...

Hey I am not sure how this can work in the Outlook Laptop client on or offline. I was just looking at the "\AppWebServices" directory of the Outlook Client installation (Laptop Version) and there are two files less than the server directory.

-Reports.asmx
-UserManager.asmx

Ronald Lemmen said...

Mark,

You are right that this wont work in the outlook client. I'm still working on a solution to that.

Ronald

Saritha said...

It will be great if you can show the date along with the time when the comments were made!
As of today, I could get this code to work with outlook client (desktop). I don't know what the other's comments are referring to about not working in outlook client, do they mean outlook client for laptop?

Ronald Lemmen said...

Hi Saritha,

The others are indeed referring to the offline client (laptop client). That client can't connect to the CRM webservices. The desktop/online client gets all its data from the real CRM server and not from a local datastore. Therefore it will be able to connect to the CRM webservices.

Hope this answers your question.

Kind regards,
Ronald

PS, I'll take a look soon to see if I can also show the dates with the comments :)

godmcse said...

Hello Ronald,
How was your trip? Hope all went well. I wish I was there but too short of a notice.

I have the above code in my script. But when I am using it in the Outlook client (online or Offline) I get an "Error while retrieving roles." The next message is to contact system administrator.

Also I had the code is a new script that I was calling from the Onload function. It most cases it stated that an object is missing. So I put the code in the Global.js. it seems to work well with the web client. I am still having trouble with the outlook.

I did notice that the Outlook client is running off my local machine and not the CRM server. Yet I am online.

Any discovery on the outlook fix for the script above?
Thank you
Godmcse

godmcse said...

my above comment was posted on 3-14-07

Ronald Lemmen said...

Hi godmsce,

The code wont work in the offline client/laptop client. This doesnt matter if that client is online or not. The code only works with the online/desktop client.

There is no code yet for the other client.

Geert said...

Hi Ronald,

Do you have a solution yet for the offline laptop client?
With the offline laptop client we get errors with receiving the userID and role.

Ronald Lemmen said...

Hi Geert,

Unfortunately I cannot help you with this. There is still no information on how to do this offline..

Ronald

Abraham Saldana said...

Hi Ronald,

Thanks for your demo code, I also created a sample demo code calling my own web service using this type of javascript calls, I package my demo code and the instructions on my blog...in this implementation the demo is intended for the Case entity form and will validate for any open activities.

http://blog.crmbuzz.net/archive/2007/05/18/How-to-call-my-web-service-using-Javascript.aspx

Anonymous said...

What if you want to get the UID from a Javascript code snippet in a page which doesn't import the RemoteCommand.js?
We want to create a menu option that opens an aspx which should receive the UID as a parameter, and we are trying to include the JS code in the isv.config file... We haven't been able to figure it out yet : P

Congratulations for your helpful blog, by the way!

Anonymous said...

Ok, already got it solved thanks to this post, in case it could be useful to anyone: http://dmcrm.blogspot.com/2007/07/pulling-logged-in-user-via-javascript.html

Gino said...

Hi Ronald,

Thanks for the excellent code, I have seen and do appreciate that this will not work in the CRM laptop client, my question is, does anyone know if it possible to stop the error messages appearing for those users?
For example is it possible in Javascript to detect the client in use and only process the code if using an online client.

Thanks again,
Gino

Jaber said...

Hi Ronald
You have done great work.
I just played around with your code and I tried your code on the onLoad event of the form and works really fine. And for that i am able to skip step four.
Excellent job Ronald.
Regards.
Jaber

Anonymous said...

Another way to retrieve the security role is from the database. One can create a connection and use SQL to get it.
The example below checks on the existance of the role named "Sales". Keep in mind that a user can hold multiple roles. You will need to adapt the example so it fits your needs (SQL Server name & DB Name for sure). Here is the excerpt you can put in the on load event:
var bSales = 0;
var connection = new ActiveXObject("ADODB.Connection");
var connectionString = "Provider=SQLOLEDB;Server=YOUR_SQL_SERVER;Database=CHECKINSQLSEVER_MSCRM;Integrated Security=sspi";
connection.Open(connectionString);
var query = "SELECT count(RoleBase.Name) FROM SystemUserBase INNER JOIN SystemUserRoles ON SystemUserBase.SystemUserId = SystemUserRoles.SystemUserId INNER JOIN RoleBase ON SystemUserRoles.RoleId = RoleBase.RoleId WHERE SystemUserBase.DomainName = SUSER_SNAME() AND (RoleBase.Name like '%Sales')";
var rs = new ActiveXObject("ADODB.Recordset");
rs.Open(query, connection, /*adOpenKeyset*/1, /*adLockPessimistic*/2);
rs.moveFirst();

while (!rs.eof) {
bSales = rs.Fields(0).Value;
rs.moveNext();
}

connection.Close();

Based on the value of bSales (zero or not) you can disable fields.
Hence this is a database connection it should work for off line clients aswell (didn't check this (yet)).No need for the Web services. I hope this can help some people - Johan (Real).

Anonymous said...

Hi

I am trying to restrict users to certain records. E.g. users can only open leads that has been created by the actual user and not be able to open any Leads created by any other users.

function getUserId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI"/MSCRMServices/");
var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue.UserId;
}
}
catch(e)
{
alert("Error while retrieving userid.");
}
return null;
}

if (getUserId() != crmForm.all.ownerid.DataValue[0].id))
{
crmForm.all.tab0Tab.style.display = 'none';
crmForm.all.tab1Tab.style.display = 'none';
crmForm.all.tab2Tab.style.display = 'none';
crmForm.all.tab3Tab.style.display = 'none';
crmForm.all.firstname_c.style.display = 'none';
crmForm.all.firstname_d.style.display = 'none';
crmForm.all.lastname_c.style.display = 'none';
crmForm.all.lastname_d.style.display = 'none';
}

We would really apprecaite any ideas or suggestions.
Thanks alot

Anonymous said...

Hi

Just to give you a little more information with regards to the previous post.

We are trying to restrict users from accessing the details within a Lead or an Account that is not owned by them.

We would like users to be able to view all leads but be unable to actually access Lead information inside the record if the Lead is not owned by them.

We used your script but inserted the ownerid schema name but were unable to achieve the desired results. Really appreciate your assistance.

Thanks

CoMal said...

Someone was asking about hiding fields based on business unit.

The WhoAmI requests also returns the businessunit id but calls it MerchantId:

function getBusinessUnitId()
{
try
{
var command = new RemoteCommand("SystemUser", "WhoAmI", "/MSCRMServices/");
var oResult = command.Execute();

if (oResult.Success)
{
return oResult.ReturnValue.MerchantId;
}
}
catch(e)
{
alert("Error while retrieving businessunit.");
}
return null;
}

Ian Oldbury said...

Hi Ronald,

I'm looking at using this script with CRM 4.0, do you know the changes required to make this work? I've looked for the "SystemUser" webservice referred to and it doesn't seem to exist anymore. Any thoughts/ Ideas?

Regards
Ian

Jeroen said...

Ronald,
I also didn't succeed to get this script working on CRM 4.0

Anonymous said...

RemoteCommand is gone too!

Anonymous said...

Great stuff :) Thanks!
One problem though, i tried this in CRM4 RC0 and it didnt work!!!
So i cant use these great codes in our customers upgrades and i will have to come up with some other solutions.

Is their any work around to make it work with CRM 4 as well???

uri said...

Thanks Ronald,
it's perfect ;)

uri

Anonymous said...

Hi Ronald,

realy fine thing! It runs very well with a Web-Client.
I put the code in the onload event. And most of Outlook clients go fine with it. Just only a few machine in the network have problems with the onload scripts. If they go via web-client everything is fine!

Any idea?
Thx
Bernd

lorenzo said...

Hi guys,

I've been looking for the same functionality from CRM 4.0 as well. So here is what i found. http://jianwang.blogspot.com/2008/01/crm-40-check-current-users-security.html

Basically it's based on generating xml query manualy.

Alistair said...

Hi Ronald,

I have been working with CRM 3.0 to restrict field level access and have found a way to get your functionality to work with the Outlook client. All you need to do is specify a full path to the CRM Server in the RemoteCommand method, for example;

var command = new RemoteCommand("SystemUser", "WhoAmI", "http://{servername}:{port number}/MSCRMServices/");

This makes sure that the correct Web Service is called. There are of course two small caveats for this method;

- This will not work for Laptop clients that are not connected to the CRM Server i.e. Offline.

- You may get the following Internet Explorer warning message when the Execute() command is called : This page is accessing information that is not under its control. This poses a security risk. Do you want to continue?

To get rid of this message, change the IE options for the security zone (usually Local intranet) in which your localhost site is operating. Under the Miscellaneous section of the Security Settings change the 'Access data sources across domains' to Enable.

Regards

Alistair

Robert said...

I am also trying to get this to work in 4.0.

Some things learned:

RemoteCommand() DOES exist and is loaded into each page. (it is in different location on server, but still there)

Some of the 3.0 .asmx services are gone, like SystemUser, which is why Ronald's 3.0 example does not work.

Still don't have it working, but getting close....

Anonymous said...

Hope this helps:

http://crowechizek.com/cs/blogs/crm/archive/2008/05/08/hide-show-fields-in-crm-4-0-based-on-security-role.aspx

Microsoft CRM 3.0 Customization said...

terrific code... thanks for sharing dude...