Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

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


Large and complex (international) CRM installations all face the same problem: how do we show the users only the data that they need to see? We don’t mean authorizations related to functionality, but related to business content. Imagine you run a big business and have a million customers worldwide. Then a sales rep responsible for a group of customers in Belgium should not see any customers from Asia in his search results. Or a sales rep with responsibility for a certain branche should not be bothered with customers of other branches. Furthermore, if the structure of the sales organisation changes, you don’t want to end up changing all kind of authorization profiles.

To solve these issues, SAP came up in CRM for the PCUI with a pretty nice solution: CRM-ACE. This stands for Access Control Engine and is a framework to calculate user dependent access rights on object level. It originates from Channel Management but works in all PCUI functionalities. Unfortunately it doesn’t work in other environments like IC Webclient or via the SAP GUI (but I created a development request for this….).

When I started looking into ACE a while ago there was very little documentation and information on this topic. There is some information in the IMG and a very basic SAP tutor file but posts in SDN Forums asking for info got almost no reply. As usual I found out how it works myself by debugging and tracing….and therefore I thought it would be a good idea to share it with you.

Difference with ‘normal’ authorizations


What is the real difference with the ‘normal’authorization concept we are all familiar with? In this traditional concept you have to specify all values is the role; e.g. the sales organisation which the user is allowed to see data from. If you have 30 sales organisations you need 30 roles. These are static autorisations. In ACE you can specify in one role that all users who have this role can see customers for the sales area to which they are linked to. So with 30 sales organisations you only need one role. If a sales rep moves from one organisation to another you don’t even need to change his authorizations. These are dynamic 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


Does ACE boost the performance of your system? Seen the logic of the tables above, it should. However unfortunately the answer is no, or maybe just a very little. When you search for business partners, in the background it still retrieves all business partners, and then at a later stage it limits the result list according to the ACE rules. So from that point in time the result list is smaller, but the first search already spoiled the response time. But maybe in future releases SAP will improve the logic of searching in the PCUI so it first reads the ACE tables, and then the other tables. In most SAP systems this will be quicker, depending on how the employees are mapped to the business partners.

Code samples of the methods


And last but not least…..the code samples. In this example I implemented class ZCL_CRM_ACERULE_RELATION which does the following: it allows users only to see business partners for which they have a relation with. For example, they are defined as employee responsible, or as account manager.

As I’m not a real programmer (actually I’m a functional consultant however somehow I often end up programming too) there might be some room for improvement. I’m open for your suggestions!

GET_OBJECTS_BY_FILTER


This method retrieves all business partners which are organisations and all contact persons which are linked to a BP (because if you see a BP, you also want to see it’s CPs!)

METHOD IF_CRM_ACE_OBJECTS_BY_FILTER~GET_OBJECTS_BY_FILTER .

DATA: ls_ace_object_key TYPE crms_ace_object_guid,
ls_bsp_seareq_account TYPE crmt_bsp_seareq_account,
lt_partner_key TYPE bup_partner_guid_t,
ls_partner_key TYPE bupa_partner_guid,
ls_control TYPE crmt_bsp_search_control,
lt_crmm_but_lnk0011 TYPE TABLE OF crmm_but_lnk0011,
ls_crmm_but_lnk0011 TYPE crmm_but_lnk0011,
lt_bu_partner_guid TYPE bu_partner_guid,
lt_crmt_bsp_sales_area_bp_t TYPE crmt_bsp_sales_area_bp_t,
ls_crmt_bsp_sales_area_bp_t TYPE crmt_bsp_sales_area_bp.


DATA: lv_partner type BU_PARTNER_GUID,
lv_ace_guid TYPE crms_ace_object_guid.

*- Select all organisations
SELECT partner_guid
INTO lv_partner
FROM but000 where type = '2'.

MOVE lv_partner TO lv_ace_guid-object_guid.
APPEND lv_ace_guid TO ex_object_guid_table.
ENDSELECT.

*- Get all CPs which are linked to a BP

SELECT b~partner_guid
INTO lv_partner
FROM but051 as a inner join but000 as b on b~partner = a~partner2.

MOVE lv_partner TO lv_ace_guid-object_guid.
APPEND lv_ace_guid TO ex_object_guid_table.

ENDSELECT.


ENDMETHOD.


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


This method retrieves BPs to which the user is linked.

METHOD if_crm_ace_actors_from_user~get_actors_from_user .


*- This method gets the users and determines the BP which is linked as relation to an activity or business partner

DATA: l_iv_partner_guid TYPE bu_partner_guid,
l_bp_nr TYPE bu_partner,
ls_actor_id TYPE crms_ace_actor_id.

CLEAR: l_iv_partner_guid, ex_actor_id_table.
REFRESH: ex_actor_id_table.

*- First determine the BP of the user
CALL FUNCTION 'BP_CENTRALPERSON_GET'
EXPORTING
iv_username = im_usr_name
IMPORTING
ev_bu_partner_guid = l_iv_partner_guid
EXCEPTIONS
no_central_person = 1
no_business_partner = 2
no_id = 3
OTHERS = 4
.
IF sy-subrc <> 0.

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

47 Comments