@linkup-ai/abap-ai 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/abap/security-agent/README.md +30 -0
- package/dist/abap/security-agent/zcl_lkp_ai_guard.clas.abap +282 -0
- package/dist/abap/security-agent/zcl_lkp_ai_icf_handler.clas.abap +275 -0
- package/dist/abap/security-agent/zcx_lkp_ai_denied.clas.abap +50 -0
- package/dist/abap/security-agent/zlkp_ai_audit.tabl.asdic +19 -0
- package/dist/abap/security-agent/zlkp_ai_config.tabl.asdic +21 -0
- package/dist/abap/security-agent/zlkp_ai_setup.prog.abap +359 -0
- package/dist/adt-client.js +63 -3
- package/dist/cli/init.js +40 -6
- package/dist/cli/install-agent.js +290 -0
- package/dist/cli.js +20 -2
- package/dist/index.js +69 -32
- package/dist/license-guard.js +43 -1
- package/dist/sap-policy-loader.js +255 -0
- package/dist/security-policy.js +69 -20
- package/dist/tests/create-payload.test.js +213 -0
- package/dist/tests/security-policy.test.js +167 -0
- package/dist/tool-handler.js +80 -0
- package/dist/tools/activate.js +27 -4
- package/dist/tools/create.js +2 -0
- package/dist/tools/delete.js +12 -41
- package/dist/tools/execute.js +112 -0
- package/dist/tools/system-info.js +64 -25
- package/dist/tools/transports.js +39 -9
- package/package.json +11 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ABAP Security Agent — Objetos SAP
|
|
2
|
+
|
|
3
|
+
Objetos ABAP que compõem o ABAP Security Agent do LKPABAP.ia.
|
|
4
|
+
Instalados no SAP do cliente via `abap-ai init` ou manualmente.
|
|
5
|
+
|
|
6
|
+
## Objetos
|
|
7
|
+
|
|
8
|
+
| Objeto | Tipo | Descrição | Plano |
|
|
9
|
+
|--------|------|-----------|-------|
|
|
10
|
+
| `ZLKP_AI_CONFIG` | Tabela | Configuração por ambiente (SM30) | Enterprise+ |
|
|
11
|
+
| `ZLKP_AI_AUDIT` | Tabela | Audit log de operações (SE16N) | Enterprise+ |
|
|
12
|
+
| `ZCX_LKP_AI_DENIED` | Classe | Exception para tool bloqueada | Enterprise+ |
|
|
13
|
+
| `ZCL_LKP_AI_GUARD` | Classe | Lógica de validação e audit | Enterprise+ |
|
|
14
|
+
| `ZCL_LKP_AI_ICF_HANDLER` | Classe | Handler HTTP REST (SICF) | Corporate |
|
|
15
|
+
| `ZLKP_AUTH` | Auth Object | Autorização PFCG | Corporate |
|
|
16
|
+
|
|
17
|
+
## Ordem de instalação
|
|
18
|
+
|
|
19
|
+
1. `ZLKP_AI_CONFIG` (tabela)
|
|
20
|
+
2. `ZLKP_AI_AUDIT` (tabela)
|
|
21
|
+
3. `ZCX_LKP_AI_DENIED` (exception — depende de nada)
|
|
22
|
+
4. `ZCL_LKP_AI_GUARD` (depende das tabelas + exception)
|
|
23
|
+
5. `ZCL_LKP_AI_ICF_HANDLER` (depende do guard — só Corporate)
|
|
24
|
+
6. `ZLKP_AUTH` (auth object — só Corporate, criado via SU21)
|
|
25
|
+
|
|
26
|
+
## Integração por plano
|
|
27
|
+
|
|
28
|
+
- **Pro:** MCP server lê `ZLKP_AI_CONFIG` e insere em `ZLKP_AI_AUDIT` via SQL Console
|
|
29
|
+
- **Enterprise:** MCP server chama `ZCL_LKP_AI_GUARD` via ADT (leitura de policy + escrita de audit)
|
|
30
|
+
- **Corporate:** MCP server chama `/sap/z/lkpai/guard/check` (ICF handler com AUTHORITY-CHECK)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
CLASS zcl_lkp_ai_guard DEFINITION
|
|
2
|
+
PUBLIC
|
|
3
|
+
FINAL
|
|
4
|
+
CREATE PRIVATE.
|
|
5
|
+
|
|
6
|
+
PUBLIC SECTION.
|
|
7
|
+
CLASS-METHODS get_instance
|
|
8
|
+
RETURNING VALUE(ro_inst) TYPE REF TO zcl_lkp_ai_guard.
|
|
9
|
+
|
|
10
|
+
"! Verifica se a tool é permitida no ambiente atual.
|
|
11
|
+
"! @parameter iv_tool_name | Nome da tool MCP (ex: abap_delete)
|
|
12
|
+
"! @parameter iv_uname | Usuário SAP (default: sy-uname)
|
|
13
|
+
"! @parameter rv_allowed | abap_true se permitido
|
|
14
|
+
"! @raising zcx_lkp_ai_denied | Se a tool é bloqueada
|
|
15
|
+
METHODS check_permission
|
|
16
|
+
IMPORTING
|
|
17
|
+
iv_tool_name TYPE string
|
|
18
|
+
iv_uname TYPE uname DEFAULT sy-uname
|
|
19
|
+
RETURNING
|
|
20
|
+
VALUE(rv_allowed) TYPE abap_bool
|
|
21
|
+
RAISING
|
|
22
|
+
zcx_lkp_ai_denied.
|
|
23
|
+
|
|
24
|
+
"! Grava uma entrada no audit log (tabela ZLKP_AI_AUDIT).
|
|
25
|
+
"! @parameter iv_tool_name | Nome da tool MCP
|
|
26
|
+
"! @parameter iv_obj_type | Tipo do objeto ABAP (ex: PROG, CLAS)
|
|
27
|
+
"! @parameter iv_obj_name | Nome do objeto
|
|
28
|
+
"! @parameter iv_status | ALLOWED, DENIED ou ERROR
|
|
29
|
+
"! @parameter iv_message | Motivo do deny ou descrição do erro
|
|
30
|
+
METHODS write_audit
|
|
31
|
+
IMPORTING
|
|
32
|
+
iv_tool_name TYPE string
|
|
33
|
+
iv_obj_type TYPE string OPTIONAL
|
|
34
|
+
iv_obj_name TYPE string OPTIONAL
|
|
35
|
+
iv_status TYPE string
|
|
36
|
+
iv_message TYPE string OPTIONAL.
|
|
37
|
+
|
|
38
|
+
"! Retorna a policy do ambiente atual como JSON (para o MCP server ler).
|
|
39
|
+
"! @parameter rv_json | JSON com a policy completa
|
|
40
|
+
METHODS get_policy_json
|
|
41
|
+
RETURNING VALUE(rv_json) TYPE string.
|
|
42
|
+
|
|
43
|
+
PROTECTED SECTION.
|
|
44
|
+
PRIVATE SECTION.
|
|
45
|
+
CLASS-DATA go_instance TYPE REF TO zcl_lkp_ai_guard.
|
|
46
|
+
|
|
47
|
+
DATA mv_env_role TYPE char12.
|
|
48
|
+
DATA mv_current_tool TYPE char50.
|
|
49
|
+
|
|
50
|
+
METHODS load_env_role.
|
|
51
|
+
|
|
52
|
+
METHODS check_authority
|
|
53
|
+
IMPORTING iv_operation TYPE char12
|
|
54
|
+
RETURNING VALUE(rv_ok) TYPE abap_bool.
|
|
55
|
+
ENDCLASS.
|
|
56
|
+
|
|
57
|
+
CLASS zcl_lkp_ai_guard IMPLEMENTATION.
|
|
58
|
+
|
|
59
|
+
METHOD get_instance.
|
|
60
|
+
IF go_instance IS NOT BOUND.
|
|
61
|
+
go_instance = NEW #( ).
|
|
62
|
+
go_instance->load_env_role( ).
|
|
63
|
+
ENDIF.
|
|
64
|
+
ro_inst = go_instance.
|
|
65
|
+
ENDMETHOD.
|
|
66
|
+
|
|
67
|
+
METHOD load_env_role.
|
|
68
|
+
" Determinar o papel do ambiente baseado no client role
|
|
69
|
+
" T000-CCCATEGORY: C = Customizing, P = Production, T = Test, S = SAP Reference
|
|
70
|
+
DATA lv_category TYPE t000-cccategory.
|
|
71
|
+
|
|
72
|
+
SELECT SINGLE cccategory FROM t000
|
|
73
|
+
INTO @lv_category
|
|
74
|
+
WHERE mandt = @sy-mandt.
|
|
75
|
+
|
|
76
|
+
CASE lv_category.
|
|
77
|
+
WHEN 'C'.
|
|
78
|
+
mv_env_role = 'DEVELOPMENT'.
|
|
79
|
+
WHEN 'T'.
|
|
80
|
+
mv_env_role = 'QUALITY'.
|
|
81
|
+
WHEN 'P'.
|
|
82
|
+
mv_env_role = 'PRODUCTION'.
|
|
83
|
+
WHEN OTHERS.
|
|
84
|
+
" SAP reference ou desconhecido — tratar como DEV
|
|
85
|
+
mv_env_role = 'DEVELOPMENT'.
|
|
86
|
+
ENDCASE.
|
|
87
|
+
|
|
88
|
+
" Override: se existe entrada na config com env_role explícito, usa ela
|
|
89
|
+
SELECT SINGLE env_role FROM zlkp_ai_config
|
|
90
|
+
INTO @DATA(lv_override)
|
|
91
|
+
WHERE env_role <> @space
|
|
92
|
+
AND tool_name = '*'
|
|
93
|
+
AND user_group = '*'.
|
|
94
|
+
IF sy-subrc = 0 AND lv_override IS NOT INITIAL.
|
|
95
|
+
mv_env_role = lv_override.
|
|
96
|
+
ENDIF.
|
|
97
|
+
ENDMETHOD.
|
|
98
|
+
|
|
99
|
+
METHOD check_permission.
|
|
100
|
+
" 1. Verificar na tabela de configuração
|
|
101
|
+
" Prioridade de lookup:
|
|
102
|
+
" a) env_role + user (match exato por usuário)
|
|
103
|
+
" b) env_role + user_group (match por grupo SU01)
|
|
104
|
+
" c) env_role + '*' (wildcard — todos os usuários)
|
|
105
|
+
DATA lv_allowed TYPE abap_bool.
|
|
106
|
+
DATA lv_user_group TYPE zlkp_ai_config-user_group.
|
|
107
|
+
|
|
108
|
+
" Determinar user_group do usuário (campo CLASS em SU01)
|
|
109
|
+
SELECT SINGLE class FROM usr02
|
|
110
|
+
INTO @DATA(lv_usr_class)
|
|
111
|
+
WHERE bname = @iv_uname.
|
|
112
|
+
IF sy-subrc <> 0.
|
|
113
|
+
lv_usr_class = ''.
|
|
114
|
+
ENDIF.
|
|
115
|
+
|
|
116
|
+
" Lookup com prioridade: usuário > grupo > wildcard
|
|
117
|
+
DATA lv_found TYPE abap_bool VALUE abap_false.
|
|
118
|
+
|
|
119
|
+
" a) Match por usuário individual
|
|
120
|
+
SELECT SINGLE allowed FROM zlkp_ai_config
|
|
121
|
+
INTO @lv_allowed
|
|
122
|
+
WHERE env_role = @mv_env_role
|
|
123
|
+
AND user_group = @iv_uname
|
|
124
|
+
AND tool_name = @iv_tool_name.
|
|
125
|
+
IF sy-subrc = 0.
|
|
126
|
+
lv_found = abap_true.
|
|
127
|
+
ENDIF.
|
|
128
|
+
|
|
129
|
+
" b) Match por grupo de usuário (SU01 CLASS)
|
|
130
|
+
IF lv_found = abap_false AND lv_usr_class IS NOT INITIAL.
|
|
131
|
+
SELECT SINGLE allowed FROM zlkp_ai_config
|
|
132
|
+
INTO @lv_allowed
|
|
133
|
+
WHERE env_role = @mv_env_role
|
|
134
|
+
AND user_group = @lv_usr_class
|
|
135
|
+
AND tool_name = @iv_tool_name.
|
|
136
|
+
IF sy-subrc = 0.
|
|
137
|
+
lv_found = abap_true.
|
|
138
|
+
ENDIF.
|
|
139
|
+
ENDIF.
|
|
140
|
+
|
|
141
|
+
" c) Match por wildcard '*' (todos os usuários)
|
|
142
|
+
IF lv_found = abap_false.
|
|
143
|
+
SELECT SINGLE allowed FROM zlkp_ai_config
|
|
144
|
+
INTO @lv_allowed
|
|
145
|
+
WHERE env_role = @mv_env_role
|
|
146
|
+
AND user_group = '*'
|
|
147
|
+
AND tool_name = @iv_tool_name.
|
|
148
|
+
IF sy-subrc = 0.
|
|
149
|
+
lv_found = abap_true.
|
|
150
|
+
ENDIF.
|
|
151
|
+
ENDIF.
|
|
152
|
+
|
|
153
|
+
" d) Fallback: wildcard de tool '*' para o grupo/user
|
|
154
|
+
IF lv_found = abap_false.
|
|
155
|
+
SELECT SINGLE allowed FROM zlkp_ai_config
|
|
156
|
+
INTO @lv_allowed
|
|
157
|
+
WHERE env_role = @mv_env_role
|
|
158
|
+
AND user_group = '*'
|
|
159
|
+
AND tool_name = '*'.
|
|
160
|
+
IF sy-subrc = 0.
|
|
161
|
+
lv_found = abap_true.
|
|
162
|
+
ENDIF.
|
|
163
|
+
ENDIF.
|
|
164
|
+
|
|
165
|
+
" Avaliar resultado
|
|
166
|
+
IF lv_found = abap_true AND lv_allowed = abap_false.
|
|
167
|
+
write_audit(
|
|
168
|
+
iv_tool_name = iv_tool_name
|
|
169
|
+
iv_status = 'DENIED'
|
|
170
|
+
iv_message = |Tool { iv_tool_name } bloqueada em { mv_env_role } para { iv_uname }| ).
|
|
171
|
+
|
|
172
|
+
RAISE EXCEPTION TYPE zcx_lkp_ai_denied
|
|
173
|
+
EXPORTING
|
|
174
|
+
iv_tool = iv_tool_name
|
|
175
|
+
iv_env = CONV string( mv_env_role ).
|
|
176
|
+
ENDIF.
|
|
177
|
+
|
|
178
|
+
" 2. Verificar autorização SAP (se objeto ZLKP_AUTH existe)
|
|
179
|
+
DATA lv_operation TYPE char12.
|
|
180
|
+
|
|
181
|
+
" Mapear tool → operação
|
|
182
|
+
IF iv_tool_name CP 'abap_create*' OR iv_tool_name = 'abap_scaffold_rap'.
|
|
183
|
+
lv_operation = 'CREATE'.
|
|
184
|
+
ELSEIF iv_tool_name = 'abap_delete'.
|
|
185
|
+
lv_operation = 'DELETE'.
|
|
186
|
+
ELSEIF iv_tool_name CP 'abap_write*' OR iv_tool_name = 'abap_activate'
|
|
187
|
+
OR iv_tool_name CP 'abap_refactor*' OR iv_tool_name = 'abap_extract_method'.
|
|
188
|
+
lv_operation = 'WRITE'.
|
|
189
|
+
ELSE.
|
|
190
|
+
lv_operation = 'READ'.
|
|
191
|
+
ENDIF.
|
|
192
|
+
|
|
193
|
+
mv_current_tool = iv_tool_name.
|
|
194
|
+
IF check_authority( lv_operation ) = abap_false.
|
|
195
|
+
write_audit(
|
|
196
|
+
iv_tool_name = iv_tool_name
|
|
197
|
+
iv_status = 'DENIED'
|
|
198
|
+
iv_message = |Sem autorização ZLKP_AUTH para { lv_operation }/{ iv_tool_name }| ).
|
|
199
|
+
|
|
200
|
+
RAISE EXCEPTION TYPE zcx_lkp_ai_denied
|
|
201
|
+
EXPORTING
|
|
202
|
+
iv_tool = iv_tool_name
|
|
203
|
+
iv_env = CONV string( mv_env_role ).
|
|
204
|
+
ENDIF.
|
|
205
|
+
|
|
206
|
+
rv_allowed = abap_true.
|
|
207
|
+
ENDMETHOD.
|
|
208
|
+
|
|
209
|
+
METHOD check_authority.
|
|
210
|
+
" Mapear operação → ACTVT padrão SAP
|
|
211
|
+
DATA lv_actvt TYPE activ_auth.
|
|
212
|
+
CASE iv_operation.
|
|
213
|
+
WHEN 'READ'. lv_actvt = '03'.
|
|
214
|
+
WHEN 'WRITE'. lv_actvt = '02'.
|
|
215
|
+
WHEN 'CREATE'. lv_actvt = '01'.
|
|
216
|
+
WHEN 'DELETE'. lv_actvt = '06'.
|
|
217
|
+
WHEN OTHERS. lv_actvt = '03'.
|
|
218
|
+
ENDCASE.
|
|
219
|
+
|
|
220
|
+
AUTHORITY-CHECK OBJECT 'ZLKP_AUTH'
|
|
221
|
+
ID 'ACTVT' FIELD lv_actvt
|
|
222
|
+
ID 'LKPAI_OP' FIELD iv_operation
|
|
223
|
+
ID 'LKPAI_TL' FIELD mv_current_tool.
|
|
224
|
+
|
|
225
|
+
rv_ok = COND #( WHEN sy-subrc = 0 THEN abap_true ELSE abap_false ).
|
|
226
|
+
ENDMETHOD.
|
|
227
|
+
|
|
228
|
+
METHOD write_audit.
|
|
229
|
+
DATA ls_audit TYPE zlkp_ai_audit.
|
|
230
|
+
|
|
231
|
+
ls_audit-mandt = sy-mandt.
|
|
232
|
+
|
|
233
|
+
TRY.
|
|
234
|
+
ls_audit-log_guid = cl_system_uuid=>create_uuid_x16_static( ).
|
|
235
|
+
CATCH cx_uuid_error.
|
|
236
|
+
" Fallback: timestamp como ID
|
|
237
|
+
GET TIME STAMP FIELD DATA(lv_ts).
|
|
238
|
+
ls_audit-log_guid = lv_ts.
|
|
239
|
+
ENDTRY.
|
|
240
|
+
|
|
241
|
+
GET TIME STAMP FIELD ls_audit-timestamp.
|
|
242
|
+
ls_audit-sy_uname = sy-uname.
|
|
243
|
+
ls_audit-tool_name = iv_tool_name.
|
|
244
|
+
ls_audit-obj_type = iv_obj_type.
|
|
245
|
+
ls_audit-obj_name = iv_obj_name.
|
|
246
|
+
ls_audit-status = iv_status.
|
|
247
|
+
ls_audit-message = iv_message.
|
|
248
|
+
|
|
249
|
+
INSERT zlkp_ai_audit FROM ls_audit.
|
|
250
|
+
IF sy-subrc <> 0.
|
|
251
|
+
" Audit silencioso — não deve quebrar operações
|
|
252
|
+
ENDIF.
|
|
253
|
+
COMMIT WORK.
|
|
254
|
+
ENDMETHOD.
|
|
255
|
+
|
|
256
|
+
METHOD get_policy_json.
|
|
257
|
+
" Monta JSON com toda a policy do ambiente atual
|
|
258
|
+
DATA lt_config TYPE STANDARD TABLE OF zlkp_ai_config.
|
|
259
|
+
|
|
260
|
+
SELECT * FROM zlkp_ai_config
|
|
261
|
+
INTO TABLE @lt_config
|
|
262
|
+
WHERE env_role = @mv_env_role.
|
|
263
|
+
|
|
264
|
+
" Montar JSON manualmente (sem dependência de /UI2/CL_JSON)
|
|
265
|
+
DATA(lv_json) = |\{"env_role":"{ mv_env_role }","tools":[\n|.
|
|
266
|
+
DATA lv_first TYPE abap_bool VALUE abap_true.
|
|
267
|
+
|
|
268
|
+
LOOP AT lt_config INTO DATA(ls_cfg).
|
|
269
|
+
IF lv_first = abap_false.
|
|
270
|
+
lv_json = lv_json && |,\n|.
|
|
271
|
+
ENDIF.
|
|
272
|
+
lv_json = lv_json && |\{"tool":"{ ls_cfg-tool_name }","allowed":{ COND #(
|
|
273
|
+
WHEN ls_cfg-allowed = abap_true THEN 'true' ELSE 'false'
|
|
274
|
+
) },"max_rows":{ ls_cfg-max_rows }\}|.
|
|
275
|
+
lv_first = abap_false.
|
|
276
|
+
ENDLOOP.
|
|
277
|
+
|
|
278
|
+
lv_json = lv_json && |]\}|.
|
|
279
|
+
rv_json = lv_json.
|
|
280
|
+
ENDMETHOD.
|
|
281
|
+
|
|
282
|
+
ENDCLASS.
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
CLASS zcl_lkp_ai_icf_handler DEFINITION
|
|
2
|
+
PUBLIC
|
|
3
|
+
FINAL
|
|
4
|
+
CREATE PUBLIC.
|
|
5
|
+
|
|
6
|
+
PUBLIC SECTION.
|
|
7
|
+
INTERFACES if_http_extension.
|
|
8
|
+
|
|
9
|
+
PROTECTED SECTION.
|
|
10
|
+
PRIVATE SECTION.
|
|
11
|
+
METHODS handle_check
|
|
12
|
+
IMPORTING io_request TYPE REF TO if_http_request
|
|
13
|
+
io_response TYPE REF TO if_http_response.
|
|
14
|
+
|
|
15
|
+
METHODS handle_policy
|
|
16
|
+
IMPORTING io_request TYPE REF TO if_http_request
|
|
17
|
+
io_response TYPE REF TO if_http_response.
|
|
18
|
+
|
|
19
|
+
METHODS handle_exec
|
|
20
|
+
IMPORTING io_request TYPE REF TO if_http_request
|
|
21
|
+
io_response TYPE REF TO if_http_response.
|
|
22
|
+
|
|
23
|
+
METHODS handle_audit
|
|
24
|
+
IMPORTING io_request TYPE REF TO if_http_request
|
|
25
|
+
io_response TYPE REF TO if_http_response.
|
|
26
|
+
|
|
27
|
+
METHODS set_json_response
|
|
28
|
+
IMPORTING io_response TYPE REF TO if_http_response
|
|
29
|
+
iv_status TYPE i
|
|
30
|
+
iv_json TYPE string.
|
|
31
|
+
|
|
32
|
+
METHODS extract_json_field
|
|
33
|
+
IMPORTING iv_json TYPE string
|
|
34
|
+
iv_field TYPE string
|
|
35
|
+
RETURNING VALUE(rv_value) TYPE string.
|
|
36
|
+
ENDCLASS.
|
|
37
|
+
|
|
38
|
+
CLASS zcl_lkp_ai_icf_handler IMPLEMENTATION.
|
|
39
|
+
|
|
40
|
+
METHOD if_http_extension~handle_request.
|
|
41
|
+
DATA(lv_path) = server->request->get_header_field( '~path_info' ).
|
|
42
|
+
DATA(lv_method) = server->request->get_method( ).
|
|
43
|
+
|
|
44
|
+
CASE lv_path.
|
|
45
|
+
WHEN '/check'.
|
|
46
|
+
IF lv_method = 'POST'.
|
|
47
|
+
handle_check( io_request = server->request io_response = server->response ).
|
|
48
|
+
ELSE.
|
|
49
|
+
set_json_response( io_response = server->response iv_status = 405
|
|
50
|
+
iv_json = '{"error":"Method not allowed. Use POST."}' ).
|
|
51
|
+
ENDIF.
|
|
52
|
+
|
|
53
|
+
WHEN '/policy'.
|
|
54
|
+
IF lv_method = 'GET'.
|
|
55
|
+
handle_policy( io_request = server->request io_response = server->response ).
|
|
56
|
+
ELSE.
|
|
57
|
+
set_json_response( io_response = server->response iv_status = 405
|
|
58
|
+
iv_json = '{"error":"Method not allowed. Use GET."}' ).
|
|
59
|
+
ENDIF.
|
|
60
|
+
|
|
61
|
+
WHEN '/exec'.
|
|
62
|
+
IF lv_method = 'POST'.
|
|
63
|
+
handle_exec( io_request = server->request io_response = server->response ).
|
|
64
|
+
ELSE.
|
|
65
|
+
set_json_response( io_response = server->response iv_status = 405
|
|
66
|
+
iv_json = '{"error":"Method not allowed. Use POST."}' ).
|
|
67
|
+
ENDIF.
|
|
68
|
+
|
|
69
|
+
WHEN '/audit'.
|
|
70
|
+
IF lv_method = 'POST'.
|
|
71
|
+
handle_audit( io_request = server->request io_response = server->response ).
|
|
72
|
+
ELSE.
|
|
73
|
+
set_json_response( io_response = server->response iv_status = 405
|
|
74
|
+
iv_json = '{"error":"Method not allowed. Use POST."}' ).
|
|
75
|
+
ENDIF.
|
|
76
|
+
|
|
77
|
+
WHEN OTHERS.
|
|
78
|
+
set_json_response( io_response = server->response iv_status = 404
|
|
79
|
+
iv_json = '{"error":"Endpoints: /check (POST), /policy (GET), /exec (POST), /audit (POST)"}' ).
|
|
80
|
+
ENDCASE.
|
|
81
|
+
ENDMETHOD.
|
|
82
|
+
|
|
83
|
+
METHOD handle_check.
|
|
84
|
+
DATA(lv_body) = io_request->get_cdata( ).
|
|
85
|
+
|
|
86
|
+
DATA(lv_tool) = extract_json_field( iv_json = lv_body iv_field = 'tool' ).
|
|
87
|
+
DATA(lv_obj_type) = extract_json_field( iv_json = lv_body iv_field = 'obj_type' ).
|
|
88
|
+
DATA(lv_obj_name) = extract_json_field( iv_json = lv_body iv_field = 'obj_name' ).
|
|
89
|
+
|
|
90
|
+
IF lv_tool IS INITIAL.
|
|
91
|
+
set_json_response( io_response = io_response iv_status = 400
|
|
92
|
+
iv_json = '{"error":"Campo tool é obrigatório."}' ).
|
|
93
|
+
RETURN.
|
|
94
|
+
ENDIF.
|
|
95
|
+
|
|
96
|
+
DATA(lo_guard) = zcl_lkp_ai_guard=>get_instance( ).
|
|
97
|
+
|
|
98
|
+
TRY.
|
|
99
|
+
lo_guard->check_permission( iv_tool_name = lv_tool ).
|
|
100
|
+
|
|
101
|
+
lo_guard->write_audit(
|
|
102
|
+
iv_tool_name = lv_tool
|
|
103
|
+
iv_obj_type = lv_obj_type
|
|
104
|
+
iv_obj_name = lv_obj_name
|
|
105
|
+
iv_status = 'ALLOWED' ).
|
|
106
|
+
|
|
107
|
+
DATA(lv_policy_json) = lo_guard->get_policy_json( ).
|
|
108
|
+
set_json_response( io_response = io_response iv_status = 200
|
|
109
|
+
iv_json = |\{"allowed":true,"policy":{ lv_policy_json }\}| ).
|
|
110
|
+
|
|
111
|
+
CATCH zcx_lkp_ai_denied INTO DATA(lx_denied).
|
|
112
|
+
set_json_response( io_response = io_response iv_status = 403
|
|
113
|
+
iv_json = |\{"allowed":false,"reason":"Tool { lv_tool } bloqueada em { lx_denied->mv_env }"\}| ).
|
|
114
|
+
ENDTRY.
|
|
115
|
+
ENDMETHOD.
|
|
116
|
+
|
|
117
|
+
METHOD handle_policy.
|
|
118
|
+
DATA(lo_guard) = zcl_lkp_ai_guard=>get_instance( ).
|
|
119
|
+
DATA(lv_json) = lo_guard->get_policy_json( ).
|
|
120
|
+
set_json_response( io_response = io_response iv_status = 200 iv_json = lv_json ).
|
|
121
|
+
ENDMETHOD.
|
|
122
|
+
|
|
123
|
+
METHOD handle_exec.
|
|
124
|
+
"! Executa um programa ABAP e retorna o output (list).
|
|
125
|
+
"! POST /exec { "program": "ZLKP_AI_SETUP", "variant": "" }
|
|
126
|
+
"!
|
|
127
|
+
"! Segurança: verifica autorização S_PROGRAM antes de executar.
|
|
128
|
+
"! Usa SUBMIT ... EXPORTING LIST TO MEMORY para capturar output.
|
|
129
|
+
DATA(lv_body) = io_request->get_cdata( ).
|
|
130
|
+
DATA lv_program TYPE syrepid.
|
|
131
|
+
DATA lv_variant TYPE variant.
|
|
132
|
+
lv_program = extract_json_field( iv_json = lv_body iv_field = 'program' ).
|
|
133
|
+
lv_variant = extract_json_field( iv_json = lv_body iv_field = 'variant' ).
|
|
134
|
+
|
|
135
|
+
IF lv_program IS INITIAL.
|
|
136
|
+
set_json_response( io_response = io_response iv_status = 400
|
|
137
|
+
iv_json = '{"error":"Campo program é obrigatório."}' ).
|
|
138
|
+
RETURN.
|
|
139
|
+
ENDIF.
|
|
140
|
+
|
|
141
|
+
" Normalizar
|
|
142
|
+
TRANSLATE lv_program TO UPPER CASE.
|
|
143
|
+
|
|
144
|
+
" Segurança: só permitir programas Z* ou Y* (namespace cliente)
|
|
145
|
+
IF lv_program(1) <> 'Z' AND lv_program(1) <> 'Y'.
|
|
146
|
+
set_json_response( io_response = io_response iv_status = 403
|
|
147
|
+
iv_json = |\{"error":"Apenas programas Z* ou Y* podem ser executados via LKPABAP.ia."\}| ).
|
|
148
|
+
RETURN.
|
|
149
|
+
ENDIF.
|
|
150
|
+
|
|
151
|
+
" Verificar se programa existe
|
|
152
|
+
SELECT SINGLE name FROM trdir INTO @DATA(lv_exists) WHERE name = @lv_program.
|
|
153
|
+
IF sy-subrc <> 0.
|
|
154
|
+
set_json_response( io_response = io_response iv_status = 404
|
|
155
|
+
iv_json = |\{"error":"Programa { lv_program } não encontrado."\}| ).
|
|
156
|
+
RETURN.
|
|
157
|
+
ENDIF.
|
|
158
|
+
|
|
159
|
+
" Verificar autorização S_PROGRAM
|
|
160
|
+
AUTHORITY-CHECK OBJECT 'S_PROGRAM'
|
|
161
|
+
ID 'P_GROUP' DUMMY
|
|
162
|
+
ID 'P_ACTION' FIELD 'SUBMIT'.
|
|
163
|
+
IF sy-subrc <> 0.
|
|
164
|
+
set_json_response( io_response = io_response iv_status = 403
|
|
165
|
+
iv_json = '{"error":"Sem autorização S_PROGRAM para executar programas."}' ).
|
|
166
|
+
RETURN.
|
|
167
|
+
ENDIF.
|
|
168
|
+
|
|
169
|
+
" Executar com captura de output
|
|
170
|
+
DATA lt_list TYPE TABLE OF abaplist.
|
|
171
|
+
DATA lv_output TYPE string.
|
|
172
|
+
|
|
173
|
+
TRY.
|
|
174
|
+
IF lv_variant IS NOT INITIAL.
|
|
175
|
+
SUBMIT (lv_program) USING SELECTION-SET lv_variant
|
|
176
|
+
EXPORTING LIST TO MEMORY AND RETURN.
|
|
177
|
+
ELSE.
|
|
178
|
+
SUBMIT (lv_program)
|
|
179
|
+
EXPORTING LIST TO MEMORY AND RETURN.
|
|
180
|
+
ENDIF.
|
|
181
|
+
|
|
182
|
+
" Ler output da memória
|
|
183
|
+
CALL FUNCTION 'LIST_FROM_MEMORY'
|
|
184
|
+
TABLES
|
|
185
|
+
listobject = lt_list
|
|
186
|
+
EXCEPTIONS
|
|
187
|
+
not_found = 1
|
|
188
|
+
OTHERS = 2.
|
|
189
|
+
|
|
190
|
+
IF sy-subrc = 0 AND lt_list IS NOT INITIAL.
|
|
191
|
+
" Converter list para texto
|
|
192
|
+
DATA lt_text TYPE TABLE OF char255.
|
|
193
|
+
CALL FUNCTION 'LIST_TO_ASCI'
|
|
194
|
+
TABLES
|
|
195
|
+
listasci = lt_text
|
|
196
|
+
listobject = lt_list
|
|
197
|
+
EXCEPTIONS
|
|
198
|
+
empty_list = 1
|
|
199
|
+
list_index_invalid = 2
|
|
200
|
+
OTHERS = 3.
|
|
201
|
+
|
|
202
|
+
LOOP AT lt_text INTO DATA(lv_line).
|
|
203
|
+
lv_output = lv_output && lv_line && cl_abap_char_utilities=>newline.
|
|
204
|
+
ENDLOOP.
|
|
205
|
+
ENDIF.
|
|
206
|
+
|
|
207
|
+
IF lv_output IS INITIAL.
|
|
208
|
+
lv_output = '(programa executado sem output)'.
|
|
209
|
+
ENDIF.
|
|
210
|
+
|
|
211
|
+
" Gravar no audit
|
|
212
|
+
DATA(lo_guard) = zcl_lkp_ai_guard=>get_instance( ).
|
|
213
|
+
lo_guard->write_audit(
|
|
214
|
+
iv_tool_name = 'abap_execute'
|
|
215
|
+
iv_obj_type = 'PROG'
|
|
216
|
+
iv_obj_name = CONV string( lv_program )
|
|
217
|
+
iv_status = 'ALLOWED' ).
|
|
218
|
+
|
|
219
|
+
" Escapar aspas duplas no output para JSON
|
|
220
|
+
REPLACE ALL OCCURRENCES OF '"' IN lv_output WITH '\"'.
|
|
221
|
+
REPLACE ALL OCCURRENCES OF cl_abap_char_utilities=>newline IN lv_output WITH '\n'.
|
|
222
|
+
|
|
223
|
+
set_json_response( io_response = io_response iv_status = 200
|
|
224
|
+
iv_json = |\{"success":true,"program":"{ lv_program }","output":"{ lv_output }"\}| ).
|
|
225
|
+
|
|
226
|
+
CATCH cx_root INTO DATA(lx_error).
|
|
227
|
+
set_json_response( io_response = io_response iv_status = 500
|
|
228
|
+
iv_json = |\{"error":"Erro ao executar { lv_program }: { lx_error->get_text( ) }"\}| ).
|
|
229
|
+
ENDTRY.
|
|
230
|
+
ENDMETHOD.
|
|
231
|
+
|
|
232
|
+
METHOD handle_audit.
|
|
233
|
+
"! Grava uma entrada de audit diretamente.
|
|
234
|
+
"! POST /audit { "tool":"abap_read", "obj_type":"PROG", "obj_name":"ZTEST", "status":"ALLOWED" }
|
|
235
|
+
DATA(lv_body) = io_request->get_cdata( ).
|
|
236
|
+
|
|
237
|
+
DATA(lv_tool) = extract_json_field( iv_json = lv_body iv_field = 'tool' ).
|
|
238
|
+
DATA(lv_type) = extract_json_field( iv_json = lv_body iv_field = 'obj_type' ).
|
|
239
|
+
DATA(lv_name) = extract_json_field( iv_json = lv_body iv_field = 'obj_name' ).
|
|
240
|
+
DATA(lv_status) = extract_json_field( iv_json = lv_body iv_field = 'status' ).
|
|
241
|
+
DATA(lv_message) = extract_json_field( iv_json = lv_body iv_field = 'message' ).
|
|
242
|
+
|
|
243
|
+
IF lv_tool IS INITIAL OR lv_status IS INITIAL.
|
|
244
|
+
set_json_response( io_response = io_response iv_status = 400
|
|
245
|
+
iv_json = '{"error":"Campos tool e status são obrigatórios."}' ).
|
|
246
|
+
RETURN.
|
|
247
|
+
ENDIF.
|
|
248
|
+
|
|
249
|
+
DATA(lo_guard) = zcl_lkp_ai_guard=>get_instance( ).
|
|
250
|
+
lo_guard->write_audit(
|
|
251
|
+
iv_tool_name = lv_tool
|
|
252
|
+
iv_obj_type = lv_type
|
|
253
|
+
iv_obj_name = lv_name
|
|
254
|
+
iv_status = lv_status
|
|
255
|
+
iv_message = lv_message ).
|
|
256
|
+
|
|
257
|
+
set_json_response( io_response = io_response iv_status = 200
|
|
258
|
+
iv_json = '{"success":true}' ).
|
|
259
|
+
ENDMETHOD.
|
|
260
|
+
|
|
261
|
+
METHOD set_json_response.
|
|
262
|
+
io_response->set_status( code = iv_status reason = '' ).
|
|
263
|
+
io_response->set_header_field( name = 'Content-Type' value = 'application/json; charset=utf-8' ).
|
|
264
|
+
io_response->set_cdata( iv_json ).
|
|
265
|
+
ENDMETHOD.
|
|
266
|
+
|
|
267
|
+
METHOD extract_json_field.
|
|
268
|
+
DATA(lv_pattern) = |"{ iv_field }":"([^"]*)"|.
|
|
269
|
+
FIND REGEX lv_pattern IN iv_json SUBMATCHES rv_value.
|
|
270
|
+
IF sy-subrc <> 0.
|
|
271
|
+
CLEAR rv_value.
|
|
272
|
+
ENDIF.
|
|
273
|
+
ENDMETHOD.
|
|
274
|
+
|
|
275
|
+
ENDCLASS.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
CLASS zcx_lkp_ai_denied DEFINITION
|
|
2
|
+
PUBLIC
|
|
3
|
+
INHERITING FROM cx_static_check
|
|
4
|
+
FINAL
|
|
5
|
+
CREATE PUBLIC.
|
|
6
|
+
|
|
7
|
+
PUBLIC SECTION.
|
|
8
|
+
INTERFACES if_t100_message.
|
|
9
|
+
|
|
10
|
+
CONSTANTS:
|
|
11
|
+
BEGIN OF tool_blocked,
|
|
12
|
+
msgid TYPE symsgid VALUE 'ZLKP_AI',
|
|
13
|
+
msgno TYPE symsgno VALUE '001',
|
|
14
|
+
attr1 TYPE scx_attrname VALUE 'MV_TOOL',
|
|
15
|
+
attr2 TYPE scx_attrname VALUE 'MV_ENV',
|
|
16
|
+
attr3 TYPE scx_attrname VALUE '',
|
|
17
|
+
attr4 TYPE scx_attrname VALUE '',
|
|
18
|
+
END OF tool_blocked.
|
|
19
|
+
|
|
20
|
+
DATA mv_tool TYPE string READ-ONLY.
|
|
21
|
+
DATA mv_env TYPE string READ-ONLY.
|
|
22
|
+
|
|
23
|
+
METHODS constructor
|
|
24
|
+
IMPORTING
|
|
25
|
+
textid LIKE if_t100_message=>t100key OPTIONAL
|
|
26
|
+
previous LIKE previous OPTIONAL
|
|
27
|
+
iv_tool TYPE string OPTIONAL
|
|
28
|
+
iv_env TYPE string OPTIONAL.
|
|
29
|
+
|
|
30
|
+
PROTECTED SECTION.
|
|
31
|
+
PRIVATE SECTION.
|
|
32
|
+
ENDCLASS.
|
|
33
|
+
|
|
34
|
+
CLASS zcx_lkp_ai_denied IMPLEMENTATION.
|
|
35
|
+
METHOD constructor.
|
|
36
|
+
CALL METHOD super->constructor
|
|
37
|
+
EXPORTING
|
|
38
|
+
previous = previous.
|
|
39
|
+
|
|
40
|
+
mv_tool = iv_tool.
|
|
41
|
+
mv_env = iv_env.
|
|
42
|
+
|
|
43
|
+
CLEAR me->textid.
|
|
44
|
+
IF textid IS INITIAL.
|
|
45
|
+
if_t100_message~t100key = tool_blocked.
|
|
46
|
+
ELSE.
|
|
47
|
+
if_t100_message~t100key = textid.
|
|
48
|
+
ENDIF.
|
|
49
|
+
ENDMETHOD.
|
|
50
|
+
ENDCLASS.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
@EndUserText.label : 'LKPABAP.ia: Audit Log de Operações'
|
|
2
|
+
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
|
3
|
+
@AbapCatalog.tableCategory : #TRANSPARENT
|
|
4
|
+
@AbapCatalog.deliveryClass : #L
|
|
5
|
+
@AbapCatalog.dataMaintenance : #RESTRICTED
|
|
6
|
+
define table zlkp_ai_audit {
|
|
7
|
+
|
|
8
|
+
key mandt : mandt not null;
|
|
9
|
+
key log_guid : sysuuid_x16 not null;
|
|
10
|
+
timestamp : timestampl;
|
|
11
|
+
sy_uname : syuname;
|
|
12
|
+
tool_name : abap.char(50);
|
|
13
|
+
obj_type : abap.char(10);
|
|
14
|
+
obj_name : abap.char(120);
|
|
15
|
+
risk_level : abap.char(12);
|
|
16
|
+
status : abap.char(10);
|
|
17
|
+
message : abap.char(255);
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
@EndUserText.label : 'LKPABAP.ia: Configuração por ambiente'
|
|
2
|
+
@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
|
|
3
|
+
@AbapCatalog.tableCategory : #TRANSPARENT
|
|
4
|
+
@AbapCatalog.deliveryClass : #C
|
|
5
|
+
@AbapCatalog.dataMaintenance : #ALLOWED
|
|
6
|
+
define table zlkp_ai_config {
|
|
7
|
+
|
|
8
|
+
key mandt : mandt not null;
|
|
9
|
+
key env_role : abap.char(12) not null;
|
|
10
|
+
key user_group : abap.char(30) not null;
|
|
11
|
+
key tool_name : abap.char(50) not null;
|
|
12
|
+
allowed : abap_boolean;
|
|
13
|
+
max_rows : abap.int4;
|
|
14
|
+
changed_by : syuname;
|
|
15
|
+
changed_at : timestampl;
|
|
16
|
+
|
|
17
|
+
// user_group: '*' = todos os usuários (default)
|
|
18
|
+
// 'CHARM_USERS' = grupo específico (mapeado via SU01 user group)
|
|
19
|
+
// 'DANDRADE' = usuário individual
|
|
20
|
+
|
|
21
|
+
}
|