# Grants Audit — ROA_WEB on MARIUSM_AUTO **Date**: 2026-04-11 **Auditor**: oracle-agent (team service-auto-sapt3) **Oracle target**: `10.0.20.121:1521/ROA` **Queries run as**: `CONTAFIN_ORACLE` --- ## 1. Findings ### 1.1. ROA_WEB user — DOES NOT EXIST | Source query | Result | |---|---| | `SELECT * FROM DBA_USERS WHERE USERNAME='ROA_WEB'` | 0 rows | | `SELECT * FROM DBA_TAB_PRIVS WHERE GRANTEE='ROA_WEB'` | 0 rows | | `SELECT * FROM DBA_SYS_PRIVS WHERE GRANTEE='ROA_WEB'` | 0 rows | | `SELECT * FROM DBA_ROLE_PRIVS WHERE GRANTEE='ROA_WEB'` | 0 rows | No Oracle user `ROA_WEB` exists on the `ROA` instance. All four privilege dictionaries are empty for this grantee. Clean slate — no cleanup needed before creation. ### 1.2. Why CONTAFIN_ORACLE "just works" on MARIUSM_AUTO ``` USER_ROLE_PRIVS for CONTAFIN_ORACLE: RESOURCE, CONNECT, IMP_FULL_DATABASE, DBA, EXP_FULL_DATABASE ``` `CONTAFIN_ORACLE` holds the `DBA` role, so it has `SELECT/INSERT/UPDATE/DELETE/EXECUTE ANY TABLE/PROCEDURE`. No explicit per-table grants are needed — that's why the Săpt 1 POC (`poc/hello_oracle.py`) and this audit script both queried MARIUSM_AUTO without any explicit grant work. **Implication for prototype (Săpt 3)**: we can connect as `CONTAFIN_ORACLE` directly, skip `ROA_WEB` entirely, and still validate hypotheses #1 (oracledb + OUT params) and #2 (SP creation + call). Only hypothesis #3 (grant-scoped access via SP, read-only SELECT rejected) **requires** the scoped `ROA_WEB` user and is therefore the only reason to ask DBA for the create. ### 1.3. Required target objects — exist | Object | Type | Status | |---|---|---| | `MARIUSM_AUTO.AUTO_VMASINICLIENTI` | VIEW | present | | `MARIUSM_AUTO.DEV_TIP_DEVIZ` | TABLE | present | | `MARIUSM_AUTO.SP_CREEAZA_COMANDA_PROTOTIP` | — | **not yet created** (task #3) | --- ## 2. Decision — Two-phase approach ### Phase A — Săpt 3 (this week): use CONTAFIN_ORACLE - No DBA request needed. - Prototype backend module `service_auto` connects via the existing `CONTAFIN_ORACLE` credentials (already in `backend/.env`). - Proves hypotheses #1 + #2 (sync-facade + PL/SQL OUT params) end-to-end. - `backend/.env` entry for MARIUSM_AUTO (task #5) points to existing CONTAFIN_ORACLE user; only schema owner changes (`MARIUSM_AUTO` instead of whichever the login uses for current companies). ### Phase B — Săpt 4+ (after prototype): create ROA_WEB and migrate Needed only to prove hypothesis #3 (grant-scoped access through SP while direct DML/SELECT is rejected). This requires DBA action. --- ## 3. Motivare securitate — de ce ROA_WEB cu SP-only **Oracle version** (confirmat 2026-04-12): `Oracle Database 21c Express Edition`. Schema-level grants (23ai+) nu sunt disponibile. Proxy auth (12c+) există dar anulează beneficiul de securitate — vezi §4. ### 3.1. Attack surface comparison | User conectare | SQL injection poate face | Attack surface | |---|---|---| | `CONTAFIN_ORACLE` (DBA) | SELECT/INSERT/DELETE pe **orice tabelă, orice schemă** | maxim | | `ROA_WEB` proxied ca schemă | SELECT/INSERT/DELETE pe **orice tabelă din schemă** | mediu | | `ROA_WEB` cu EXECUTE-only pe SP | doar apel SP cu parametri validați de SP | **minim** | `CONTAFIN_ORACLE` conectat din web = punct slab critic: SQL injection prin parametrii unui request HTTP poate exfiltra sau modifica date din orice schemă de pe server. `ROA_WEB` cu EXECUTE-only: atacatorul poate cel mult apela SP-urile cu parametri injectați. SP-urile Oracle sunt compilate, nu interpretate — parametrii sunt bind variables, nu SQL concatenat. Suprafața de atac e redusă la logica validată din SP. ### 3.2. De ce nu proxy authentication Proxy auth (`ALTER USER MARIUSM_AUTO GRANT CONNECT THROUGH ROA_WEB`) dă `ROA_WEB` identitatea completă a schemei proxied → SELECT/INSERT direct pe orice tabelă din acea schemă. Pierde exact beneficiul de securitate pentru care creăm `ROA_WEB`. --- ## 4. Arhitectura multi-tenant scalabilă (Oracle 21c) ### 4.1. Workflow firmă nouă Firmele noi se creează via `impdp` dintr-o schemă template menținută la zi: ```bash impdp system/... SCHEMAS=TEMPLATE_AUTO REMAP_SCHEMA=TEMPLATE_AUTO:FIRMA_NOUA ... ``` Schema nouă conține deja **toate** obiectele (SP-uri, view-uri, tabele) din template. Onboarding-ul ROA_WEB = 1 script rulat după impdp: ```sql -- onboarding_roa_web.sql — rulat ca CONTAFIN_ORACLE după impdp pentru fiecare firmă nouă -- Înlocuiește FIRMA_NOUA cu schema reală GRANT EXECUTE ON FIRMA_NOUA.SP_CREEAZA_COMANDA_PROTOTIP TO ROA_WEB; GRANT SELECT ON FIRMA_NOUA.AUTO_VMASINICLIENTI TO ROA_WEB; GRANT SELECT ON FIRMA_NOUA.DEV_TIP_DEVIZ TO ROA_WEB; GRANT SELECT ON FIRMA_NOUA.CALENDAR TO ROA_WEB; -- CALENDAR: period selector AppHeader (shared/routes/calendar.py) GRANT SELECT ON FIRMA_NOUA.DEV_ORDL TO ROA_WEB; -- DEV_ORDL: GET /api/service-auto/comenzi (list comenzi) GRANT SELECT ON FIRMA_NOUA.NOM_LUCRARI TO ROA_WEB; -- NOM_LUCRARI: JOIN cu DEV_ORDL pentru nrord (get_comenzi) -- adaugă orice alte SP/view-uri noi apărute de la ultimul onboarding ``` ### 4.2. Workflow SP/obiect nou Când un SP nou se adaugă în **toate** schemele (ca parte dintr-o migrare), scriptul de migrare are două componente: **Componenta 1** — `migration_YYYYMMDD_sp_noua.sql` — rulat per schemă (existent deja): ```sql CREATE OR REPLACE PROCEDURE MARIUSM_AUTO.SP_NOUA (...) AS ...; -- (repetat pentru fiecare schemă/firmă) ``` **Componenta 2** — `migration_YYYYMMDD_sp_noua_grants.sql` — rulat O SINGURĂ DATĂ, loopează automat toate schemele din `V_NOM_FIRME`: ```sql -- Rulat ca CONTAFIN_ORACLE după ce migration_1 a rulat pe toate schemele BEGIN FOR firm IN ( SELECT DISTINCT schema FROM contafin_oracle.v_nom_firme WHERE schema IS NOT NULL ) LOOP BEGIN EXECUTE IMMEDIATE 'GRANT EXECUTE ON ' || firm.schema || '.SP_NOUA TO ROA_WEB'; EXCEPTION WHEN OTHERS THEN NULL; -- skip dacă schema nu a primit încă migrarea SP (deployment order) END; END LOOP; END; / ``` `V_NOM_FIRME` e sursa de adevăr pentru toate schemele active — același tabel folosit de backend pentru login JWT. Orice firmă adăugată în `V_NOM_FIRME` e inclusă automat la **următoarea** migrare grants. ### 4.3. ROA_WEB creation script (one-time, DBA action) ```sql -- Rulat O SINGURĂ DATĂ ca SYS sau CONTAFIN_ORACLE CREATE USER ROA_WEB IDENTIFIED BY ""; GRANT CREATE SESSION TO ROA_WEB; -- Fără alte privilegii sistem. Accesul la date = exclusiv prin granturi per-obiect. ``` ### 4.4. Negative test — confirmă hypothesis #3 (scoped access) ```sql -- Conectat ca ROA_WEB (NU CONTAFIN_ORACLE): SELECT * FROM MARIUSM_AUTO.DEV_ORDL WHERE ROWNUM < 2; -- ORA-00942 așteptat INSERT INTO MARIUSM_AUTO.DEV_ORDL (id_ordl, id_lucrare) VALUES (1, 1); -- ORA-00942 așteptat -- SP call via bind vars — succes așteptat (ROLLBACK după verificare): BEGIN MARIUSM_AUTO.SP_CREEAZA_COMANDA_PROTOTIP( p_id_tip => 1, p_id_masiniclient => , p_id_firma => 110, p_solicitari => 'Test H3', p_id_ordl => :o_id_ordl, p_nrord => :o_nrord ); END; / ROLLBACK; ``` ### 4.5. Sumar scalabilitate | Eveniment | Acțiune ROA_WEB | Cost | |---|---|---| | ROA_WEB creat (o dată) | `CREATE USER` + `GRANT CREATE SESSION` | O dată | | Firmă nouă (`impdp`) | `onboarding_roa_web.sql` cu schema nouă | 1 script per firmă | | SP nou în toate schemele | `migration_YYYYMMDD_sp_noua_grants.sql` (loop V_NOM_FIRME) | 1 script per migrare | | View/tabelă nouă expusă | același pattern ca SP | 1 script per migrare | | Expunere `CALENDAR` pentru period selector | `GRANT SELECT {SCHEMA}.CALENDAR TO ROA_WEB` per schemă | 1 linie per schemă (parte din onboarding §4.1) | | Expunere `DEV_ORDL` + `NOM_LUCRARI` pentru GET /comenzi | `GRANT SELECT {SCHEMA}.DEV_ORDL/NOM_LUCRARI TO ROA_WEB` per schemă | 2 linii per schemă (parte din onboarding §4.1) | --- ## 5. Action items - [x] **Task #1** — Grants audit completat (2026-04-11) - [x] **Task #3** — `SP_CREEAZA_COMANDA_PROTOTIP` creat + testat cu rollback (2026-04-11) - [x] **Task #5** — `mariusm_test` entry în `backend/.env` cu CONTAFIN_ORACLE (2026-04-11) - [ ] **Săpt 4** (deferred, DBA action): 1. `CREATE USER ROA_WEB` + `GRANT CREATE SESSION` (§4.3) 2. Rulează `onboarding_roa_web.sql` pentru MARIUSM_AUTO (§4.1) 3. Switch `backend/secrets/mariusm_test.oracle_pass` + user → ROA_WEB în `.env` 4. Rulează `test_grants_integration.py` → 3 skipped trebuie să devină 3 passed 5. Rulează negative test din §4.4 manual (SQL Developer sau sqlplus)