@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.
@@ -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
+ }