[{ALLOW view All}]
[{ALLOW edit Markus}]

[{TableOfContents }]

!!Connecting SAP SuccessFactors Using an OAuth 2.0 Client (OA2CS)

Follow \\
SIMG > ABAP Platform > Application Server > Basis Services > Communication Interfaces \\
> OAuth 2.0 Client Implementation for SAP SuccessFactors Integration

The default implementation allows only a connection to ONE SuccessFactors instance.\\
Then you can use the existing objects.

This ones decribes how to create custom objects for multiple instances.

!certificates / communication
1. Configure Proxy Settings\\
x.SICF > F8 > Menu > Client > Proxy Settings \\
*   > HTTPS Protocol = <host>:<port> SKIPPED !!!\\
*   > Global Settings > Set Active SKIPPED !!!
   
! How to connect to multiple SF instances
You can use below default configuration if you want to connect your SAP system to ONE SF instance only, otherwise you need to copy/adjust everything

When you create a configuration with x.OA2C_CONFIG you need to enable SAML2 with the "SAML2 2.0 Disabled" button.\\
You can also re-do that if you use button "<TRASHBIN> SAML 2.0 Settings" and and re-enable.\\
__First time and each time you re-enable SAML 2.0 a new certificate in STRUST at "SSF OAuth2 Client Identity Provider -Signature" is created.__\\
You cannot have multiple STRUST certificates for different SF instances (one for each instance), because SSF application OA2CS is hardcoded at class CL_OA2C_SAML20_METADATA->CHECK_SSF_APPLICATION_OA2C().\\
__The trick__ is to use the one and only STRUST certificate in all SF instances at "Manage OAuth2 Client Applications".
When you create a second OA2C_CONFIG configuration for the second instance, no new certificate at STRUST "SSF OAuth2 Client Identity Provider -Signature" is created!\\
This happens only if you re-enable SAML 2.0.

!OAuth configuration
* OAuth profile type, SM30 > OA2C_TYPES (used as filter in the BAdI)
* OAuth profile, default "SUCCESSFACTORS", use SE80 > Create > Others > OAuth 2.0 Client Profile (t.OA2C_PROFILES, you can search in SE80 with "_QS Development Object")
* Provider type, default "SuccessFactors", x.CLB2_PTYPE (SM30 > CLB2V_PTYPE)
* Application ID, default "DEFAULT", x.CLB2_APPLI
* API Method, default GENERIC, SM30 > CLB2V_PTYPE_METH (copy default)
* API Method Version, default V1 and CL_CLB2_METHOD, SM30 > CLB2V_PTYPE_VERS (copy default)
* Server, default "SuccessFactors", SM34 > CLB2VC_PLATF_DEF to set server with url, provider, csfr + api version
* Collaboration: Communication Server Settings, SM30 > CLB2V_PLATF
* Application Server Assignment, default "DEFAULT", SM34 > CLB2VC_APPLI_PLATF  <= here we have the OAuth App ID and the companyId
* Server Communication, default "SuccessFactors", SM34 > CLB2VC_PLATF, make sure to fill also the "Authentication Methods" like USER

! Transaction OA2C_CONFIG
Stored at t.oa2c_client.\\
{{{
Client Secret: is mandatory, but does NOT matter here (use anything like xxx)
Token Endpoint: <sf_host>/oauth/token
enable Form Fields, Header Fields, Current user related, SAML 2.0 Bearer Assertion
SAML 2.0 Audience = www.successfactors.com
SAML 2.0 Recipient = token endpoint
User E-Mail for SAML 2.0 Name ID = 998 (see below) 
SSL Client PSE = choose Default or Anonym (where you put the SF general SSH certificate)
}}}

! SAML Subject Name Identifier (user) / Modification
This refers to OA2C_CONFIG at "User E-Mail for SAML 2.0 Name ID".\\
You can have the options specified at CL_OA2C_SAML20_ASSERTION->BUILD_NAME_ID().\\
By default the local SAP username is passed to SF, but SF expects a userId, so this will not work.\\
The only option which made sense to me is to use __998__ and specify the userId as Alias at the SAP user.\\
But you can have only the ALIAS only as upper case in SAP, so the SF userId must be uppercase too (or just numbers).\\
I modified the SAP code at CL_OA2C_SAML20_ASSERTION->BUILD_NAME_ID():
{{{
es_saml20_name_id-_value = to_lower( es_saml20_name_id-_value ).
}}}

! Create BAdIs, copy/adjust classes
Note: use always same case to avoid mixing up at all artifacts:
Profile, Type, Application ID, Service Provider Type, SSF ID, Server

{{{
CL_OA2C_SPECIFICS_DEFAULT
CL_OA2C_CONFIG_EXT_DEFAULT

                        (default)                 (custom)
Enh. Spot               OA2C_SPECIFICS            (not visible in OA2C_CONFIG) 
Enh. Impl.              SMI_OA2C_SPEC_SFSF        (not visible in OA2C_CONFIG)
BAdI Def                OA2C_SPECIFICS_BADI_DEF   
BAdi Impl.              SMI_OA2C_SPEC_SFSF_BIZX   => Z_SMI_OA2C_SPEC_SFSF_BIZX_CD*
Filter Value:		SUCCESSFACTORS            => <Your_New_Filter>
Implementation Class:	CL_SMI_OA2C_SPEC_SFSF	  => copied to ZCL_SMI_OA2C_SPEC_SFSF_CD
						  => replace all references from class CL_SMI_OA2C_CONFIG_SFSF to ZCL_SMI_OA2C_CONFIG_SFSF_CD
						  => edit method IF_OA2C_SPECIFICS~GET_CONFIG_EXTENSION (replace R_CONFIG_EXTENSION name)
*SE18 > es.OA2C_SPECIFICS
  > Right-Click > Create implementation > ei.Z_SMI_OA2C_SPEC_SFSF_CD
  > bi.Z_SMI_OA2C_SPEC_SFSF_BIZX_CD
  > assign class from above and add filter
}}}
{{{
Enh. Spot               OA2C_CONFIG_EXTENSION
Enh. Impl.              SMI_OA2C_CONFIG_SFSF
BAdI Def                OA2C_CONFIG_EXTENSION_BADI_DEF
BAdi Impl.              SMI_OA2C_CONFIG_SFSF_BIZX
Filter Value:		SUCCESSFACTORS
Implementation Class:	CL_SMI_OA2C_CONFIG_SFSF => copied to ZCL_SMI_OA2C_CONFIG_SFSF_CD
			=> update attributes GC_APPLICATION and GC_SMI_SP_SFSF
			
SE18 > es.OA2C_CONFIG_EXTENSION
  > Right-Click > Create implementation > ei.Z_SMI_OA2C_CONFIG_SFSF_CD
  > bi.Z_SMI_OA2C_CONFIG_SFSF_BIZX_CD
  > assign class from above and add filter	
}}}

! SuccessFactors "Manage OAuth2 Client Applications"
# button "Register Client Application"
# Application name = you can use any, but I recommend "SAP_<sysid>_<mandt>" of source SAP system
# Application URL = any, example http://SAP_<sysid>_<mandt>
# X.509 Certificate = the one created at STRUST at "SSF OAuth2 Client Identity Provider -Signature"
** export at STRUST
** double click on "SSF OAuth2 Client Identity Provider -Signature"
** double click on subject to get the certificate below
** button "Export certificate" > as Base64 > into any file
** open file and copy/paste certificate without header/footer
# Register
After that you get an "API key", which is the "OAuth 2.0 Client ID" at OA2C_CONFIG.\\
You also need to use this one at the OAuth configuration above. Actually you can create this one first, create SAP OAuth and come back here to update certificate afterwards.

!OAuth with RFC Connections
You can use RFC for OAuth. 
# create RFC of type G - "HTTP Connections to External Server"
# Host = SF server, Port = 443
# Path Prefix = keep EMPTY, if this is empty you can override it like below code
# Activate SSL, do not use a user
# button "OAuth Settings"
** Profile = your OAuth profile name
** Configuration = the OA2C_CONFIG configuration name
If everything is correct you can use button "Connection Test" and get a HTTP 200 response.\\
If not there is no proper response, you need to set __a session (!) breakpoint at cl_oa2c_client_protocol_utils=>get_tokens() to capture the response__ to know why.
{{{
REPORT ZMDW_EC_RFC.

  DATA:
    lo_client     TYPE REF TO if_http_client,
    lv_res_data_bin TYPE xstring,
    lv_res_data_str TYPE string,
    lv_req_data_bin TYPE xstring,
    lv_req_data_str TYPE string,
    lo_conv       TYPE REF TO cl_abap_conv_in_ce.

  cl_http_client=>create_by_destination(
    EXPORTING
      "destination              = 'SF_Instance1_OAuth'
      destination              = 'SF_Instance2_OAuth'
    IMPORTING
      client                   = lo_client
    EXCEPTIONS
      argument_not_found       = 1
      destination_not_found    = 2
      destination_no_authority = 3
      plugin_not_active        = 4
      internal_error           = 5
      OTHERS                   = 6
  ).

  lo_client->request->set_method( if_http_request=>co_request_method_get ).
 "New path ->
  lo_client->request->set_header_field( name  = '~request_uri'   value = '/odata/v2/FODivision?$filter=externalCode+eq+''204''' ).  " <=========

  lo_client->send( ).
  lo_client->receive( ).

  lv_res_data_bin = lo_client->response->get_data( ).

  lo_conv = cl_abap_conv_in_ce=>create( input = lv_res_data_bin ).
  lo_conv->read( IMPORTING data = lv_res_data_str ).

  lo_client->close( ).

  cl_demo_output=>display(
    EXPORTING
      data = lv_res_data_str
      name = 'Response'
  ).
}}}

!Modifications (OLD, NOT NECESSARY)
Whenever you choose to create SAML a new STRUST is created:
"SSF OAuth2 Client Identity Provider - Signature" 
{{{
" Create entry with r.ZMDW_TEST, the new applic need to be assigned to the type via SM30 > CLB2V_PTYPE
FORM create_sf_application.
  DATA: ls_app          TYPE ssfapplic,
        ls_appt         TYPE ssfapplict, 
        lv_old_app_name TYPE string,
        lv_new_app_name TYPE string.
 
  lv_old_app_name = 'ZOACQ'.   " erase former custom ones, set blank if none
  lv_new_app_name = ''.
 
  IF lv_old_app_name <> ''.
    DELETE FROM ssfapplic WHERE applic = lv_old_app_name.
    DELETE FROM ssfapplict WHERE applic = lv_old_app_name.
    WRITE: / |deleted { lv_old_app_name }, rc={ sy-subrc }|.
  ENDIF.
 
  IF lv_new_app_name <> ''.
    SELECT SINGLE * FROM ssfapplic INTO ls_app WHERE applic = 'OA2CS'.
    ls_app-applic = lv_new_app_name.
    MODIFY ssfapplic FROM  ls_app. 
    WRITE: / |modified { lv_new_app_name }, rc={ sy-subrc }|. 
 
    SELECT SINGLE * FROM ssfapplict INTO ls_appt WHERE sprsl = 'E' AND applic = 'OA2CS'.
    ls_appt-applic = lv_new_app_name.
    ls_appt-descript = |OAuth2 Client Identity Provider - { lv_new_app_name }|.
    MODIFY ssfapplict FROM  ls_appt.
    WRITE: / |modified { lv_new_app_name }, rc={ sy-subrc }|. 
  ENDIF.
 
ENDFORM.
}}}
Add SFF ID specific parameters with
* x.SSFA (t.SSFAPPLIC) or SM30 > VSSFARGS (x.SIMG > Multi Bank Connectivity Connector > Maintain SSF Application Parameters)
								

! Connection Test
* r.OA2C_GENERIC_ACCESS
* r.RCLB2_DEMO_GENERIC
{{{
Choose your Service Provider Type and Application ID
Request Method: HTTP Get (GET)
Manually Entered Endpoint
  Endpoint: /odata/v2/FODivision/
  Authentication Context: User Context (USER)
}}}
* with transaction OA2C_GRANT you can create/delete tokens (for your own user only)
* tokens are stored at table OA2C_TOKEN_ADM (and OA2C_TOKEN_SCOPE, not used here). You can delete records here to force re-authentication

! SF Token lifetime
With r.OA2C_GENERIC_ACCESS you can see we get a token which is valid 24h.
The token metadata are stored at table OA2C_TOKEN_ADM for the current user and it is reused.\\
In other words: You can skrew up configuration now for the next 24h but the connection still works.\\
To re-authenticate delete records at OA2C_TOKEN_ADM (or use CL_OA2C_CLIENT->DELETE_TOKENS()?)

! Successfactors auth endpoints

The first is not secure anymore and should not be used anymore.
{{{
/oauth/idp
	parameters: 
		client_id
		user_id
		private_key
		token_url
		use_email
		use_username  <=== true
/oauth/token
	parameters:
		company_id
		client_id
		grant_type
		assertion
		new_token	<=== true
}}}
	
!Trace
/usr/sap/DMD/D11/work/dev_w*  (work process traces) => search for "OA2C"

!!Issues

!Enable SAML button
OA2C_CONFIG > reenable/toggle "SAML 2.0 Disabled"	is creating a new certificate at STRUST "SSF OAuth2 Client Identity Provider - Signature", but not if just edited- Then you need to copy the STRUST certificate to SF again!.

!400 "Invalid SAML assertion. For the correct SAML assertion format.."
* x.OA2C_CONFIG press button "Delete SAML 2.0 Settings" Then reenable SAML 2.0 by pressing the toggle button "SAML 2.0 Disabled".
* to use username from DMD use 998 in OA2C_CONFIG and add the userId as alias to your SAP user

!401 "Client authentication failed (e.g., unknown client, no client authentication included, ...)"
* Wrong certificate between STRUST and SF
OR
* you have a mismatch in the codes/attributes in the custom classes of the two BAdIs

!401 "Unable to verify the signature of the SAML assertion. Please ensure that the assertion has a signature and the key pairs match the client ID."
* wrong certificate in SF OAuth => use the one from STRUST "SSF OAuth2 Client Identity Provider - Signature"

!401 Unable to authenticate the client (Login failed - invalid user)
see [https://me.sap.com/notes/2668018/E]
* Test: CL_OA2C_SAML20_ASSERTION->BUILD_NAME_ID() > at line 25 es_saml20_name_id-_value = sy-uname. > replace with userId
* the SF login expects the userId though you configured and pass the username
OR
* you use 998, the alias is always upper case, but the SF userid is lower case

!SAML 2.0 for OAuth 2.0 client is disabled.
* visible at exception in CL_OA2C_CLIENT->IF_OAUTH2_CLIENT~EXECUTE_SAML20_FLOW() line 174
* OA2C_CONFIG > reenable/toggle with "SAML 2.0 Disabled"

! 400 - Parameter "company_id" is required in the OAuth request
You haven't created the BAdIs and copied/adopted the classes to provide

!No implementation exists for BAdI CLB2_OAUTH2_IMPL
* Enhancement Implementation SMI_OAUTH2_AUTH_MAP > Add filter

! HTTPIO_ERROR_OA2C_NO_SECRET
 => OA2C_CONFIG > Edit and re-enter the client secret > Save
 
! Direct connect to <server> failed: NIECONN_REFUSED(-10)
 => network / proxy issue, see [note 3518358|https://me.sap.com/notes/3518358/E]