ABAP Connectivity, Google Cloud Platform

Connect from AS ABAP to Google Cloud Platform App-Engine resource secured with Google Identity-Aware Proxy

1. Introduction

In our recent project we had the requirement to connect from an AS ABAP system to an API published on Google Cloud Platform App Engine. The API was secured through Googles Identity Aware Proxy (IAP) and Google Cloud Identity & Access Management (IAM) service accounts for authentication to prevent unauthorized use.

This blog describes how to solve the problem on ABAP side to consume a GCP App Engine resource protected through Google IAP. This includes how to generate a Json Web-Token(JWT) signed with service account credentials using RS256 encryption, how to exchange the JWT for Google-signed OpenID(OIDC) token and how to use the OIDC token to request the IAP-protected resource on Google App Engine.

2. Enabling Identity-Aware Proxy for App Engine

First thing to be considered before deploying an API to Googles App Engine, is to think about how to prevent it from being used unauthorized. Research on this topic gives two different solutions for securing endpoints published on App Engine: Cloud Endpoints and Identity-Aware Proxy.

With Cloud Endpoints it is possible to describe the authentication and authorization processes by using OpenAPI standard. This gives you many possibilities to define how an endpoint is secured with roles and which user or technical user (service accounts) are able to access different endpoints.

One major drawback of Cloud Endpoints for our project was, that Cloud Endpoints only supports apps that were published with the environment parameter set to flex on Google App Engine. The flexible environment parameter in the app.yaml describes how google should set up the environment in which your app is running and flexible is using basically docker container as a wrapper for your application. For our use case, we did not want to have Docker, since this requires additional security steps for the application.

The second option is to use the Identity-Aware Proxy. Cloud Identity-Aware Proxy (Cloud IAP) controls access to your cloud applications and VMs running on Google Cloud Platform. Cloud IAP works by verifying user identity and context of the request to determine if a user should be allowed to access an application or a VM. It is less versatile as Cloud Endpoints but easier to use. It also supports apps that were deployed in the standard environment of GCP App Engine.

First step is to create a new IAP-secured Web App User. Navigate to IAM&Admin -> Service accounts and create a new service account. Type in a name and description and press create. On the next screen you have to select a role. Type in IAP in the search and select the role IAP-secured Web App User.

Also Read: SAP ABAP 7.5 Certification Preparation Guide

In the next window you can grant user access to the service account, which is not necessary for the scenario. Press the “Create Key” button and select the option P12. This is needed later for AS ABAP import to STRUST.

The P12 file download should start to your local machine. The service account allows access to the cloud resources, so store it securely. Note the shown secret for later use.

Next step is to activate the IAP for App Engine. Navigate to “Security -> Identity Aware Proxy”

In the list all your available HTTP resources are shown. There you can switch the IAP on for App Engine by moving the switch to the right. All your apps that run in App Engine are listed below.

Select the app in the list you want to add service account authentication to and add the new created service account on the right-hand side panel. Select “IAP-secured Web App User” as role.

Later, we also need the OAUTH Client ID for communication from AS ABAP. To get the ID navigate to IAP and click the three dots button on the right of “App Engine app” and select “Edit OAuth Client” from the menu.

In the next window you can find the Client ID of the OAUTH client on the top. Note down the ID for later use.

With this, we have created a new technical user (service account) and have enabled Identity Aware Proxy for our App Engine HTTP resources. Now whenever the resource is called the IAP is requesting an authentication and checks if the user is authorized to use the resource. We are done on Google side. Now we need to implement the access from an AS ABAP instance.

3. AS ABAP to GCP App Engine access

This chapter shows how to configure ABAP AS for access to GCP and how to write a access handler class in ABAP that will communicate with HTTP resources deployed on GCP App Engine.

Before we continue, we need to understand how the authentication process works with IAP. This flow diagram shows what steps are needed to access a protected resource:

(source: https://bravenewgeek.com/api-authentication-with-gcp-identity-aware-proxy/)

The API consumer is the ABAP AS. First, a JSON Web Token needs to be created and signed with service account private key. The private key can be found in the P12 file that we have downloaded when creating the service account. The signature needs to be RS256 encrypted. Next, we need to exchange the signed JWT with an Open ID token (OIDC). Google offers a service for JWT to OIDC exchange. For return, the service provides the OIDC that can be used to access the IAP protected resource.

3.1 Import service account certificate to STRUST

Before we can write ABAP code and consume our App Engine resource, we have to make sure two things: First, we need to import the P12 file into the AS ABAP system, second we have to make sure, google is a trusted source for communication.

3.1.1 Create new SSF Application

In ABAP AS the certificates are organized in SSF Applications. It is recommended to create a new SSF application for each new use case. Let’s create a new entry in table SSFAPPLIC. Go to transaction SE11 and open the table. Go to data browser and create a new entry.

Use JWT_SI for APPLIC and select everything except B_INCCERTS, B_DETACHED, B_ASKPWD. As Description we set JWT Signature. This entry will be later a new node in transaction STRUST where we can import certificates. Save the new entry.

Next open transaction SSFA. Press “New Entries”. In the dropdown there should be a new option that we just created in table SSFAPPLIC. Select it and set the properties as shown in the screenshot.

This will give us a new node in transaction STRUST.

3.1.2 Import Certificates into STRUST

Open transaction STRUST and a new node should be available with name “SSF JWT Signature”.

Go into “Edit” mode, right click the new node and select “Create” from the context menu. In the “Create PSE” window set Algorithm to “RSA” and Signature Algorithm to “SHA256”.

Confirm the selection and the new node will now be available for imports. From the top menu select “PSE->Import” and select the service account P12 file you have downloaded. You might need to enter the secret that was shown when downloading the P12 file from GCP.

Now the P12 file is loaded into the “File” node in STRUST. Next, we need to move it from “File” node to the right SSF Application. On the top menu select “PSE->Save as”. In the next window select “SSF Application” and select the SSF application we have created in the previous steps.

Confirm the selection and press save. With this, we have now imported the Service Account P12 file into the ABAP AS and can use it to sign our JWT for requests to GCP. With STRUST we have a secure place to store the service account private key and certificate information.

Next, we need to make Google to be a trusted source for communication. This can be achieved by importing the Google Root CA into STRUST.

Go to “https://www.googleapis.com” and download the certificate from the browser. In transaction STRUST, double click the node “SSL Client (Anonymous)”. On the right-hand side panel, in the very bottom press the “Import certificate” button and select the downloaded certificate file. Then press the “Add to Certificate List” button. Confirm by pressing “Save” button at the very top.

With this we have achieved two things: First, we have now the GCP service account private key and certificate imported into the system and second, google is now a trusted source for communication. Now we can create the HTTP connections that will be used for communication in our ABAP code later.

3.2 The ABAP connector

This section covers the setup and code of a ABAP connector class, that will be used to consume the App Engine Endpoints.

3.2.1 HTTP Connections to External System

As shown in the sequence diagram, we need to make two requests. The first request to exchange a signed Json-Web-Token (JWT) for an OpenID Connect-Token (OIDC) with Google. The second request to consume the protected resource on App Engine. For this, we create two “HTTP Connections to External System” in transaction SM59. Starting with the endpoint to exchange JWT with OIDC, press on “Create” button. In the next Window fill in “RFC Destination” name. Call this destination GCP_OAUTH2_TOKEN. Connection Type is “G” and will indicate that this is a connection to external server.

For target host enter “www.googleapis.com” and for path prefix “/ouath2/v4/token”. This is an endpoint provided by google for JWT to OIDC exchange. For Service No enter 443 which is the HTTPS port number. On “Logon & Security” tab activate SSL and select ANONYM SSL Client. Remember the Google Root certificate we imported in STRUST in chapter 3.1.2? This will now be checked whenever a connection to www.googleapis.com is opened and www.googleapis.com will be set as a trusted source.

On “Special Options” tab set HTTP version to HTTP 1.1 and Accept Cookie to Yes(All).

Now the connection needs to be saved and can be tested with a press on “Connection Test”, which should result in HTTP status code 200.

Next cerate another RFC destination which will be the endpoint running on App-Engine. Adjust the settings same as for the OIDC connection. Only thing that needs to be changed is the target host and path prefix on “Technical Settings” tab.

Next, we can start to write the ABAP connector class.

3.2.2 ABAP Connector

This chapter covers the ABAP code in form of an ABAP class that will create and sign a JWT with the private key of the service account that secures the App-Engine resource, exchange the JWT for a OIDC token and use the OIDC token to communicate with the App-Engine endpoint.

Before we start to code the class, create two new structures that will be used for the header and payload of our JWT.

Create two new structures: zgcp_jwt_header and zgcp_jwt_payload.

The zgcp_jwt_header is the JWT header and need two properties:

  • ALG stands for Algorithm and will include the algorithm that is used for encryption which is RS256
  • TYP stands for token type and will be JWT

The payload structure is zgcp_jwt_payload:

  • ISS stands for issuer and will be the name of our Google Service Account
  • AUD stands for audience, basically the consumer of the token
  • TARGET_AUDIENCE is the id of out OAUTH client at Google
  • IAT stands for issued at time and is a timestamp in UNIX time, when we created the token
  • EXP stands for expires and is a timestamp in UNIX time when the token will expire.

Both structures will be the JWT. Next, cerate a new ABAP class, in this example the class is called ZCL_GCP_API_HANDLER. We can start with the first step of the sequence diagram from chapter 3 which would be to

  • Create an JWT
  • Sign and encrypt the JWT with the private key of the into STRUST imported Service Account

Create a new class ZCL_GCP_API_HANDLER. In the private section of the class create two new local types which we will use later:

TYPES:
      ltty_tssfbin TYPE STANDARD TABLE OF ssfbin WITH KEY table_line WITHOUT FURTHER SECONDARY KEYS,

      BEGIN OF oidc_token_json,
        id_token TYPE string,
      END OF oidc_token_json.
  • We will use ltty_tssfbin to define tables of type ssfbin which is needed for function module SSF_KRN_SIGN to sign the JWT.
  • oidc_token_json will be used to deserealize the OIDC json token to a abap structure after the exchange with Google.

Create a new method called create_rs256_signed_jwt. The methods signature looks as follows:

CLASS-METHODS create_rs256_signed_jwt
      IMPORTING
                iv_jwt_header               TYPE zgcp_jwt_header
                iv_jwt_payload              TYPE zgcp_jwt_payload
                iv_ssf_profilename          TYPE string
                iv_ssf_id                   TYPE string
                iv_ssf_result               TYPE i
      RETURNING VALUE(rv_signed_jwt_base64) TYPE string
      RAISING   zcx_gcp_api_handler.

As already mentioned, the encryption for JWT signature needs to be RS256. Importing parameters are a JWT header and JWT payload in form of the structures we have created, SSF profile name that we created in chapter 3.1.1, the SSF id and an SSF result. Returning value of the method should be a signed JWT base64 encoded in string format.

The full methods implementation looks as follows:

METHOD create_rs256_signed_jwt.
    DATA lt_input_bin TYPE STANDARD TABLE OF ssfbin.
    DATA lt_output_bin TYPE STANDARD TABLE OF ssfbin.
    DATA lv_input_length TYPE ssflen.
    DATA lv_output_length TYPE ssflen.
    DATA lv_output_crc TYPE ssfreturn.
    DATA lt_signer TYPE STANDARD TABLE OF ssfinfo.
    DATA lv_unix_iat TYPE string.

    DATA(lv_jwt_payload) = /ui2/cl_json=>serialize(
         data  = iv_jwt_payload
         pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).
    DATA(lv_jwt_header) = /ui2/cl_json=>serialize(
           data  = iv_jwt_header
           pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).

    DATA(lv_jwt_header_base64) = cl_http_utility=>encode_base64( unencoded = lv_jwt_header ).
    DATA(lv_jwt_payload_base64) = cl_http_utility=>encode_base64( unencoded = lv_jwt_payload ).

    DATA(lv_data_base64) = |{ lv_jwt_header_base64 }.{ lv_jwt_payload_base64 }|.
    base64_url_encode(
      CHANGING
        iv_base64 = lv_data_base64
    ).

    TRY.
        lt_input_bin = string_to_binary_tab( iv_string = lv_data_base64 ).
      CATCH zcx_gcp_api_handler INTO DATA(lo_cx).
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = lo_cx->textid.
    ENDTRY.

    lt_signer = VALUE #( ( id = iv_ssf_id profile = iv_ssf_profilename result = iv_ssf_result ) ).

    lv_input_length = strlen( lv_data_base64 ).

    CALL FUNCTION 'SSF_KRN_SIGN'
      EXPORTING
        str_format                   = 'PKCS1-V1.5'
        b_inc_certs                  = abap_false
        b_detached                   = abap_false
        b_inenc                      = abap_false
        ostr_input_data_l            = lv_input_length
        str_hashalg                  = 'SHA256'
      IMPORTING
        ostr_signed_data_l           = lv_output_length
        crc                          = lv_output_crc    " SSF Return code
      TABLES
        ostr_input_data              = lt_input_bin
        signer                       = lt_signer
        ostr_signed_data             = lt_output_bin
      EXCEPTIONS
        ssf_krn_error                = 1
        ssf_krn_noop                 = 2
        ssf_krn_nomemory             = 3
        ssf_krn_opinv                = 4
        ssf_krn_nossflib             = 5
        ssf_krn_signer_list_error    = 6
        ssf_krn_input_data_error     = 7
        ssf_krn_invalid_par          = 8
        ssf_krn_invalid_parlen       = 9
        ssf_fb_input_parameter_error = 10.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_signature_failed.
    ENDIF.

    TRY.
        DATA(lv_signature) = binary_tab_to_string(
                        it_bin_tab = lt_output_bin
                        iv_length  = lv_output_length
                    ).
      CATCH zcx_gcp_api_handler INTO DATA(lo_zcx).
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = lo_zcx->textid.
    ENDTRY.

    DATA(lv_jwt) = |{ lv_data_base64 }.{ cl_http_utility=>encode_base64( unencoded = lv_signature ) }|.
    base64_url_encode(
      CHANGING
        iv_base64 = lv_jwt
    ).

    rv_signed_jwt_base64 = lv_jwt.
  ENDMETHOD.

Code Explanation:

First, the imported ABAP structures for JWT header and payload are serialized as json string. For json handling in ABAP we can use the class /ui2/cl_json which has many handy options for json processing. Next, we need to base64 encode the json strings and concatenate the header and payload using the “.” (dot) separator:

DATA(lv_jwt_payload) = /ui2/cl_json=>serialize(
         data  = iv_jwt_payload
         pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).
    DATA(lv_jwt_header) = /ui2/cl_json=>serialize(
           data  = iv_jwt_header
           pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).

    DATA(lv_jwt_header_base64) = cl_http_utility=>encode_base64( unencoded = lv_jwt_header ).
    DATA(lv_jwt_payload_base64) = cl_http_utility=>encode_base64( unencoded = lv_jwt_payload ).

    DATA(lv_data_base64) = |{ lv_jwt_header_base64 }.{ lv_jwt_payload_base64 }|.

Next, the string needs to be bsae64 URL encoded. For that we write our own helper function base64_url_encode:

CLASS-METHODS base64_url_encode
      CHANGING
        iv_base64 TYPE string.
METHOD base64_url_encode.
    REPLACE ALL OCCURRENCES OF '=' IN iv_base64 WITH ''.
    REPLACE ALL OCCURRENCES OF '+' IN iv_base64 WITH '-'.
    REPLACE ALL OCCURRENCES OF '/' IN iv_base64 WITH '_'.
  ENDMETHOD.

With this function we can call

   base64_url_encode(
      CHANGING
        iv_base64 = lv_data_base64
    ).

in the create_rs256_signed_jwt method. Now we need to sign the JWT with the private key of the service account that we have imported into STRUST. For signature we use function module SSF_KRN_SIGN. The function module expects a binary table as input and gives a binary table as output. This means, we need first to convert our string into a binary table, import it to the function module that will give us another binary table, which we again need to convert back into a string.

Let’s write two another methods that will do the conversion: string_to_binary_tab and binary_tab_to_string.

 CLASS-METHODS string_to_binary_tab
      IMPORTING
                iv_string         TYPE string
      RETURNING VALUE(rt_bin_tab) TYPE ltty_tssfbin
      RAISING   zcx_gcp_api_handler.

    CLASS-METHODS binary_tab_to_string
      IMPORTING
                it_bin_tab       TYPE ltty_tssfbin
                iv_length        TYPE ssflen
      RETURNING VALUE(rv_string) TYPE string
      RAISING   zcx_gcp_api_handler.

And the implementation is:

METHOD string_to_binary_tab.
    DATA lv_xstring TYPE xstring.
    CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
      EXPORTING
        text     = iv_string
        encoding = '4110'
      IMPORTING
        buffer   = lv_xstring
      EXCEPTIONS
        failed   = 1
        OTHERS   = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_strtobin_conversion_failed.
    ENDIF.

    CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
      EXPORTING
        buffer     = lv_xstring
      TABLES
        binary_tab = rt_bin_tab.
  ENDMETHOD.
 METHOD binary_tab_to_string.
    CALL FUNCTION 'SCMS_BINARY_TO_STRING'
      EXPORTING
        input_length = iv_length
        encoding     = '4110'
      IMPORTING
        text_buffer  = rv_string
      TABLES
        binary_tab   = it_bin_tab
      EXCEPTIONS
        failed       = 1
        OTHERS       = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_bintostr_conversion_failed.
    ENDIF.
  ENDMETHOD.

This code converts the string into the binary table and the binary table back to a string. As already Next, convert the JWT string into a binary table with the call of:

 TRY.
        lt_input_bin = string_to_binary_tab( iv_string = lv_data_base64 ).
      CATCH zcx_gcp_api_handler INTO DATA(lo_cx).
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = lo_cx->textid.
    ENDTRY.

Next, we can add a new entry into the lt_signer table, which will hold information about our SSF profile and application we have created, we also need to determine the string length of our encoded JWT token. Last, we can call the SSF_KRN_SIGN function module to sign the JWT with the private key of our service account. The output will be given in table lt_output_bin:

lt_signer = VALUE #( ( id = iv_ssf_id profile = iv_ssf_profilename result = iv_ssf_result ) ).

    lv_input_length = strlen( lv_data_base64 ).

    CALL FUNCTION 'SSF_KRN_SIGN'
      EXPORTING
        str_format                   = 'PKCS1-V1.5'
        b_inc_certs                  = abap_false
        b_detached                   = abap_false
        b_inenc                      = abap_false
        ostr_input_data_l            = lv_input_length
        str_hashalg                  = 'SHA256'
      IMPORTING
        ostr_signed_data_l           = lv_output_length
        crc                          = lv_output_crc    " SSF Return code
      TABLES
        ostr_input_data              = lt_input_bin
        signer                       = lt_signer
        ostr_signed_data             = lt_output_bin
      EXCEPTIONS
        ssf_krn_error                = 1
        ssf_krn_noop                 = 2
        ssf_krn_nomemory             = 3
        ssf_krn_opinv                = 4
        ssf_krn_nossflib             = 5
        ssf_krn_signer_list_error    = 6
        ssf_krn_input_data_error     = 7
        ssf_krn_invalid_par          = 8
        ssf_krn_invalid_parlen       = 9
        ssf_fb_input_parameter_error = 10.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_signature_failed.
    ENDIF.

Now convert the lt_output_bin table back to a string with the use of the helper function binary_tab_to_string:

TRY.
        DATA(lv_signature) = binary_tab_to_string(
                        it_bin_tab = lt_output_bin
                        iv_length  = lv_output_length
                    ).
      CATCH zcx_gcp_api_handler INTO DATA(lo_zcx).
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = lo_zcx->textid.
    ENDTRY.

Only thing left is to concatenate the JWT with the signature and the “.”(dot) separator. The signature needs to be base64 encoded too:

DATA(lv_jwt) = |{ lv_data_base64 }.{ cl_http_utility=>encode_base64( unencoded = lv_signature ) }|.
    base64_url_encode(
      CHANGING
        iv_base64 = lv_jwt
    ).

    rv_signed_jwt_base64 = lv_jwt.

The return value of the create_rs256_signed_jwt function can now be set. As we have the signed JWT we need to exchange it with an OIDC with Google. For this create a new function called exchange_jwt_with_oidc_token. The function signature looks as follows:

CLASS-METHODS exchange_jwt_with_oidc_token
      IMPORTING
                iv_exchange_destination TYPE c
                iv_jwt_token            TYPE string
      RETURNING VALUE(rv_oidc_base64)   TYPE string
      RAISING   zcx_gcp_api_handler.

The input parameters are the destination we have created in SM59 for exchanging the token and the signed JWT token itself. The returning value of the function is the OIDC token. Full method implementation looks as follows:

METHOD exchange_jwt_with_oidc_token.
    DATA lo_client  TYPE REF TO if_http_client.
    DATA ls_response TYPE oidc_token_json.

    CALL METHOD cl_http_client=>create_by_destination
      EXPORTING
        destination              = iv_exchange_destination
      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.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_oauth_dest_not_found.
    ENDIF.

    IF lo_client IS BOUND.
      lo_client->request->set_method( if_http_request=>co_request_method_post ).
      lo_client->request->set_formfield_encoding( formfield_encoding = if_http_entity=>co_formfield_encoding_encoded ).

      lo_client->request->set_form_field(
        EXPORTING
          name  = 'grant_type'
          value = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
      ).

      lo_client->request->set_form_field(
        EXPORTING
          name  = 'assertion'
          value = iv_jwt_token
      ).

      lo_client->send( ).
      lo_client->receive(
        EXCEPTIONS
          http_communication_failure = 1
          http_invalid_state         = 2
          http_processing_failed     = 3
      ).
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = zcx_gcp_api_handler=>zcx_oauth_token_receive_fail.
      ENDIF.

      DATA(lv_response_json) = lo_client->response->get_cdata( ).

      /ui2/cl_json=>deserialize(
        EXPORTING
          json = lv_response_json
          pretty_name = /ui2/cl_json=>pretty_mode-camel_case
        CHANGING data = ls_response ).

      IF ls_response-id_token IS INITIAL.
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = zcx_gcp_api_handler=>zcx_oauth_token_receive_fail.
      ENDIF.

      rv_oidc_base64 = ls_response-id_token.
    ENDIF.
  ENDMETHOD.

Code Explanation:

First, we create a new HTTP client from our SM59 destination. Once the client is created, we need to set some form properties:

  • grant_type is ‘urn:ietf:params:oauth:grant-type:jwt-bearer’
  • assertion is the given signed JWT token

Next, we can call send and receive. The OIDC token can now be received from the response in json format and parsed into a variable of our local type oidc_token.

DATA(lv_response_json) = lo_client->response->get_cdata( ).

      /ui2/cl_json=>deserialize(
        EXPORTING
          json = lv_response_json
          pretty_name = /ui2/cl_json=>pretty_mode-camel_case
        CHANGING data = ls_response ).

Now the function returning value can be set with:

rv_oidc_base64 = ls_response-id_token.

With this, we have signed a JWT with the private key of our Google Service Account and exchanged it for an OIDC token. Only thing left is to make the request to our API using the new OIDC token for authentication.

In the next step we can create a method to do the API request called “do_api_request”. The signature of the method looks as follows:

CLASS-METHODS do_api_request
      IMPORTING
                iv_destination     TYPE c
                iv_oidc_token      TYPE string
                iv_method          TYPE string
                iv_xcontent        TYPE xstring OPTIONAL
                iv_content         TYPE string  OPTIONAL
                iv_sub_uri         TYPE string  OPTIONAL
                it_header_fields   TYPE tihttpnvp OPTIONAL
                it_cookies         TYPE tihttpcki OPTIONAL
                iv_content_type    TYPE string DEFAULT 'application/json'
      RETURNING VALUE(rs_response) TYPE zgcp_response
      RAISING   zcx_gcp_api_handler.

Importing values are:

  • The destination we have created in SM59 to access our API
  • the OIDC token string
  • the method type for the request that could be GET/POST etc. (optional)
  • content as string and xstring if we need to post something (optional)
  • sub uri if we have multiple sub uris for our destination (optional)
  • header fields if we need any (optional)
  • a table to save cookies if needed (optional)
  • and the content type that will be default set to application/json (default application/json)

Full method implementation looks as follows:

METHOD do_api_request.
    DATA lo_client_api  TYPE REF TO if_http_client.
    DATA lv_response TYPE string.
    DATA lv_oidc TYPE string.

    CALL METHOD cl_http_client=>create_by_destination
      EXPORTING
        destination              = iv_destination
      IMPORTING
        client                   = lo_client_api
      EXCEPTIONS
        argument_not_found       = 1
        destination_not_found    = 2
        destination_no_authority = 3
        plugin_not_active        = 4
        internal_error           = 5
        OTHERS                   = 6.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_api_dest_not_found.
    ENDIF.

    IF lo_client_api IS BOUND.
      lv_oidc = |Bearer { iv_oidc_token }|.

      lo_client_api->request->set_header_fields( fields = it_header_fields ).

      lo_client_api->request->set_content_type( content_type = iv_content_type ).
      lo_client_api->request->set_method( method = iv_method ).

*         set jwt token auth
      lo_client_api->request->set_header_field(
          name  = 'Authorization' ##NO_TEXT
          value = lv_oidc
      ).
      lo_client_api->request->set_header_field(
          name  = 'content-type'
          value = iv_content_type
      ).


      IF iv_sub_uri IS NOT INITIAL.
        cl_http_utility=>set_request_uri(
            request = lo_client_api->request
            uri     = iv_sub_uri
        ).
      ENDIF.

      IF iv_xcontent IS NOT INITIAL.
        lo_client_api->request->set_data( data = iv_xcontent ).
      ENDIF.

      IF iv_content IS NOT INITIAL.
        lo_client_api->request->set_cdata( data = iv_content ).
      ENDIF.

      LOOP AT it_cookies ASSIGNING FIELD-SYMBOL(<cookie>).
        lo_client_api->request->set_cookie(
          EXPORTING
            name    = <cookie>-name                 " Name of cookie
            path    = <cookie>-path               " Path of Cookie
            value   = <cookie>-value                 " Cookie value
            domain  = <cookie>-xdomain               " Domain Name of Cookie
            expires = <cookie>-expires               " Cookie expiry date
            secure  = <cookie>-secure                " 0: unsaved; 1:saved
        ).
      ENDLOOP.

      lo_client_api->send( ).
      lo_client_api->receive(
        EXCEPTIONS
          http_communication_failure = 1
          http_invalid_state         = 2
          http_processing_failed     = 3
      ).
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = zcx_gcp_api_handler=>zcx_api_receive_failed.
      ENDIF.

      rs_response-content = lo_client_api->response->get_data( ).
      lo_client_api->response->get_status( IMPORTING code   = rs_response-code
                                                     reason = rs_response-reason ).
      lo_client_api->response->get_cookies( CHANGING cookies = rs_response-cookies ).
    ENDIF.
  ENDMETHOD.

First, we create a http client from the SM59 destination we have created for our API endpoint. Next, we have to set some request header properties:

Authorization field has to be in format “Bearer + OIDC token”. We can create a string simply by concatenating the string ‘Bearer ‘ with our OIDC token. We must set the content type, method type, sub uri and the content if available. We can add some cookies if needed too. The we can send the request and receive a response!

If everything went right, our GCP App Engine API should now respond with whatever response we have defined. In the next step a test class can be created to test the new GCP ABAP connector.

3.2.3 Testing the new ABAP connector class

A new class “ZCL_GCP_JWT_AUTH_TEST” can be created containing one method “test_api“.

 METHOD test_api.

    DATA(lv_iat) = zcl_gcp_api_handler=>get_iat_unixtime( ).
    DATA(ls_jwt_payload) = VALUE zgcp_jwt_payload( iss = 'service account email'
                                              aud = 'https://www.googleapis.com/oauth2/v4/token'
                                              target_audience = 'OAuth client ID'
                                              iat = lv_iat
                                              exp = lv_iat + 30 ).

    DATA(ls_jwt_header) = VALUE zgcp_jwt_header( typ = 'JWT'
                                                 alg = 'RS256' ).

    TRY.
        DATA(lv_signed_jwt) = zcl_gcp_api_handler=>create_rs256_signed_jwt(
          EXPORTING
            iv_jwt_header        = ls_jwt_header
            iv_jwt_payload       = ls_jwt_payload
            iv_ssf_profilename   = 'SAPJWT_SI001.pse'
            iv_ssf_id            = '<implicit>'
            iv_ssf_result        = 28
        ).
      CATCH zcx_gcp_api_handler.
    ENDTRY.

    TRY.
        DATA(lv_oidc) = zcl_gcp_api_handler=>exchange_jwt_with_oidc_token(
                        iv_exchange_destination = 'GCP_OAUTH2_TOKEN'
                        iv_jwt_token            = lv_signed_jwt
        ).
      CATCH zcx_gcp_api_handler.
    ENDTRY.

    TRY.
        DATA(lv_response) = zcl_gcp_api_handler=>do_api_request(
                            iv_destination = 'GCP_SA_AUTH_TEST'
                            iv_oidc_token  = lv_oidc
                            iv_method      = if_http_request=>co_request_method_post
                            iv_content     = |\{"message": "hello world"\}|
                        ).
      CATCH zcx_gcp_api_handler.
    ENDTRY.

  ENDMETHOD.

Following steps needs to be performed:

  • Create JWT header and payload
  • sign the JWT with private GCP service account key
  • exchange signed JWT with OIDC token
  • do secured App Engine api request

A JWT consists of a header and a payload. ABAP structures zgcp_jwt_payload and zgcp_jwt_header have been created to map the needed JWT data.

In payload, issuer which is the service account email needs to be provided, audience is the endpoint for OIDC token exchange from Google, target audience is the OAuth client ID (we took a note in chapter 2). iat and exp is the time when we issued the token and when it will expire in Unix time format (there is a helper function to get the Unix time of the system in the ABAP connector class ZCL_GCP_API_HADLER). Once the values are maped to the ABAP structures, we cann call the functions of the connector class as follows:

  • create_rs256_signed_jwt, which will return the signed JWT in string format
  • exchange_jwt_with_oidc_token, which will return the OIDC token in exchange for the JWT
  • do_api_request, which is the request to the App Engine endpoint and will return whatever was defined for the endpoint

With this code and configurations of the ABAP AS it is now possible to consume a Google App Engine API secured with IAP, from ABAP applications in a hybrid scenario.

4. Appendix

Full ZCL_GCP_API_HADLER class implementation:

CLASS zcl_gcp_api_handler DEFINITION
  PUBLIC
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    CLASS-METHODS create_rs256_signed_jwt
      IMPORTING
                iv_jwt_header               TYPE zgcp_jwt_header
                iv_jwt_payload              TYPE zgcp_jwt_payload
                iv_ssf_profilename          TYPE string
                iv_ssf_id                   TYPE string
                iv_ssf_result               TYPE i
      RETURNING VALUE(rv_signed_jwt_base64) TYPE string
      RAISING   zcx_gcp_api_handler.

    CLASS-METHODS exchange_jwt_with_oidc_token
      IMPORTING
                iv_exchange_destination TYPE c
                iv_jwt_token            TYPE string
      RETURNING VALUE(rv_oidc_base64)   TYPE string
      RAISING   zcx_gcp_api_handler.

    CLASS-METHODS do_api_request
      IMPORTING
                iv_destination     TYPE c
                iv_oidc_token      TYPE string
                iv_method          TYPE string
                iv_xcontent        TYPE xstring OPTIONAL
                iv_content         TYPE string  OPTIONAL
                iv_sub_uri         TYPE string  OPTIONAL
                it_header_fields   TYPE tihttpnvp OPTIONAL
                it_cookies         TYPE tihttpcki OPTIONAL
                iv_content_type    TYPE string DEFAULT 'application/json'
      RETURNING VALUE(rs_response) TYPE zgcp_response
      RAISING   zcx_gcp_api_handler.

    CLASS-METHODS get_iat_unixtime RETURNING VALUE(rv_iat) TYPE int4.

  PROTECTED SECTION.

  PRIVATE SECTION.

    TYPES:
      ltty_tssfbin TYPE STANDARD TABLE OF ssfbin WITH KEY table_line WITHOUT FURTHER SECONDARY KEYS,

      BEGIN OF oidc_token_json,
        id_token TYPE string,
      END OF oidc_token_json.

    CLASS-METHODS string_to_binary_tab
      IMPORTING
                iv_string         TYPE string
      RETURNING VALUE(rt_bin_tab) TYPE ltty_tssfbin
      RAISING   zcx_gcp_api_handler.

    CLASS-METHODS binary_tab_to_string
      IMPORTING
                it_bin_tab       TYPE ltty_tssfbin
                iv_length        TYPE ssflen
      RETURNING VALUE(rv_string) TYPE string
      RAISING   zcx_gcp_api_handler.

    CLASS-METHODS base64_url_encode
      CHANGING
        iv_base64 TYPE string.
ENDCLASS.

CLASS ZCL_GCP_API_HANDLER IMPLEMENTATION.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_GCP_API_HANDLER=>BASE64_URL_ENCODE
* +-------------------------------------------------------------------------------------------------+
* | [<-->] IV_BASE64                      TYPE        STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD base64_url_encode.
    REPLACE ALL OCCURRENCES OF '=' IN iv_base64 WITH ''.
    REPLACE ALL OCCURRENCES OF '+' IN iv_base64 WITH '-'.
    REPLACE ALL OCCURRENCES OF '/' IN iv_base64 WITH '_'.
  ENDMETHOD.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_GCP_API_HANDLER=>BINARY_TAB_TO_STRING
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_BIN_TAB                     TYPE        LTTY_TSSFBIN
* | [--->] IV_LENGTH                      TYPE        SSFLEN
* | [<-()] RV_STRING                      TYPE        STRING
* | [!CX!] ZCX_GCP_API_HANDLER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD binary_tab_to_string.
    CALL FUNCTION 'SCMS_BINARY_TO_STRING'
      EXPORTING
        input_length = iv_length
        encoding     = '4110'
      IMPORTING
        text_buffer  = rv_string
      TABLES
        binary_tab   = it_bin_tab
      EXCEPTIONS
        failed       = 1
        OTHERS       = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_bintostr_conversion_failed.
    ENDIF.
  ENDMETHOD.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_GCP_API_HANDLER=>CREATE_RS256_SIGNED_JWT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_JWT_HEADER                  TYPE        ZGCP_JWT_HEADER
* | [--->] IV_JWT_PAYLOAD                 TYPE        ZGCP_JWT_PAYLOAD
* | [--->] IV_SSF_PROFILENAME             TYPE        STRING
* | [--->] IV_SSF_ID                      TYPE        STRING
* | [--->] IV_SSF_RESULT                  TYPE        I
* | [<-()] RV_SIGNED_JWT_BASE64           TYPE        STRING
* | [!CX!] ZCX_GCP_API_HANDLER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD create_rs256_signed_jwt.
    DATA lt_input_bin TYPE STANDARD TABLE OF ssfbin.
    DATA lt_output_bin TYPE STANDARD TABLE OF ssfbin.
    DATA lv_input_length TYPE ssflen.
    DATA lv_output_length TYPE ssflen.
    DATA lv_output_crc TYPE ssfreturn.
    DATA lt_signer TYPE STANDARD TABLE OF ssfinfo.
    DATA lv_unix_iat TYPE string.

    DATA(lv_jwt_payload) = /ui2/cl_json=>serialize(
         data  = iv_jwt_payload
         pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).
    DATA(lv_jwt_header) = /ui2/cl_json=>serialize(
           data  = iv_jwt_header
           pretty_name = /ui2/cl_json=>pretty_mode-low_case
    ).

    DATA(lv_jwt_header_base64) = cl_http_utility=>encode_base64( unencoded = lv_jwt_header ).
    DATA(lv_jwt_payload_base64) = cl_http_utility=>encode_base64( unencoded = lv_jwt_payload ).

    DATA(lv_data_base64) = |{ lv_jwt_header_base64 }.{ lv_jwt_payload_base64 }|.
    base64_url_encode(
      CHANGING
        iv_base64 = lv_data_base64
    ).

    TRY.
        lt_input_bin = string_to_binary_tab( iv_string = lv_data_base64 ).
      CATCH zcx_gcp_api_handler INTO DATA(lo_cx).
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = lo_cx->textid.
    ENDTRY.

    lt_signer = VALUE #( ( id = iv_ssf_id profile = iv_ssf_profilename result = iv_ssf_result ) ).

    lv_input_length = strlen( lv_data_base64 ).

    CALL FUNCTION 'SSF_KRN_SIGN'
      EXPORTING
        str_format                   = 'PKCS1-V1.5'
        b_inc_certs                  = abap_false
        b_detached                   = abap_false
        b_inenc                      = abap_false
        ostr_input_data_l            = lv_input_length
        str_hashalg                  = 'SHA256'
      IMPORTING
        ostr_signed_data_l           = lv_output_length
        crc                          = lv_output_crc    " SSF Return code
      TABLES
        ostr_input_data              = lt_input_bin
        signer                       = lt_signer
        ostr_signed_data             = lt_output_bin
      EXCEPTIONS
        ssf_krn_error                = 1
        ssf_krn_noop                 = 2
        ssf_krn_nomemory             = 3
        ssf_krn_opinv                = 4
        ssf_krn_nossflib             = 5
        ssf_krn_signer_list_error    = 6
        ssf_krn_input_data_error     = 7
        ssf_krn_invalid_par          = 8
        ssf_krn_invalid_parlen       = 9
        ssf_fb_input_parameter_error = 10.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_signature_failed.
    ENDIF.

    TRY.
        DATA(lv_signature) = binary_tab_to_string(
                        it_bin_tab = lt_output_bin
                        iv_length  = lv_output_length
                    ).
      CATCH zcx_gcp_api_handler INTO DATA(lo_zcx).
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = lo_zcx->textid.
    ENDTRY.

    DATA(lv_jwt) = |{ lv_data_base64 }.{ cl_http_utility=>encode_base64( unencoded = lv_signature ) }|.
    base64_url_encode(
      CHANGING
        iv_base64 = lv_jwt
    ).

    rv_signed_jwt_base64 = lv_jwt.
  ENDMETHOD.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_GCP_API_HANDLER=>DO_API_REQUEST
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_DESTINATION                 TYPE        C
* | [--->] IV_OIDC_TOKEN                  TYPE        STRING
* | [--->] IV_METHOD                      TYPE        STRING
* | [--->] IV_XCONTENT                    TYPE        XSTRING(optional)
* | [--->] IV_CONTENT                     TYPE        STRING(optional)
* | [--->] IV_SUB_URI                     TYPE        STRING(optional)
* | [--->] IT_HEADER_FIELDS               TYPE        TIHTTPNVP(optional)
* | [--->] IT_COOKIES                     TYPE        TIHTTPCKI(optional)
* | [--->] IV_CONTENT_TYPE                TYPE        STRING (default ='application/json')
* | [<-()] RS_RESPONSE                    TYPE        ZGCP_RESPONSE
* | [!CX!] ZCX_GCP_API_HANDLER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD do_api_request.
    DATA lo_client_api  TYPE REF TO if_http_client.
    DATA lv_response TYPE string.
    DATA lv_oidc TYPE string.

    CALL METHOD cl_http_client=>create_by_destination
      EXPORTING
        destination              = iv_destination
      IMPORTING
        client                   = lo_client_api
      EXCEPTIONS
        argument_not_found       = 1
        destination_not_found    = 2
        destination_no_authority = 3
        plugin_not_active        = 4
        internal_error           = 5
        OTHERS                   = 6.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_api_dest_not_found.
    ENDIF.

    IF lo_client_api IS BOUND.
      lv_oidc = |Bearer { iv_oidc_token }|.

      lo_client_api->request->set_header_fields( fields = it_header_fields ).

      lo_client_api->request->set_content_type( content_type = iv_content_type ).
      lo_client_api->request->set_method( method = iv_method ).

*         set jwt token auth
      lo_client_api->request->set_header_field(
          name  = 'Authorization' ##NO_TEXT
          value = lv_oidc
      ).
      lo_client_api->request->set_header_field(
          name  = 'content-type'
          value = iv_content_type
      ).


      IF iv_sub_uri IS NOT INITIAL.
        cl_http_utility=>set_request_uri(
            request = lo_client_api->request
            uri     = iv_sub_uri
        ).
      ENDIF.

      IF iv_xcontent IS NOT INITIAL.
        lo_client_api->request->set_data( data = iv_xcontent ).
      ENDIF.

      IF iv_content IS NOT INITIAL.
        lo_client_api->request->set_cdata( data = iv_content ).
      ENDIF.

      LOOP AT it_cookies ASSIGNING FIELD-SYMBOL(<cookie>).
        lo_client_api->request->set_cookie(
          EXPORTING
            name    = <cookie>-name                 " Name of cookie
            path    = <cookie>-path               " Path of Cookie
            value   = <cookie>-value                 " Cookie value
            domain  = <cookie>-xdomain               " Domain Name of Cookie
            expires = <cookie>-expires               " Cookie expiry date
            secure  = <cookie>-secure                " 0: unsaved; 1:saved
        ).
      ENDLOOP.

      lo_client_api->send( ).
      lo_client_api->receive(
        EXCEPTIONS
          http_communication_failure = 1
          http_invalid_state         = 2
          http_processing_failed     = 3
      ).
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = zcx_gcp_api_handler=>zcx_api_receive_failed.
      ENDIF.

      rs_response-content = lo_client_api->response->get_data( ).
      lo_client_api->response->get_status( IMPORTING code   = rs_response-code
                                                     reason = rs_response-reason ).
      lo_client_api->response->get_cookies( CHANGING cookies = rs_response-cookies ).
    ENDIF.
  ENDMETHOD.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_GCP_API_HANDLER=>EXCHANGE_JWT_WITH_OIDC_TOKEN
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_EXCHANGE_DESTINATION        TYPE        C
* | [--->] IV_JWT_TOKEN                   TYPE        STRING
* | [<-()] RV_OIDC_BASE64                 TYPE        STRING
* | [!CX!] ZCX_GCP_API_HANDLER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD exchange_jwt_with_oidc_token.
    DATA lo_client  TYPE REF TO if_http_client.
    DATA ls_response TYPE oidc_token_json.

    CALL METHOD cl_http_client=>create_by_destination
      EXPORTING
        destination              = iv_exchange_destination
      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.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_oauth_dest_not_found.
    ENDIF.

    IF lo_client IS BOUND.
      lo_client->request->set_method( if_http_request=>co_request_method_post ).
      lo_client->request->set_formfield_encoding( formfield_encoding = if_http_entity=>co_formfield_encoding_encoded ).

      lo_client->request->set_form_field(
        EXPORTING
          name  = 'grant_type'
          value = 'urn:ietf:params:oauth:grant-type:jwt-bearer'
      ).

      lo_client->request->set_form_field(
        EXPORTING
          name  = 'assertion'
          value = iv_jwt_token
      ).

      lo_client->send( ).
      lo_client->receive(
        EXCEPTIONS
          http_communication_failure = 1
          http_invalid_state         = 2
          http_processing_failed     = 3
      ).
      IF sy-subrc <> 0.
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = zcx_gcp_api_handler=>zcx_oauth_token_receive_fail.
      ENDIF.

      DATA(lv_response_json) = lo_client->response->get_cdata( ).

      /ui2/cl_json=>deserialize(
        EXPORTING
          json = lv_response_json
          pretty_name = /ui2/cl_json=>pretty_mode-camel_case
        CHANGING data = ls_response ).

      IF ls_response-id_token IS INITIAL.
        RAISE EXCEPTION TYPE zcx_gcp_api_handler
          EXPORTING
            textid = zcx_gcp_api_handler=>zcx_oauth_token_receive_fail.
      ENDIF.

      rv_oidc_base64 = ls_response-id_token.
    ENDIF.
  ENDMETHOD.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method ZCL_GCP_API_HANDLER=>GET_IAT_UNIXTIME
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RV_IAT                         TYPE        INT4
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD get_iat_unixtime.
    DATA lv_unix_iat TYPE string.

    GET TIME STAMP FIELD DATA(lv_timestamp).

    CONVERT TIME STAMP lv_timestamp TIME ZONE 'UTC' INTO DATE DATA(lv_date) TIME DATA(lv_time).

    cl_pco_utility=>convert_abap_timestamp_to_java(
      EXPORTING
        iv_date      = lv_date
        iv_time      = lv_time
        iv_msec      = 0
      IMPORTING
        ev_timestamp = lv_unix_iat
    ).

    rv_iat = substring( val = lv_unix_iat off = 0 len = strlen( lv_unix_iat ) - 3 ).
  ENDMETHOD.

* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Private Method ZCL_GCP_API_HANDLER=>STRING_TO_BINARY_TAB
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_STRING                      TYPE        STRING
* | [<-()] RT_BIN_TAB                     TYPE        LTTY_TSSFBIN
* | [!CX!] ZCX_GCP_API_HANDLER
* +--------------------------------------------------------------------------------------</SIGNATURE>
  METHOD string_to_binary_tab.
    DATA lv_xstring TYPE xstring.
    CALL FUNCTION 'SCMS_STRING_TO_XSTRING'
      EXPORTING
        text     = iv_string
        encoding = '4110'
      IMPORTING
        buffer   = lv_xstring
      EXCEPTIONS
        failed   = 1
        OTHERS   = 2.
    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_gcp_api_handler
        EXPORTING
          textid = zcx_gcp_api_handler=>zcx_strtobin_conversion_failed.
    ENDIF.

    CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
      EXPORTING
        buffer     = lv_xstring
      TABLES
        binary_tab = rt_bin_tab.
  ENDMETHOD.
ENDCLASS.

Leave a Reply

Your email address will not be published. Required fields are marked *