Skip to content

Add class for certificate API #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
## Prerequisites

- SAP Basis 7.50 or higher
- Packages: `ajson`, `string-map`, `error`, `http`, `distinguished-name`
- Packages:
- [`ajson`](https://github.com/sbcgua/ajson)
- [`error`](https://github.com/abapPM/ABAP-Error)
- [`distinguished-name`](https://github.com/abapPM/ABAP-Distinguished-Name)

## Installation

Expand Down
56 changes: 19 additions & 37 deletions src/z_strust_installer.prog.abap
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE TEXT-t03.
p_test AS CHECKBOX DEFAULT abap_true.
SELECTION-SCREEN END OF BLOCK b3.

CONSTANTS c_api TYPE string VALUE 'https://tools.abappm.com/api/v1/certificates'.

INITIALIZATION.

DATA(subrc) = cl_abap_pse=>authority_check( iv_activity = '01' )
Expand Down Expand Up @@ -64,47 +62,31 @@ START-OF-SELECTION.
WRITE: /'Domain:', p_domain COLOR COL_POSITIVE.
SKIP.

" We can't query wildcard domains so we try with "api" sub-domain
" TODO: if this fails, loop over a couple other common sub-domains
DATA(query) = replace( val = p_domain sub = '*' with = 'api' ).
query = cl_abap_dyn_prg=>escape_xss_url( query ).

" Get certificate data from tools.abappm.com
DATA(agent) = zcl_http_agent=>create( ).

agent->global_headers( )->set(
iv_key = zif_http_agent=>c_header-accept
iv_val = zif_http_agent=>c_content_type-json ).

DATA(response) = agent->request( |{ c_api }?domain={ query }| ).
TRY.
DATA(json) = zcl_strust2_cert_api=>get_certificates( p_domain ).

IF response->is_ok( ) = abap_false.
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, response->error( ).
STOP.
ENDIF.
TRY.
DATA(ajson) = zcl_ajson=>parse( json ).
CATCH zcx_ajson_error INTO DATA(ajson_error).
WRITE: / 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
STOP.
ENDTRY.

TRY.
DATA(ajson) = zcl_ajson=>parse( response->cdata( ) ).
CATCH zcx_ajson_error INTO DATA(ajson_error).
WRITE: / 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
STOP.
ENDTRY.
IF ajson->get( '/error' ) IS NOT INITIAL.
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
STOP.
ENDIF.

IF ajson->get( '/error' ) IS NOT INITIAL.
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
STOP.
ENDIF.
" Keep fingers crossed that the response matches what we need for the update
DATA(cert_domain) = ajson->get( '/domain' ).

" Keep fingers crossed that the response matches what we need for the update
DATA(cert_domain) = ajson->get( '/domain' ).
IF cert_domain <> p_domain AND p_domain NA '*'.
WRITE: / 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
STOP.
ENDIF.

IF cert_domain <> p_domain AND p_domain NA '*'.
WRITE: / 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
STOP.
ENDIF.
" We finally have a certificate that can be used for the update, yay!

" We finally have a certificate that can be used for the update, yay!
TRY.
" Root and intermediate certificates
IF p_root = abap_true.

Expand Down
66 changes: 23 additions & 43 deletions src/z_strust_updater.prog.abap
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
SELECTION-SCREEN END OF BLOCK b1.

SELECTION-SCREEN BEGIN OF BLOCK b2 WITH FRAME TITLE TEXT-t02.
SELECT-OPTIONS s_subj FOR ('STRING') NO INTERVALS.

Check failure on line 17 in src/z_strust_updater.prog.abap

View check run for this annotation

abaplint / abaplint

Variable "S_SUBJ" contains unknown: Select option, fallback

https://rules.abaplint.org/unknown_types
SELECTION-SCREEN END OF BLOCK b2.

SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE TEXT-t03.
Expand All @@ -26,8 +26,6 @@
p_test AS CHECKBOX DEFAULT abap_true.
SELECTION-SCREEN END OF BLOCK b3.

CONSTANTS c_api TYPE string VALUE 'https://tools.abappm.com/api/v1/certificates'.

INITIALIZATION.

DATA(subrc) = cl_abap_pse=>authority_check( iv_activity = '01' )
Expand Down Expand Up @@ -111,51 +109,33 @@
WRITE: /5 'Domain:', domain COLOR COL_POSITIVE.
SKIP.

" We can't query wildcard domains so we try with "api" sub-domain
" TODO: if this fails, loop over a couple other common sub-domains
DATA(query) = replace( val = domain sub = '*' with = 'api' ).
query = cl_abap_dyn_prg=>escape_xss_url( query ).

" Get certificate data from tools.abappm.com
DATA(agent) = zcl_http_agent=>create( ).

agent->global_headers( )->set(
iv_key = zif_http_agent=>c_header-accept
iv_val = zif_http_agent=>c_content_type-json ).

DATA(response) = agent->request( |{ c_api }?domain={ query }| ).

IF response->is_ok( ) = abap_false.
WRITE: /10 'Error getting certificates from API:' COLOR COL_NEGATIVE, response->error( ).
ULINE.
CONTINUE.
ENDIF.

TRY.
DATA(ajson) = zcl_ajson=>parse( response->cdata( ) ).
CATCH zcx_ajson_error INTO DATA(ajson_error).
WRITE: /10 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
ULINE.
CONTINUE.
ENDTRY.

IF ajson->get( '/error' ) IS NOT INITIAL.
WRITE: /10 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
ULINE.
CONTINUE.
ENDIF.
DATA(json) = zcl_strust2_cert_api=>get_certificates( domain ).

TRY.
DATA(ajson) = zcl_ajson=>parse( json ).
CATCH zcx_ajson_error INTO DATA(ajson_error).
WRITE: /10 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
ULINE.
CONTINUE.
ENDTRY.

IF ajson->get( '/error' ) IS NOT INITIAL.
WRITE: /10 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
ULINE.
CONTINUE.
ENDIF.

" Keep fingers crossed that the response matches what we need for the update
DATA(cert_domain) = ajson->get( '/domain' ).
" Keep fingers crossed that the response matches what we need for the update
DATA(cert_domain) = ajson->get( '/domain' ).

IF cert_domain <> domain AND domain NA '*'.
WRITE: /10 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
ULINE.
CONTINUE.
ENDIF.
IF cert_domain <> domain AND domain NA '*'.
WRITE: /10 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
ULINE.
CONTINUE.
ENDIF.

" We finally have a certificate that can be used for the update, yay!
TRY.
" We finally have a certificate that can be used for the update, yay!
" Root and intermediate certificates
IF p_root = abap_true.

Expand Down
176 changes: 176 additions & 0 deletions src/zcl_strust2_cert_api.clas.abap
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
CLASS zcl_strust2_cert_api DEFINITION
PUBLIC
FINAL
CREATE PUBLIC.

************************************************************************
* Trust Management: Certificate API
*
* Get peer, intermediate, and root certificates for a domain from
* https://tools.abappm.com
*
* Copyright 2025 apm.to Inc. <https://apm.to>
* SPDX-License-Identifier: MIT
************************************************************************
PUBLIC SECTION.

CLASS-METHODS get_certificates
IMPORTING
!domain TYPE string
!ssl_id TYPE ssfapplssl DEFAULT 'ANONYM'
!debug TYPE abap_bool DEFAULT abap_false
RETURNING
VALUE(result) TYPE string
RAISING
zcx_error.

PROTECTED SECTION.
PRIVATE SECTION.

CONSTANTS:
c_api_host TYPE string VALUE 'https://tools.abappm.com',
c_api_endpoint TYPE string VALUE '/api/v1/certificates'.

CLASS-METHODS _client
IMPORTING
ssl_id TYPE ssfapplssl
uri TYPE string
RETURNING
VALUE(result) TYPE REF TO if_http_client
RAISING
zcx_error.

CLASS-METHODS _response
IMPORTING
http_client TYPE REF TO if_http_client
RETURNING
VALUE(result) TYPE REF TO if_http_response
RAISING
zcx_error.

CLASS-METHODS _debug
IMPORTING
headers TYPE tihttpnvp
cookies TYPE tihttpcki
debug TYPE abap_bool.

ENDCLASS.



CLASS zcl_strust2_cert_api IMPLEMENTATION.


METHOD get_certificates.

" We can't query wildcard domains so we try with "api" sub-domain
" TODO: if this fails, loop over a couple other common sub-domains
DATA(query) = replace( val = domain sub = '*' with = 'api' ).

query = cl_abap_dyn_prg=>escape_xss_url( query ).

DATA(http_client) = _client(
ssl_id = ssl_id
uri = |{ c_api_endpoint }?domain={ query }| ).

DATA(fetch_response) = _response( http_client ).

" --- Retrieve Certificates from Response ---

DATA(json_response) = fetch_response->get_cdata( ).

IF debug = abap_true.
cl_abap_browser=>show_html( html_string = json_response ).
ENDIF.

IF json_response IS INITIAL OR json_response(1) <> '{'.
zcx_error=>raise( 'Invalid response (expected JSON):' && json_response ).
ENDIF.

result = json_response.

ENDMETHOD.


METHOD _client.

cl_http_client=>create_by_url(
EXPORTING
url = c_api_host
ssl_id = ssl_id
IMPORTING
client = result
EXCEPTIONS
OTHERS = 99 ).
IF sy-subrc <> 0.
zcx_error=>raise_t100( ).
ENDIF.

result->request->set_header_field(
name = '~request_uri'
value = uri ).

result->request->set_header_field(
name = 'accept'
value = 'application/json' ).

ENDMETHOD.


METHOD _debug.

CHECK debug = abap_true.

DATA(html) = `<h2>Headers:</h2>\n`.
LOOP AT headers ASSIGNING FIELD-SYMBOL(<header>).
html = html && |<p>{ <header>-name }: { <header>-value }</p>\n|.
ENDLOOP.

html = html && `<h2>Cookies:</h2>\n`.
LOOP AT cookies ASSIGNING FIELD-SYMBOL(<cookie>).
html = html && |<p>{ <cookie>-name }: { <cookie>-value }</p>\n|.
ENDLOOP.

cl_abap_browser=>show_html( html_string = html ).

ENDMETHOD.


METHOD _response.

DATA:
status_code TYPE sy-subrc,
message TYPE string.

http_client->propertytype_accept_cookie = if_http_client=>co_enabled.

http_client->request->set_version( if_http_entity=>co_protocol_version_1_1 ).

Check failure on line 147 in src/zcl_strust2_cert_api.clas.abap

View check run for this annotation

abaplint / abaplint

Attribute or constant "co_protocol_version_1_1" not found in "if_http_entity"

https://rules.abaplint.org/check_syntax

http_client->send(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
http_invalid_timeout = 4
OTHERS = 5 ).
IF sy-subrc = 0.
http_client->receive(
EXCEPTIONS
http_communication_failure = 1
http_invalid_state = 2
http_processing_failed = 3
OTHERS = 4 ).
ENDIF.

IF sy-subrc <> 0.
http_client->get_last_error(
IMPORTING
code = status_code
message = message ).
zcx_error=>raise( |{ message } (HTTP/{ status_code })| ).
ENDIF.

result = http_client->response.

ENDMETHOD.
ENDCLASS.
16 changes: 16 additions & 0 deletions src/zcl_strust2_cert_api.clas.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
<asx:values>
<VSEOCLASS>
<CLSNAME>ZCL_STRUST2_CERT_API</CLSNAME>
<LANGU>E</LANGU>
<DESCRIPT>Trust Management: Certificate API</DESCRIPT>
<STATE>1</STATE>
<CLSCCINCL>X</CLSCCINCL>
<FIXPT>X</FIXPT>
<UNICODE>X</UNICODE>
</VSEOCLASS>
</asx:values>
</asx:abap>
</abapGit>