Skip to Content

There are multiple issues related to this solution and in fact SAP has released a knowledge article to the topic that it is not allowed citing security reasons – SAP KBA: 1622881 – Approve by E-mail and Reject by E-mail functionality but there are certainly workarounds available.

The security issues, mainly, are:

• Validating correct approver and delegate approvers

• Emails could be sent with From option in mails making it even more difficult to validate

However, I did try to implement the process and succeeded in doing so with few (not recommended) workarounds.

My main motivation came from this link where a similar solution is suggested but for SAP Workflow:

http://www.****************/Tutorials/Workflow/offline/Index.htm

The BASIS configurations remain the same as given in the above link: The steps are as follows:

1) Create Offline User in SAP (It could be a new user if the approver will forward the mail to approve or reject requests, in case of reply back it has to be      WF-BATCH)

2) Configure the SAP-Connect node via SICF Transaction

3) Configure and activate the SMTP Service via SMICM transaction

4) Configure and set the Inbound E-Mail Exit Configuration

Even the next few steps remain the same, only the actual approval process has to be changed. In the 4th step, we need to provide a class name to process emails. In this example, I named the class as: Z_PROCESS_INBOUND_WORKFLOW. Add Interface to the class: IF_INBOUND_EXIT_BCS. You will see 2 methods added from the interface.

Add the code in the methods:

Z_PROCESS_INBOUND_WORKFLOW->IF_INBOUND_EXIT_BCS~CREATE_INSTANCE

Here, we need to create an instance of the class to be used for further processing.
Sample Code below:

  DATA: lo_ref TYPE REF TO z_process_inbound_workflow.

* check if the instance is initial

  IF lo_ref IS INITIAL.

    CREATE OBJECT lo_ref.

  ENDIF.

* Return the Instance

  ro_ref = lo_ref.

Z_PROCESS_INBOUND_WORKFLOW->IF_INBOUND_EXIT_BCS~PROCESS_INBOUND

This method will be called automatically for the processing the message when it is received by the SAP system.

Sample Code Below:

* Declare for Inbound E-Mail processing
  DATA: lo_document     TYPE REF TO if_document_bcs,
        l_mail_attr     TYPE bcss_dbpa,
        l_mail_content  TYPE bcss_dbpc,
        lv_reqno        TYPE grac_reqno,
        lv_approve_reject TYPE char1,
        lt_cont_text    TYPE soli_tab,
        ls_cont_text    TYPE soli,
        lo_reply        TYPE REF TO cl_send_request_bcs,
        sender          TYPE REF TO if_sender_bcs,
        sender_addr     TYPE string,
        lv_email        TYPE ad_smtpadr,
        send_request    TYPE REF TO cl_bcs,
        lo_approval     TYPE REF TO z_grac_approbation_by_email.
*——————————————————————–*
*- Get a pointer to the reply email object -*
*——————————————————————–*
  TRY.
      lo_reply = io_sreq->reply( ).
    CATCH cx_send_req_bcs.
  ENDTRY.
**** Check to make sure this is from an approved Sender
  sender = io_sreq->get_sender( ).
  sender_addr =  sender->address_string( ).
  lv_email = sender_addr.
  TRANSLATE sender_addr TO UPPER CASE.
**** Only reply if this message came from within our mail system or domain
**** SPAMMERS Beware, your e-mails will not be processed!!!
IF sender_addr CS ‘@xxx.COM‘.
**** send reply and inbound processing
*——————————————————————–*
*- Get email subject -*
*——————————————————————–*
  TRY.
      lo_document = io_sreq->get_document( ).
      l_mail_attr = lo_document->get_body_part_attributes( ‘1’ ).
*Get the request number from the desired position of the subject
      lv_reqno = l_mail_attr-subject+12(10).
    CATCH cx_document_bcs.
  ENDTRY.
*——————————————————————–*
*- Get mail body-*
*——————————————————————–*
  TRY.
      l_mail_content = lo_document->get_body_part_content( ‘1’ ).
      lt_cont_text = l_mail_content-cont_text.
      DELETE lt_cont_text WHERE line IS INITIAL.
      READ TABLE lt_cont_text INTO ls_cont_text INDEX 1.
      IF sy-subrc EQ 0.
        TRANSLATE ls_cont_text-line TO UPPER CASE.
        IF ls_cont_text-line+0(7) = ‘APPROVE’.
          lv_approve_reject = ‘A’.
        ELSEIF ls_cont_text-line+0(6) = ‘REJECT’.
          lv_approve_reject = ‘R’.
        ENDIF.
      ENDIF.
    CATCH cx_document_bcs.
  ENDTRY.

  IF lv_approve_reject IS NOT INITIAL
    AND lv_reqno IS NOT INITIAL
    AND lv_email IS NOT INITIAL.

    CREATE OBJECT lo_approval
      EXPORTING
        i_reqno          = lv_reqno
        i_email          = lv_email
        i_approve_reject = lv_approve_reject.

    CALL METHOD lo_approval->process_request .

  ENDIF.

ENDIF.

Now, I have created another class to validate approvers from their email addresses, process emails in case of any errors and finally start the approval process which is being called from above class method – Z_GRAC_APPROBATION_BY_EMAIL

First save the values in attributes of this class in the CONSTRUCTOR method.

Create a method PROCESS_REQUEST to do the processing.

In this method, the steps followed are:

  • First get the SAP user ID for the email ID of the sender
  • Validate by the SAP user ID, if the sender is actually the approver from checking tables GRFNMWRTINSTWI, GRACREQUSER
  • If not, check if the sender is a delegate approver. You can user Function Module SAP_WAPI_SUBSTITUTIONS_GET
  • If validated, create a background job using FM JOB_OPEN

The reason we need a background job is because the SY-UNAME in the system will be either WF-BATCH or a new user created by BASIS in the 1st step and that user is not the actual approver. So we create a background job and then change the user ID with the actual approver.

So, after the JOB_OPEN is called:

  • Call FM BP_JOB_READ
  • Change the user ID in Job Head and call FM BP_JOB_MODIFY
  • We will have to create a new Report Program to approve or reject the request (Z_REP_APPROBATION_BY_EMAIL) and SUBMIT the program
  • Call FM JOB_CLOSE

Now, the main logic is in the report program Z_REP_APPROBATION_BY_EMAIL.

I added 3 selection screen parameters to accept Request Number, BNAME(SAP User ID) of the approver and a field to identify Approve or Reject (A or R)

  • First step is to fetch Request ID from Request Number from table GRACREQ. Concatenate ‘ACCREQ/’ and the Request ID togeather.
  • Next is to fetch Work Item IDs for the Request Number from the table GRFNMWRTINSTWI
  • After collecting data, we will call standard methods that GRC system uses to do the processing, Code Snippets are shown below:

  go_session  =  cl_grfn_api_session=>open_daily( ).

  TRY .

      go_api ?= go_session->get( gv_reqid ).

      gv_bname = p_bname.

      CALL METHOD go_api->if_grac_api_access_request~retrieve
        EXPORTING
          iv_editable      = abap_true
          it_wi_id         = gt_wi_id
          iv_admin_mode    = lv_bool
          iv_approver_user = gv_bname.

      IF p_aprj EQ ‘A’.

        ls_user_range-sign = ‘I’.
        ls_user_range-option = ‘EQ’.
        ls_user_range-low = gv_bname.
        APPEND ls_user_range TO lt_user_range.

        lv_user = gv_bname.

        CALL METHOD cl_grac_user_rep=>retrieve_realtime_user
          EXPORTING
            iv_user          = lv_user
          IMPORTING
            es_real_userinfo = ls_real_userinfo.

        CALL METHOD cl_grac_user_rep=>retrieve_user_systems
          EXPORTING
            it_user      = lt_user_range
*           it_user_name =
*           iv_max_rows  = 1000
          RECEIVING
            rt_user      = lt_user.

        ls_val-val1 = ls_real_userinfo-department.
        ls_val-val2 = ls_real_userinfo-location.
        ls_val-val3 = ls_real_userinfo-company.
        ls_val-val4 = ls_real_userinfo-costcenter.
        ls_val1-val1 = ls_real_userinfo-userid.
        ls_val1-val2 = ls_real_userinfo-user_group.
        ls_val1-val3 = ls_real_userinfo-orgunit.

        IF lt_user IS NOT INITIAL.

          LOOP AT lt_user INTO ls_user.

            ls_val1-val4 = ls_user-connector.

            IF cl_grac_auth_engine=>authority_check(
                  iv_auth_obj   =  graca_c_emp-auth_obj
                  iv_field1     =  graca_c_actvt-actvt
                  iv_value1     =  graca_c_actvt-change
                  iv_field2     = graca_c_emp-dept
                  iv_value2     = ls_val-val1
                  iv_field3     =  graca_c_emp-location
                  iv_value3     =  ls_val-val2
                  iv_field4     =  graca_c_emp-company
                  iv_value4     =  ls_val-val3
                  iv_field5     =  graca_c_emp-cost_centre
                  iv_value5     =  ls_val-val4
              ) EQ abap_true AND
               cl_grac_auth_engine=>authority_check(
                     iv_auth_obj   =  graca_c_user-auth_obj
                     iv_field1     =  graca_c_actvt-actvt
                     iv_value1     =  graca_c_actvt-change
                     iv_field2     = graca_c_user-userid
                     iv_value2     =  ls_val1-val1
                     iv_field3     =  graca_c_user-usergroup
                     iv_value3     =  ls_val1-val2
                     iv_field4     =  graca_c_user-org_unit
                     iv_value4     =  ls_val1-val3
                     iv_field5     = graca_c_user-connector
                     iv_value5     = ls_val1-val4
                 ) EQ abap_true.
              lv_flg = ‘X’.
              EXIT.
            ENDIF.
          ENDLOOP.
        ELSE.
          ls_val1-val4 = ls_user-connector.
          IF cl_grac_auth_engine=>authority_check(
                iv_auth_obj   =  graca_c_emp-auth_obj
                iv_field1     =  graca_c_actvt-actvt
                iv_value1     =  graca_c_actvt-create
                iv_field2     = graca_c_emp-dept
                iv_value2     = ls_val-val1
                iv_field3     =  graca_c_emp-location
                iv_value3     =  ls_val-val2
                iv_field4     =  graca_c_emp-company
                iv_value4     =  ls_val-val3
                iv_field5     =  graca_c_emp-cost_centre
                iv_value5     =  ls_val-val4
            ) EQ abap_true AND
             cl_grac_auth_engine=>authority_check(
                   iv_auth_obj   =  graca_c_user-auth_obj
                   iv_field1     =  graca_c_actvt-actvt
                   iv_value1     =  graca_c_actvt-create
                   iv_field2     = graca_c_user-userid
                   iv_value2     =  ls_val1-val1
                   iv_field3     =  graca_c_user-usergroup
                   iv_value3     =  ls_val1-val2
                   iv_field4     =  graca_c_user-org_unit
                   iv_value4     =  ls_val1-val3
                   iv_field5     = graca_c_user-connector
                   iv_value5     = ls_val1-val4
               ) EQ abap_true.
            lv_flg = ‘X’.
          ENDIF.
        ENDIF.

        IF lv_flg = ‘X’.

          PERFORM f_fill_approving_details CHANGING ls_req_data
                                                    lt_item
                                                    lt_requser
                                                    lt_reqsys.

          lo_api ?= go_session->get( gv_reqid ).

          CALL METHOD lo_api->if_grac_api_access_request~update
            EXPORTING
              is_request_data = ls_req_data
              it_requser      = lt_requser
              it_reqlineitm   = lt_item
              it_reqsys       = lt_reqsys.

          CALL METHOD go_session->save.

        ENDIF.
      ELSEIF p_aprj EQ ‘R’.

        CALL METHOD go_api->if_grac_api_access_request~reject .

        CALL METHOD go_session->save.

      ENDIF.

    CATCH cx_grfn_exception INTO go_grfn_exp.
  ENDTRY.

*&———————————————————————*
*&      Form  f_fill_approving_details
*&———————————————————————*
*       text
*———————————————————————-*
*      –>LS_REQ_DATA  text
*———————————————————————-*
FORM f_fill_approving_details CHANGING   ps_req_data TYPE grac_s_api_req_data
                                        pt_item     TYPE grac_t_api_reqlineitem
                                        pt_requser  TYPE grac_t_api_user_info
                                        pt_reqsys   TYPE grac_t_api_reqsys.

  TYPES: BEGIN OF ty_gracreq,
          req_id          TYPE grfn_guid,
          req_created     TYPE grac_req_created,
          duedate         TYPE grac_duedate,
          reqtype         TYPE grac_reqtype,
          funcarea        TYPE grac_funarea,
          msmp_process_id TYPE grfn_mw_process_id,
        END OF ty_gracreq,

        BEGIN OF ty_gracitem,
          itemnum         TYPE grac_seq,
          connector       TYPE grac_reqsystem,
          prov_item_id    TYPE grfn_guid,
          prov_item_type  TYPE grac_prov_item_type,
          prov_action     TYPE grac_actiontype,
          prov_item_name  TYPE grac_prov_item_name,
          approval_status TYPE grac_approval_status,
          valid_from      TYPE grac_valid_from,
          valid_to        TYPE grac_valid_to,
          prov_type       TYPE grac_prov_type,
        END OF ty_gracitem,

        BEGIN OF ty_systems,
          systems TYPE grfn_connectorid,
        END OF ty_systems.

  DATA: lv_reqid TYPE grfn_guid,
        ls_gracreq TYPE ty_gracreq,
        lt_gracitem TYPE STANDARD TABLE OF ty_gracitem,
        ls_gracitem TYPE ty_gracitem,
        lt_gracuser TYPE STANDARD TABLE OF gracrequser,
        ls_gracuser TYPE gracrequser,
        ls_reqsys   TYPE grac_s_api_reqsys,
        lt_systems  TYPE STANDARD TABLE OF ty_systems,
        ls_systems  TYPE ty_systems,
        ls_requser  TYPE grac_s_api_user_info,
        ls_item     TYPE grac_s_api_reqlineitem.

  lv_reqid = gv_reqid+7.

  SELECT SINGLE  req_id
                 req_created
                 duedate
                 reqtype
                 funcarea
                 msmp_process_id
    FROM gracreq
    INTO ls_gracreq
    WHERE req_id = lv_reqid.
  IF sy-subrc EQ 0.
    ps_req_data-req_id = ls_gracreq-req_id.
    ps_req_data-req_created = ls_gracreq-req_created.
    ps_req_data-req_approved = ls_gracreq-duedate.
    ps_req_data-reqtype = ls_gracreq-reqtype.
    ps_req_data-msmp_process_id = ls_gracreq-msmp_process_id.
    ps_req_data-funcarea = ls_gracreq-funcarea.

    SELECT itemnum
           connector
           prov_item_id
           prov_item_type
           prov_action
           prov_item_name
           approval_status
           valid_from
           valid_to
           prov_type
      FROM gracreqprovitem
      INTO TABLE lt_gracitem
      WHERE req_id = lv_reqid.

    IF sy-subrc EQ 0.
      LOOP AT lt_gracitem INTO ls_gracitem.
        ls_item-itemnum   = ls_gracitem-itemnum.
        ls_item-item_name   = ls_gracitem-prov_item_name.
        ls_item-connector   = ls_gracitem-connector.
        ls_item-prov_item_id   = ls_gracitem-prov_item_id.
        ls_item-prov_item_type   = ls_gracitem-prov_item_type.
        ls_item-prov_action   = ls_gracitem-prov_action.
        ls_item-approval_status   = ‘AP’.
        ls_item-valid_from   = ls_gracitem-valid_from.
        ls_item-valid_to   = ls_gracitem-valid_to.
        ls_item-prov_type   = ls_gracitem-prov_type.

        APPEND ls_item TO pt_item.
      ENDLOOP.
    ENDIF.

    SELECT * FROM gracrequser
      INTO TABLE lt_gracuser
      WHERE req_id = lv_reqid.

    IF sy-subrc EQ 0.
      LOOP AT lt_gracuser INTO ls_gracuser.
        ls_requser-userid = ls_gracuser-userid.
        ls_requser-provuser = ls_gracuser-provuser.
        ls_requser-snc_name = ls_gracuser-snc_name.
        ls_requser-unsec_snc = ls_gracuser-unsec_snc.
        ls_requser-accno = ls_gracuser-accno.
        ls_requser-empposition = ls_gracuser-empposition.
        ls_requser-empjob = ls_gracuser-empjob.
        ls_requser-personnelno = ls_gracuser-personnelno.
        ls_requser-personnelarea = ls_gracuser-personnelarea.
        ls_requser-email = ls_gracuser-email.
        ls_requser-emptype = ls_gracuser-emptype.
        ls_requser-logon_langu = ls_gracuser-logon_langu.
        ls_requser-dec_notation = ls_gracuser-dec_notation.
        ls_requser-date_format = ls_gracuser-date_format.
        ls_requser-time_zone = ls_gracuser-time_zone.
        ls_requser-manager = ls_gracuser-manager.
        APPEND ls_requser TO pt_requser.

      ENDLOOP.
    ENDIF.

    SELECT systems
      FROM gracrequsersys
      INTO TABLE lt_systems
      WHERE req_id = lv_reqid.

    IF sy-subrc EQ 0.
      LOOP AT lt_systems INTO ls_systems.
        ls_reqsys-systems = ls_systems-systems.
        APPEND ls_reqsys TO pt_reqsys.
      ENDLOOP.
    ENDIF.

  ENDIF.

ENDFORM.                    “f_fill_approving_details

To report this post you need to login first.

8 Comments

You must be Logged on to comment or reply to a post.

  1. V J

    Hey,

    Thanks for the wonderful post. Wanted some insights on few things:

    1. What is exactly the deal with lv_flag i see some checks happening but what is the significanc of this variable

    2. I can see you are passing P_bname so in that case why do you need to call a batch job to change user ids?

    Thanks in advance

    (0) 
    1. Mahip Saluja Post author

      Hi,

      Regarding your point 1: it is very important to do authority check if the user has the authorizations to approve the request. The flag is set only when the authentications are done. The chunk of code to approve and reject requests is pulled out from the standard, so you will find the same when you debug.

      Regarding your point 2: Changing user IDs is very important here. See, when a request is created, the mails are sent by the background workflow user: WF-BATCH. The approver receives the email and replies back to the same email. So, that means WF-BATCH will have to process the approve or reject request. But does the WF-BATCH has the authorizations to actually approve or reject? NO. The user ID of the approvers has the authorizations. Since WF-BATCH receives back the reply from approve and it has to start the process of approving or rejecting, it has to do it on behalf of the approver user IDs. Starting a batch job is the only way to change the user ID to approve or reject.

      I hope this is clear to you. Please feel free to ask any more queries you have.

      Best Regards,

      Mahip Singh Saluja.

      (0) 
      1. V J

        hi Mahip,

        Thanks a lot for the prompt response

        I am trying the solution but the code for GRC approval is not working and giving dumps particularly at CALL METHOD go_session->save.

        Just wanted to check how did you found out that these methods can be used to approve GRC. Because when we tried to debug while approving, we are getting webdynpro based methods that cannot be used directly in a code.

        Thanks and Regards,

        V J

        (0) 
  2. Pranshu Kukreti

    Hi Mahip/ VJ,

    I know this is an old post, but I am implementing the same approval functionality in my custom program by calling standard methods. It is not giving me any error but request status is not getting changed. I am also passing details to be updated in audit log through update method, but its not getting updated in the request audit log tab.  

       lo_session             = cl_grfn_api_session=>open_daily( ).

        lo_access_request_api ?= lo_session->get(

         iv_object_id = l_reqid  ).

        lo_access_request_api->if_grac_api_access_request~update(

        is_request_data       = ls_req_data

        it_requser            = ls_request_data-requser

        it_reqlineitm         = ls_request_data-reqlineitm

        it_reqsys             = lt_reqsys

        it_audit_trail        = lt_audit_log ).

        lo_session->save( ).

    Could anyone let me know if we can achieve the approval functionality by calling these standard methods in our custom code?

    Thanks.

    PK

    (0) 
    1. Mahip Saluja Post author

      Hi Pranshu,

      I have not tried passing the audit table, but you can call exactly the same methods that SAP is calling. The above sample code is actually standard methods I found during debug.

      Best Regards,

      Mahip SIngh Saluja.

      (0) 
  3. Abhishek Bhowmik

    Hi Mahi,

    I have utilize the standard code for reject purpose  what you have wrote in Z_REP_APPROBATION_BY_EMAIL.Till reject everything went good and it is updating the table as well but after rejection one auto mail should trigger like standard program but in my case that is not triggering because of one error :

    Unable to deliver event ‘REJECT’ of object ‘CL_GRAC_ACCESS_REQUEST_WF error because of that mail is not triggered

    Do you have any idea why this error occurred..

    Regards,

    Abhishek Bhowmik

    (0) 
      1. Abhishek Bhowmik

        Hi Mahip,

        Thanx for your reply ya it is auto mail triggering and auto reject purpose of the request which is pending more than three days with approvers. I have took the idea of your post it is working fine and the mail is also triggering we do not need to create separate class to triggering the mail . thank you very much for the post and reply also.

        Regards,

        Abhishek Bhowmik

        (0) 

Leave a Reply