2

I am working on designing an approval system in CRM and need some inputs on the security design. The entity I am using has User/Team level R/W rights. The overall implementation is bit complex but to keep this question simple, consider the following two parties involved in the system:

Requester: Needs R/W access on requests created by him.

Approver Team: These are pre-defined teams whose users will approve/reject the request. Needs R/W access on requests which need their approval.

Question: How can I handle providing R/W access for both - Requester and Approver Team at the same time? Since we can't have multiple owners of a record in CRM, the Owner field can only contain either of them (Requester or Approver Team) at one time.

I can think of two solutions to this using sharing functionality and wanted to confirm my understanding:

a. Set Requester as the record Owner and share the record with Approver Team programmatically. The problem with this approach is that even if I share the record with Approver Team, I won't be able to show the sharing details on the main form (which is a requirement).

b. Set Approver Team as the record Owner and programmatically share the record with Requester using Access Templates.

Is there any better solution to handle this requirement, in case I am missing any OOB possibilities?

Ashish
  • 373
  • 1
  • 5
  • 15
  • What is the requirement about displaying the sharing details on the main form? Are there specific requirements about what to show? – Nicknow Apr 13 '17 at 22:20

2 Answers2

1

Your option a makes the most sense: Requester being the creator, should own the Request. Approver just acts on the Request, so it should be shared with.

About showing sharing details, you can put a subgrid in the form: https://www.microsoft.com/en-us/dynamics/crm-customer-center/create-a-team-template-and-add-to-an-entity-form.aspx

Add a team template to the entity form

Make sure you have the System Administrator security role or equivalent permissions in Microsoft Dynamics 365.

Check your security role

[read more in the linked page]

Since Requester is a USER an Approver is a TEAM, OOB you can only do option b (assign to the team, share with the user via Access Team).

I can't think of any clean solution involving enumerating the team members and act on each of them, so I won't suggest it.

Alex
  • 23,004
  • 4
  • 39
  • 73
  • Alex, as I mentioned earlier, problem with access template sub-grid is that it can only be used to show User records (and not Teams). In my case I will be sharing the record with an Access Team. So I need to show which all access teams this record is shared with. Thoughts? – Ashish Apr 13 '17 at 09:43
  • I missed the bit where Approver is a *team*. Then option **a** is no longer doable OOB, option **b** is (even though it's a bit less logical) – Alex Apr 13 '17 at 10:14
  • Agreed, it seems to be less logical. Is there any other way by which I can show all the teams with which a record is shared? – Ashish Apr 13 '17 at 10:17
  • Maybe doing some magic with the view XML might allow you to do it, for sure it's not doable OOB – Alex Apr 13 '17 at 10:18
  • Thanks Alex. I will check out that option. – Ashish Apr 13 '17 at 10:21
1

Well I believe that you can make solution A working with a little bit of coding (I'm not sure if you don't mind coding, but we are on StackOverflow, so I think you should consider that). First of all the design depends on the simple question - should this Request be shared with multiple teams, or only single team? Single team is simple - just add a lookup on the Request, that will point to a Team. When this team is filled in (I'm assuming that choice of this team is done somehow automatically, but it does not matter as in any scenario you would have to choose the team anyway somehow), you run a simple plugin that shares the record for this team. Sharing using SDK is really simple, just use the GrantAccessRequest:

var grantAccessRequest = new GrantAccessRequest
{
    PrincipalAccess = new PrincipalAccess
    {
        AccessMask = AccessRights.ReadAccess | AccessRights.WriteAccess,
        Principal = teamEntityReference
    },
    Target = requestReference
};

So on the form of the request you will keep the owner of the Request and will have a lookup pointing to a Team that is handling this request. Of course you can further pimp it up by for example un-sharing when the request is accepted or declined or the lookup on the request is changed etc. That would keep the POA table more happy as sharing huge amount of records can lead to fast grow of that table, so it's important to unshare records if sharing no longer needed.

If you want to share to multiple teams, you can still create a N:N relationship between your Request and Team and simply share your Request in a plugin on Associate message between Request and Team (this was a standard option before Access Teams were introduced for the users, remains still the only option for teams). This relationship can be show as a subgrid on Request form (it would look like an access team subgrid).

Of course to prevent users from Sharing the Request record on their own (in that case you will not have the Team in your lookup/subgrid) they should not have Sharing privilege. The plugin should do the sharing in admin context.

UPDATE: As for the POA considerations from the comments: both solutions will make your POA grow, because for both solutions you will have to share the Request either with the team or with the user. If you will use access team you will still have one POA entry for each Request (so 100K entries per year). I believe that the most important thing here is what happens with the Request when it ends it's lifecycle. If it does not have to be visible to the Team, after it was accepted/rejected then you should simply have a mechanism (plugin or some custom app running on some timely manner) that would unshare all the Requests that no longer require sharing, keeping your POA table in reasonable size.

There is another way of handling your scenario that would not require that much sharing/unsharing logic. You can create a "Request Acceptation" entity in 1:N parental relationship with Request. Because it's parental relationship, user owning Request will see all the "Request Acceptation" and "Request Acceptation" will be owned by proper Team (so only this team will have access). Of course I don't know anything about the business logic, but I assume that "Request Acceptation" can contain only the information relevant to the Team which can be copied in a plugin or workflow.

UPDATE2: As I just saw that you cannot unshare the record at a later stage. But I'm assuming that at some point of time Request is done/accepted/finished/rejected or whatever. If at this point both Teams and User should have access to this Request, then maybe it's a good thing to create some kind of separate entity "Archived Requests", that would not be shared, simply cloned for all the principals that are interested in seeing this information and deleting original Request. There are many variations of this idea, I hope that you get it and can adapt it accordingly to your scenario

Pawel Gradecki
  • 3,476
  • 6
  • 22
  • 37
  • Thanks Pawel. To answer your first question, the request needs to be shared with one team at a time. This team will change over the course of request, but at one time it would be one team. So one option would be to share/unshare the request programmatically whenever needed (as you mentioned in the answer). – Ashish Apr 17 '17 at 09:15
  • However, I am also trying to think what would be the best solution considering the POA growth in mind (or if there is any alternate way to totally avoid it). Kindly correct me if wrong, as per my understanding, sharing with 'one access team' is equivalent to sharing with 'one user' in terms of POA growth. But in either case, every request I create will create one POA entry. We have around 100K+ requests created every year. I looked around for ways to optimize sharing in CRM but I am not getting an exact figure for optimim POA size. I can't unshare the record at a later stage. Thoughts? – Ashish Apr 17 '17 at 09:35
  • Both solutions will create at least one POA entry. You should simply clean unnecessary sharing data from time to time. I updated my answer. I've also added another idea which in my opinion will make the POA as small as possible, but will require to create additional entity and some logic to handle data transfer between entities. – Pawel Gradecki Apr 17 '17 at 09:51
  • Thanks for the prompt suggestions Pawel. Response for Update 1: Actually the request form is pretty heavy (with lots of fields and business logic on it). Adding to this, there are other functionalities of adding additional request approvers, request watchers, etc. which I didn't mention in the initial question. Considering these complexities, I am trying to keep the request form as the main form where all of the approvers would come and mark their response. Although I am using a custom Approval entity to keep track of the approvers' individual response, but that's just behind-the-scenes. – Ashish Apr 17 '17 at 11:47
  • Response for Update 2: This is something which can be helpful. On the final completion of the request lifecycle, we can clone the request details and assign the individual cloned records to the respective stakeholders. For example, Clone 1 will be assigned to the Requester, Clone 2 to the Approver Team, Clone 3 to the Additional Approver, and so on. Let me know if you were saying otherwise. – Ashish Apr 17 '17 at 11:53
  • Yes that was basically the idea that I had in mind. Of course you will have to unshare the original request to shrink the POA table. – Pawel Gradecki Apr 17 '17 at 12:04
  • Thanks Pawel. Just one more scenario here (maybe not directly related to the question, but to the discussion). So now suppose a request is assigned to the requester and shared with a Team using R/W access (as discussed in the answer above). This Team is a User-Created Access Team and has 3 users. All of these users need to approve the request (which I am tracking using a custom Approval entity). (Continued..) – Ashish Apr 18 '17 at 09:56
  • Now consider that User 1 comes and approves this request. At this point, the request should become read-only for User 1 but User 2 and 3 still have R/W rights (since they haven't approved the request yet). Similar thing will happen when User 2 or 3 comes and approves the request. I believe the only way to deal with scenario would be have some code logic setup on request form load and make the form fields as read-only for User 1. Just wanted to confirm if I am thinking on the right track. – Ashish Apr 18 '17 at 09:58