CRM Security Model Internals


In this post I’ll describe some of the CRM Security Model internals. I’ll describe how roles and privileges are used under the covers, how security is enforced for different operations, and some of the internal structures that make it possible.

Overview

Just in case we need a quick refresher on the basics...

The CRM security model uses Windows-based authentication (Kerberos/NTLM), and internally-driven authorization. It’s basically a role-based security model, where a role is conceptually some class of person (Marketing Professional, System Administrator, etc.). CRM supports object-level (i.e. row-level) security, with support for sharing and assignment. Depending on your role and sharing information, you can have access to an object even if you’re not the owner.

Implementation

In v1.0 and v1.2, CRM used windows security descriptors and the windows AccessCheck function (actually AuthzAccessCheck) to actually validate a user’s access. A security descriptor is the same thing controls your rights on files, directories, etc. A CRM security descriptor would contain information about what users, teams, and roles had what specific rights on an object, and it would be stored in the row of the object. Users, teams, and roles would all be represented by some AD object (user object or an AD group). To check access, we would retrieve the security descriptor and call the AccessCheck function using the token of the user who was logged into CRM, which would contain the groups of all the roles and teams of which they were a member.

In v3.0, we moved to a database-driven security model since it gave us many benefits over the security descriptor model.

Roles and Privileges

A privilege is conceptually a right on an action. For example, example, holding the “Read Account” privilege gives you the right to try to read an account. Without the privilege, we don’t even bother to show the Account entity in the UI. We call this the PrivilegeCheck. What actually controls your access to a particular object instance (or row), is the depth of privilege and the relative depth of the business unit to which you belong to the business unit to which the object belongs (unless it’s an Organization-Owned object). We call this part the AccessCheck. Here’s an example:

If Bob has the “Read Account” privilege at the Parent:Child depth, he can read account A, but not Accounts B or C. If he didn’t have the privilege at all, Accounts wouldn’t even show up in the UI.

Depth is the pie-pieces you see in the Role Editor UI. So a role is really a collection of {Privilege, Depth} pairs, and a full security check = PrivilegeCheck + AccessCheck (AccessCheck implicitly checks privileges, too, but a PrivilegeCheck is more efficient since we don’t need any object information to deny someone who doesn’t even have the basic privilege).

Sharing and Assignment

CRM also supports sharing and assignment. Assignment is obviously just changing the owner of an object. Sharing provides the ability to override AccessCheck-restrictions, but PrivilegeCheck-restrictions still apply. So in the example above, if Account B was shared with Bob with Read rights, he could read B, but still not C. If Bob didn’t hold the read privilege at all, he still couldn’t read A, B, or C, even with the sharing.

PrivilegeCheck and AccessCheck

Okay, what actually happens with security when you view a grid, update an Account, etc.? Let’s start by going through the flow of updating an Account. I’ll look at it from an SDK perspective, since the UI actually does some additional stuff. Here’s the flow:

After the privilege check, we continue on with other business logic like basic validation of input against business rules or other entity-/operation-specific stuff. For the access check, we need to retrieve some security information from the row of the Account. In particular, we need to know the Primary Key, OwnerId, and OwningBusinessUnit. Once we have that, we can do the access check:

Let’s go through the steps here:

1. If the user is the owner of the record, the depth stuff doesn’t matter (remember, we’ve already gotten past the PrivilegeCheck part), so they have access.


2. Next, we need to determine the minimum privilege depth required:

     a. If the user is in the same business unit as the object, the user needs “Business” depth (half a pie from the UI).

     b. If the object is in one of the child business units of the user’s, the user needs “Parent:Child” depth (3/4 pie from the UI).

     c. Otherwise, the user will need “Organization” depth to read the object (for example, if it’s in a parent or sibling business unit).

We can do all these checks from cache lookups: the user’s business and the organization’s business hierarchy are cached, as well.

3. Then we find out if the user has all the required privileges at the minimum required depth. Included in the privilege information we cache for the user is the maximum depth at which they hold the privilege.

4. If the user doesn’t get access through the owner checks or role-based checks, we check sharing next. CRM stores all sharing information in a table called PrincipalObjectAccess which maps users and teams to objects that have been shared to them explicitly or via cascaded operations.

5. If the user still doesn’t have access by this point, we return an Access-Denied error.

So basically that’s the security part of what happens when you use the Update API or when you click “Save” on a form (though we wouldn’t even render the “Save” button if you didn’t hold the privilege).

What about when you view a grid? Well, we don’t want to retrieve a bunch of records and then do an access check row-by-row in the middle-tier. We want to be able to do the security checks in SQL so we can get good paging performance and not retrieve more records than are necessary to satisfy the user’s request.

So we invert the process slightly. Instead of determining a minimum “Read” privilege depth by looking at the relative positions of the user and the object in the business-hierarchy, we get the maximum “Read” privilege depth on the entities being queried and use that to limit the query to those business units the user will be able to read. We also do the ownership check and sharing checks as part of the query as well (we still do a PrivilegeCheck for “Read” on the entities being queried first, of course):


SELECT top 51 AccountId

FROM Account

WHERE

-- user is the current user

(Account.OwningUser = <current_user>)

OR

-- user gets access via role access (this example is checking Parent:Child)

(Account.OwningBusinessUnit in

(SELECT SubBusinessId

FROM BusinessUnitMap

WHERE BusinessUnitId = <current_user_business_unit>))

OR

-- user gets access via sharing

(AccountId in (SELECT ObjectId

FROM PrincipalObjectAccess poa

JOIN SystemUserPrincipals sup on poa.PrincipalId = sup.PrincipalId

WHERE sup.SystemUserId = <current_user> AND

poa.ObjectTypeCode = <entity_object_type> AND

((poa.AccessRightsMask | poa.InheritedAccessRightsMask)&1) = 1)))

This is a simplified query for a user who has the “Read” account privilege at “Parent:Child” depth. The user can get access through ownership, role access, or sharing. The ownership check is pretty straightforward. For the role check, we need to look at objects in the user’s business or child businesses because they have “Parent:Child” access. We use a utility table, BusinessUnitMap, that denormalizes the business hierarchy into a flat list for each business unit.

For the sharing check, we need to retrieve the entity rows where the PrincipalObjectAccess table has entries for the entity with read access (the “& 1” part) where the Principal is the user or any teams of which the user is a member. We use the utility SystemUserPrincipals table to get a flat list of all the principals of which the user is a member (including a self-mapping to the user). The “top 51” just means we are retrieving the first page of a grid with 50 records per page. The extra record is used to let the caller know whether there are more records or not.

Wrapping Up

I hope you found this brief look under the covers interesting. If you have any questions, comments, requests on the CRM Security Model, we’d love to hear from you -- please add a comment to the post and I’ll be happy to respond.

Jay Grewal

Comments (14)

  1. On the Microsoft Dynamics CRM Team Blog, Jay Grewal has posted information about the CRM Security Model…

  2. Paul Turner says:

    Great article on the internals.

  3. Bilal Yasseen says:

    Hi Jay,

    What’s the best way to generate an Access Matrix Report for MS CRM 4.0?

    For each entity (custom or system), the report should display:

    – the Access Rights (Create, Read…) as rows

    – the Security Roles as columns

    – the data as the privilage depth (user, business unit…) if the role has access otherwise display "No Access"

    Kindly note that this is really urgent.

    Your Help is really appreciated.

  4. Kumar says:

    Good article. We have a situation related to security(in CRM 4.0) where we all users have access to Read + Edit all Accounts but if a certain account is marked as "Secret", only a defined list of users should be able to Read + Edit it and all other users will not even have read permissions to that record.This list of users is per account level and is dynamic. You can add/remove people from this list. How can this be implemented using CRM security model?

  5. Frank Lee says:

    If needing a more dynamic "Secret" list of users – either setup a busness unit/security role setup that supports it OR use Record Sharing to achieve it.

    Frank Lee, Microsoft Dynamics CRM MVP

    http://microsoft-crm.spaces.live.com

    http://www.workopia.com/Links.htm

  6. Niamh says:

    Hi Jay,

    I have a similar situation. I am working with a sales organisation. They want users in different divisions not to be able to see each other’s contacts, opportunities, quotes etc. I have acheived this using business unit and role setup. However they want to make a list of certain contacts available to the whole organisation, but want to keep the inofrmation below the contact such as opportunity, quote private to just the individual divisions. If they use sharing to make records available to all, then users can automatically see all information related to that contact (opportunities/quotes). I cannot see any way to acheive this using business units and roles as they only want to share certain contacts. Any advice would be appreciated.

  7. Shilpa says:

    Hi,

    Couls you please let me know an efficient way of checking whether user has a sharing to particular record theu "Principal object access". We have over 1 crore records in POA table. Dependind on this we need sahre the record to user thru security principal class….. Currently so hare around 10 opp(along with sharing accounts, conatc and few other custom entities are taking long time).

    Pls suggest on the same.

    Thanks in advance…..

  8. reicoskyr says:

    Has anyone seen the message: Users cannot add privileges to or change access levels for roles to which they are assigned.  For Help with Changing a role, contact your CRM administrator.  

    The problem is, I am the System Administrator, and still get this message, and I do not belong to the role that I am trying to change.  Any ideas

  9. Anne Stanton says:

    Jay –

    Thanks for writing this up. It is still helpful two years later 😉

    Anne

  10. Austin says:

    I don’t understand why it isn’t possible to assign a user to a Security Role within other business units. Sharing has to be set on the account or contact, which means each time a new account or contact is setup someone has to remember to share it. If the user could just take on a role from another Business Unit, then they would have the proper access to existing and new accounts without further work.Microsoft has really limited CRM by implementing their security this way. How SAD. Now i have to jump through hoops to make a unmanageable workaround.

  11. mikeclifton82 says:

    Jay,

    I am also looking to generate a security matrix for CRM 4.0 that elaborates each role’s permissions across entities.  What is the best way to accomplish this?

    Mike

  12. Ivan Zachekan says:

    Could you tell with this sample where "Account modify" check occurs ?

    Does it in some CRM Service or maybe it has done in database via views or stored procedures ?

  13. TC says:

    I am also looking to generate a security matrix for CRM 4.0 that elaborates each role's permissions across entities.  What is the best way to accomplish this?

    TC

  14. eccountable says:

     I have a question about how the CRM SDK works with POA. I have turned on a feature in CRM to permit all past owners of a record to have rights to view the record (settings/administration/system settings/”share reassigned records with original owner”) but when I change the owner using the SDK Update method, it is not updating the PrincipalObjectAccess table. Why is that? Do you think this was just overlooked by the CRM developers? Should I have called another SDK function or stored procedure?

    Since I have over 300k records to update, I was able to come up with a solution which was to create a workflow that changed the ownership (and updated PrincipalObjectAccess) to what I wanted, and I was able to start the workflow through the SDK, but I am not satisfied that I needed to have 2 parts to my code to make this work.

Skip to main content