Skip to content

Commit a6e070e

Browse files
authored
Add class for certificate API (#5)
* Add class for certificate API * Update README.md
1 parent 520c6cb commit a6e070e

File tree

5 files changed

+238
-81
lines changed

5 files changed

+238
-81
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
9191
## Prerequisites
9292

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

9699
## Installation
97100

src/z_strust_installer.prog.abap

Lines changed: 19 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE TEXT-t03.
2424
p_test AS CHECKBOX DEFAULT abap_true.
2525
SELECTION-SCREEN END OF BLOCK b3.
2626

27-
CONSTANTS c_api TYPE string VALUE 'https://tools.abappm.com/api/v1/certificates'.
28-
2927
INITIALIZATION.
3028

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

67-
" We can't query wildcard domains so we try with "api" sub-domain
68-
" TODO: if this fails, loop over a couple other common sub-domains
69-
DATA(query) = replace( val = p_domain sub = '*' with = 'api' ).
70-
query = cl_abap_dyn_prg=>escape_xss_url( query ).
71-
72-
" Get certificate data from tools.abappm.com
73-
DATA(agent) = zcl_http_agent=>create( ).
74-
75-
agent->global_headers( )->set(
76-
iv_key = zif_http_agent=>c_header-accept
77-
iv_val = zif_http_agent=>c_content_type-json ).
78-
79-
DATA(response) = agent->request( |{ c_api }?domain={ query }| ).
65+
TRY.
66+
DATA(json) = zcl_strust2_cert_api=>get_certificates( p_domain ).
8067

81-
IF response->is_ok( ) = abap_false.
82-
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, response->error( ).
83-
STOP.
84-
ENDIF.
68+
TRY.
69+
DATA(ajson) = zcl_ajson=>parse( json ).
70+
CATCH zcx_ajson_error INTO DATA(ajson_error).
71+
WRITE: / 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
72+
STOP.
73+
ENDTRY.
8574

86-
TRY.
87-
DATA(ajson) = zcl_ajson=>parse( response->cdata( ) ).
88-
CATCH zcx_ajson_error INTO DATA(ajson_error).
89-
WRITE: / 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
90-
STOP.
91-
ENDTRY.
75+
IF ajson->get( '/error' ) IS NOT INITIAL.
76+
WRITE: / 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
77+
STOP.
78+
ENDIF.
9279

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

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

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

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

src/z_strust_updater.prog.abap

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ SELECTION-SCREEN BEGIN OF BLOCK b3 WITH FRAME TITLE TEXT-t03.
2626
p_test AS CHECKBOX DEFAULT abap_true.
2727
SELECTION-SCREEN END OF BLOCK b3.
2828

29-
CONSTANTS c_api TYPE string VALUE 'https://tools.abappm.com/api/v1/certificates'.
30-
3129
INITIALIZATION.
3230

3331
DATA(subrc) = cl_abap_pse=>authority_check( iv_activity = '01' )
@@ -111,51 +109,33 @@ START-OF-SELECTION.
111109
WRITE: /5 'Domain:', domain COLOR COL_POSITIVE.
112110
SKIP.
113111

114-
" We can't query wildcard domains so we try with "api" sub-domain
115-
" TODO: if this fails, loop over a couple other common sub-domains
116-
DATA(query) = replace( val = domain sub = '*' with = 'api' ).
117-
query = cl_abap_dyn_prg=>escape_xss_url( query ).
118-
119-
" Get certificate data from tools.abappm.com
120-
DATA(agent) = zcl_http_agent=>create( ).
121-
122-
agent->global_headers( )->set(
123-
iv_key = zif_http_agent=>c_header-accept
124-
iv_val = zif_http_agent=>c_content_type-json ).
125-
126-
DATA(response) = agent->request( |{ c_api }?domain={ query }| ).
127-
128-
IF response->is_ok( ) = abap_false.
129-
WRITE: /10 'Error getting certificates from API:' COLOR COL_NEGATIVE, response->error( ).
130-
ULINE.
131-
CONTINUE.
132-
ENDIF.
133-
134112
TRY.
135-
DATA(ajson) = zcl_ajson=>parse( response->cdata( ) ).
136-
CATCH zcx_ajson_error INTO DATA(ajson_error).
137-
WRITE: /10 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
138-
ULINE.
139-
CONTINUE.
140-
ENDTRY.
141-
142-
IF ajson->get( '/error' ) IS NOT INITIAL.
143-
WRITE: /10 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
144-
ULINE.
145-
CONTINUE.
146-
ENDIF.
113+
DATA(json) = zcl_strust2_cert_api=>get_certificates( domain ).
114+
115+
TRY.
116+
DATA(ajson) = zcl_ajson=>parse( json ).
117+
CATCH zcx_ajson_error INTO DATA(ajson_error).
118+
WRITE: /10 'Error parsing API response:' COLOR COL_NEGATIVE, ajson_error->get_text( ).
119+
ULINE.
120+
CONTINUE.
121+
ENDTRY.
122+
123+
IF ajson->get( '/error' ) IS NOT INITIAL.
124+
WRITE: /10 'Error getting certificates from API:' COLOR COL_NEGATIVE, ajson->get( '/error' ).
125+
ULINE.
126+
CONTINUE.
127+
ENDIF.
147128

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

151-
IF cert_domain <> domain AND domain NA '*'.
152-
WRITE: /10 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
153-
ULINE.
154-
CONTINUE.
155-
ENDIF.
132+
IF cert_domain <> domain AND domain NA '*'.
133+
WRITE: /10 'Certificates domain does not match request:' COLOR COL_TOTAL, cert_domain.
134+
ULINE.
135+
CONTINUE.
136+
ENDIF.
156137

157-
" We finally have a certificate that can be used for the update, yay!
158-
TRY.
138+
" We finally have a certificate that can be used for the update, yay!
159139
" Root and intermediate certificates
160140
IF p_root = abap_true.
161141

src/zcl_strust2_cert_api.clas.abap

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
CLASS zcl_strust2_cert_api DEFINITION
2+
PUBLIC
3+
FINAL
4+
CREATE PUBLIC.
5+
6+
************************************************************************
7+
* Trust Management: Certificate API
8+
*
9+
* Get peer, intermediate, and root certificates for a domain from
10+
* https://tools.abappm.com
11+
*
12+
* Copyright 2025 apm.to Inc. <https://apm.to>
13+
* SPDX-License-Identifier: MIT
14+
************************************************************************
15+
PUBLIC SECTION.
16+
17+
CLASS-METHODS get_certificates
18+
IMPORTING
19+
!domain TYPE string
20+
!ssl_id TYPE ssfapplssl DEFAULT 'ANONYM'
21+
!debug TYPE abap_bool DEFAULT abap_false
22+
RETURNING
23+
VALUE(result) TYPE string
24+
RAISING
25+
zcx_error.
26+
27+
PROTECTED SECTION.
28+
PRIVATE SECTION.
29+
30+
CONSTANTS:
31+
c_api_host TYPE string VALUE 'https://tools.abappm.com',
32+
c_api_endpoint TYPE string VALUE '/api/v1/certificates'.
33+
34+
CLASS-METHODS _client
35+
IMPORTING
36+
ssl_id TYPE ssfapplssl
37+
uri TYPE string
38+
RETURNING
39+
VALUE(result) TYPE REF TO if_http_client
40+
RAISING
41+
zcx_error.
42+
43+
CLASS-METHODS _response
44+
IMPORTING
45+
http_client TYPE REF TO if_http_client
46+
RETURNING
47+
VALUE(result) TYPE REF TO if_http_response
48+
RAISING
49+
zcx_error.
50+
51+
CLASS-METHODS _debug
52+
IMPORTING
53+
headers TYPE tihttpnvp
54+
cookies TYPE tihttpcki
55+
debug TYPE abap_bool.
56+
57+
ENDCLASS.
58+
59+
60+
61+
CLASS zcl_strust2_cert_api IMPLEMENTATION.
62+
63+
64+
METHOD get_certificates.
65+
66+
" We can't query wildcard domains so we try with "api" sub-domain
67+
" TODO: if this fails, loop over a couple other common sub-domains
68+
DATA(query) = replace( val = domain sub = '*' with = 'api' ).
69+
70+
query = cl_abap_dyn_prg=>escape_xss_url( query ).
71+
72+
DATA(http_client) = _client(
73+
ssl_id = ssl_id
74+
uri = |{ c_api_endpoint }?domain={ query }| ).
75+
76+
DATA(fetch_response) = _response( http_client ).
77+
78+
" --- Retrieve Certificates from Response ---
79+
80+
DATA(json_response) = fetch_response->get_cdata( ).
81+
82+
IF debug = abap_true.
83+
cl_abap_browser=>show_html( html_string = json_response ).
84+
ENDIF.
85+
86+
IF json_response IS INITIAL OR json_response(1) <> '{'.
87+
zcx_error=>raise( 'Invalid response (expected JSON):' && json_response ).
88+
ENDIF.
89+
90+
result = json_response.
91+
92+
ENDMETHOD.
93+
94+
95+
METHOD _client.
96+
97+
cl_http_client=>create_by_url(
98+
EXPORTING
99+
url = c_api_host
100+
ssl_id = ssl_id
101+
IMPORTING
102+
client = result
103+
EXCEPTIONS
104+
OTHERS = 99 ).
105+
IF sy-subrc <> 0.
106+
zcx_error=>raise_t100( ).
107+
ENDIF.
108+
109+
result->request->set_header_field(
110+
name = '~request_uri'
111+
value = uri ).
112+
113+
result->request->set_header_field(
114+
name = 'accept'
115+
value = 'application/json' ).
116+
117+
ENDMETHOD.
118+
119+
120+
METHOD _debug.
121+
122+
CHECK debug = abap_true.
123+
124+
DATA(html) = `<h2>Headers:</h2>\n`.
125+
LOOP AT headers ASSIGNING FIELD-SYMBOL(<header>).
126+
html = html && |<p>{ <header>-name }: { <header>-value }</p>\n|.
127+
ENDLOOP.
128+
129+
html = html && `<h2>Cookies:</h2>\n`.
130+
LOOP AT cookies ASSIGNING FIELD-SYMBOL(<cookie>).
131+
html = html && |<p>{ <cookie>-name }: { <cookie>-value }</p>\n|.
132+
ENDLOOP.
133+
134+
cl_abap_browser=>show_html( html_string = html ).
135+
136+
ENDMETHOD.
137+
138+
139+
METHOD _response.
140+
141+
DATA:
142+
status_code TYPE sy-subrc,
143+
message TYPE string.
144+
145+
http_client->propertytype_accept_cookie = if_http_client=>co_enabled.
146+
147+
http_client->request->set_version( if_http_entity=>co_protocol_version_1_1 ).
148+
149+
http_client->send(
150+
EXCEPTIONS
151+
http_communication_failure = 1
152+
http_invalid_state = 2
153+
http_processing_failed = 3
154+
http_invalid_timeout = 4
155+
OTHERS = 5 ).
156+
IF sy-subrc = 0.
157+
http_client->receive(
158+
EXCEPTIONS
159+
http_communication_failure = 1
160+
http_invalid_state = 2
161+
http_processing_failed = 3
162+
OTHERS = 4 ).
163+
ENDIF.
164+
165+
IF sy-subrc <> 0.
166+
http_client->get_last_error(
167+
IMPORTING
168+
code = status_code
169+
message = message ).
170+
zcx_error=>raise( |{ message } (HTTP/{ status_code })| ).
171+
ENDIF.
172+
173+
result = http_client->response.
174+
175+
ENDMETHOD.
176+
ENDCLASS.

src/zcl_strust2_cert_api.clas.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<abapGit version="v1.0.0" serializer="LCL_OBJECT_CLAS" serializer_version="v1.0.0">
3+
<asx:abap xmlns:asx="http://www.sap.com/abapxml" version="1.0">
4+
<asx:values>
5+
<VSEOCLASS>
6+
<CLSNAME>ZCL_STRUST2_CERT_API</CLSNAME>
7+
<LANGU>E</LANGU>
8+
<DESCRIPT>Trust Management: Certificate API</DESCRIPT>
9+
<STATE>1</STATE>
10+
<CLSCCINCL>X</CLSCCINCL>
11+
<FIXPT>X</FIXPT>
12+
<UNICODE>X</UNICODE>
13+
</VSEOCLASS>
14+
</asx:values>
15+
</asx:abap>
16+
</abapGit>

0 commit comments

Comments
 (0)