Approbation by Email and Delegation in GRC 10
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
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
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.
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
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
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.
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
Hi Abhishek,
Is the auto mail triggering if you reject the request from NWBC?
Best Regards,
Mahip Singh Saluja.
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