Hello all, in this blog I want to explain you what CRM ACE is, what it can do, what the concept is and how you can implement it.
Introduction
Difference with ‘normal’ authorizations
The concept of ACE
The basic element in the concept of ACE is the actor. To explain this in the most easy way you can say this is the linking and filtering element between the user and the object. The actor determines if the user should see the object or not. As an example look at the following picture which explains the scenario that a user is only allowed to see business partners where he is in the sales team. The user is linked to an employee and these employees are stored in the sales teams of the business partners.
From the user’s perspective you can determine the employee id which is in the sales team. Also from the business partners perspective you can see who are in his sales team. If both of them match, the user can see the object. If you understand the concept of the actor you understand the ACE for 75% already.
How the actor from both perspectives is determined is stored in a rule. Here are three methods defined: how to determine the actors from the user, how to determine the actors for an object, and a method to specify which objects to take into account in the first place. This is shown in the following pic:
An ACE rule is a combination of a role and an action (read, write, delete). These rules you can assign to ACE user groups which you can link to individual users or in most cases to dummy ‘normal’ authorization roles which you can assign in the user master.
The nice thing about the concept of ACE is that when you activate it it fills the ACE tables with data so it can later during runtime determine very fast who is allowed to see what data objects. Basically it determines beforehands for all users and for all objects what it’s actors are and stores this in tables. During runtime it knows your user so can quickly read your actors and then read all objects which have the same actor. If a new object is created after the activation it automatically in the background determines the actors and updates the corresponding tables. Really nice!
Technical view
And now the interesting technical stuff: the place where you can customize all of the above is in the IMG under CRM\Basic Functions\ACE. Most things there speak for itself and the documentation is reasonably well.
If you create a new ACE right you have to implement a new class (copy from an existing class in the range CL_CRM_ACERULE*). The class contains 5 methods:
1. GET_ACTORS_FROM_USER: this method receives the userid in the field im_usr_name and determines the actors for this user which should be put in table ex_actor_id_table. Code samples will be below in this blog.
2. GET_OBJECTS_BY_FILTER: this method determines which objects to take into account and puts their GUIDs in table ex_object_guid_table
3. CHECK_OBJECTS_BY_FILTER: this method receives the table from the GET_OBJECTS_BY_FILTER method and here you can add additional filtering
4. GET_ACTORS_FROM_OBJECTS: this method receives the internal table of method CHECK_OBJECTS_BY_FILTER and for all these object GUIDS it determines the actors at once. It puts these in the itab et_actor_ids.
5. GET_ACTORS_FROM_OBJECT: this method is called when there is a new object created after the activation; e.g. when you create a new prospect this method calculates its actors. It gets as input the object GUID in field im_object_guid and gives its actors in table ex_actor_id_table
So methods 1-4 are used during the activation of ACE and the last one is used when new objects are created. At the end of this blog you will find some code samples.
The relevant tables involved are the following (where XX can be BP for business partners, OO for ‘one order’ objects which can be activities, orders, opportunities and leads, and PR for products; these are the three objects for which SAP delivers tables)
1. CRM_ACE_XX_GRP: in this table all possible actors are stored (e.g. all employee numbers or all sales areas) with their ACE_GROUP_ID, which is the GUID linked to this actor.
2. CRM_ACE_XX_UCT: in this table all users with all their ACE_GROUP_Ids (=all their actors) are stored
3. CRM_ACE_XX_ACL: here all object Ids with their actors (in the form of the ACE_GROUP_Ids) are stored.
From these tables you can easily see how ACE internally works: it knows your users, then reads in the user context table (UCT) your acegroup Ids, and then in the access control list table (ACL) it reads directly all objects you are allowed to see. It works as easy as that.
Performance
Code samples of the methods
GET_OBJECTS_BY_FILTER
CHECK_OBJECTS_BY_FILTER
This method doesn’t do any additional filtering; it just moves the content of one itab to another.
method IF_CRM_ACE_OBJECTS_BY_FILTER~CHECK_OBJECTS_BY_FILTER .
data: ls_object_guid type crms_ace_object_guid,
lt_partnerroles type table of BAPIBUS1006_ROLES,
lt_return type table of BAPIRET2.
loop at im_object_guid_table into ls_object_guid.
CALL FUNCTION 'BUPA_ROLES_GET'
EXPORTING
IV_PARTNER =
IV_PARTNER_GUID = ls_object_guid-object_guid
TABLES
ET_PARTNERROLES = lt_partnerroles
ET_RETURN = lt_return.
*
**check if the partner has the role 'Consumer'.
If lt_return is initial.
read table lt_partnerroles with key partnerrole = 'BUP003'
transporting no fields.
if sy-subrc eq 0.
append ls_object_guid to ex_object_guid_table.
endif.
endif.
endloop.
endmethod.
GET_ACTORS_FROM_USER
MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO
WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.
ENDIF.
*- Get the partner number as actor
SELECT SINGLE partner FROM but000 into l_bp_nr where partner_guid = l_iv_partner_guid.
*- ex_actor_id_table-IM_USR_NAME = im_usr_name.
shift l_bp_nr left deleting leading '0'.
MOVE l_bp_nr TO ls_actor_id-actor_id.
APPEND ls_actor_id TO ex_actor_id_table.
SORT ex_actor_id_table.
DELETE ADJACENT DUPLICATES FROM ex_actor_id_table.
ENDMETHOD.
TYPE crms_ace_object_guid.
CONSTANTS : abap_false TYPE c VALUE ' '.
in advantage of the multi function module the query for the
channel partner must only called one time
LOOP AT it_object_guids ASSIGNING -object_guid TO ls_actor_ids-object_guid.
get the ref kind.
CALL FUNCTION 'CRM_ORDER_GET_OBJECT_TYPE'
EXPORTING
iv_ref_guid = lv_order_guid
IMPORTING
ev_ref_kind = lv_ref_kind.
MOVE lv_order_guid TO lv_bapi_order_guid.
APPEND lv_bapi_order_guid TO lt_bapi_order_guid.
*- Get the activity details
CALL FUNCTION 'BAPI_BUSPROCESSND_GETDETAILMUL'
TABLES
guid = lt_bapi_order_guid
header = lt_header_dis
partner = lt_partner_dis
status = lt_status_dis
return = lt_ret2.
*- In table lt_partner_dis are the partners
LOOP AT lt_partner_dis INTO ls_partner_dis.
CLEAR ls_object_actors.
l_bp_no = ls_partner_dis-partner_no.
*- Put leading zeros in front of BP nr
CLEAR l_string.
l_string = STRLEN( l_bp_no ).
WHILE l_string NE 10.
CONCATENATE '0' l_bp_no INTO l_bp_no.
l_string = STRLEN( l_bp_no ).
ENDWHILE.
*- Check if the relation has a linked user; if not, don't store it
CLEAR l_username.
SELECT SINGLE partner_guid INTO l_partner_guid2
FROM but000 WHERE partner = l_bp_no.
CALL FUNCTION 'BP_CENTRALPERSON_GET'
EXPORTING
IV_PERSON_ID =
iv_bu_partner_guid = l_partner_guid2
IV_EMPLOYEE_ID =
IV_USERNAME =
IMPORTING
EV_PERSON_ID =
EV_BU_PARTNER_GUID =
ev_username = l_username
ET_EMPLOYEE_ID =
EV_NAME =
EXCEPTIONS
no_central_person = 1
no_business_partner = 2
no_id = 3
OTHERS = 4
.
IF sy-subrc <> 0.
CHECK 1 = 2.
ENDIF.
*- If no linked user, don't bother saving it
CHECK NOT l_username IS INITIAL.
ls_object_actors-object_guid = lv_order_guid.
CLEAR l_string.
l_string = STRLEN( l_bp_no ).
WHILE l_string NE 10.
CONCATENATE '0' l_bp_no INTO l_bp_no.
l_string = STRLEN( l_bp_no ).
ENDWHILE.
APPEND l_bp_no TO lt_actors_for_object.
*- If the partner is the activity partner, save this for the next processing block
IF ls_partner_dis-ref_partner_fct = '00000009'.
l_activity_partner = ls_partner_dis-partner_no.
ENDIF.
ENDLOOP.
IF NOT l_activity_partner IS INITIAL.
*- Also append the partners of the business partner of the activity
*- Select all relationships of this BP with a BP with a userid linked
SELECT partner2 INTO l_partner2
FROM but050 WHERE partner1 = l_activity_partner.
*- Check if the relation has a linked user
CLEAR l_username.
SELECT SINGLE partner_guid INTO l_partner_guid2
FROM but000 WHERE partner = l_partner2.
CALL FUNCTION 'BP_CENTRALPERSON_GET'
EXPORTING
IV_PERSON_ID =
iv_bu_partner_guid = l_partner_guid2
IV_EMPLOYEE_ID =
IV_USERNAME =
IMPORTING
EV_PERSON_ID =
EV_BU_PARTNER_GUID =
ev_username = l_username
ET_EMPLOYEE_ID =
EV_NAME =
EXCEPTIONS
no_central_person = 1
no_business_partner = 2
no_id = 3
OTHERS = 4
.
IF sy-subrc TYPE crm_ace_object_guid.
CONSTANTS : abap_false TYPE c VALUE ' '.
in advantage of the multi function module the query for the
channel partner must only called one time
assign im_object_guid to TO ls_actor_ids-object_guid.
get the ref kind.
CALL FUNCTION 'CRM_ORDER_GET_OBJECT_TYPE'
EXPORTING
iv_ref_guid = lv_order_guid
IMPORTING
ev_ref_kind = lv_ref_kind.
MOVE lv_order_guid TO lv_bapi_order_guid.
APPEND lv_bapi_order_guid TO lt_bapi_order_guid.
CALL FUNCTION 'BAPI_BUSPROCESSND_GETDETAILMUL'
TABLES
guid = lt_bapi_order_guid
header = lt_header_dis
partner = lt_partner_dis
status = lt_status_dis
return = lt_ret2.
*- In table lt_partner_dis are the partners
LOOP AT lt_partner_dis INTO ls_partner_dis.
l_bp_no = ls_partner_dis-partner_no.
ls_object_actors-object_guid = lv_order_guid.
append l_bp_no TO lt_actors_for_object.
ls_object_actors-actors = lt_actors_for_object.
ENDLOOP.
ex_actor_id_table = lt_actors_for_object.
endmethod.