code wiki / _hdl_build / nx_access_lib.nx
nx_access_lib.nx source
↩ module page · 101 lines · 5534 B
1// nx_access_lib.nx -- the S-class authorization CORE. ONE rule, reusable on every site: a request is
2// allowed iff the user's GRANTED level >= the area's REQUIRED level, deny-by-default. Composes the
3// area->required-level resolver (nx_site_lock_lib / slk_resolve) with a user->granted-level resolver
4// (a roles registry). The owner (level 3) clears everything incl. exclusive areas; a family member (1)
5// can NEVER reach an owner area; no session (0) reaches nothing gated. No privilege escalation possible.
6// roles registry line: handle <TAB> level (e.g. elderwesto<TAB>3 ; aunt_jane<TAB>1)
7// Sovereign: nx_site_lock_lib only. license_tier: ORIGINAL
8import "nx_site_lock_lib.nx"
9const K_MAGIC_262144: i64 = 262144
10const K_MAGIC_32768: i64 = 32768
11
12// granted level for `handle` from the roles registry; 0 if absent (DENY-BY-DEFAULT).
13func ag_resolve_level(reg: *u8, len: i64, handle: *u8, hlen: i64) -> i64 {
14 let fs: *i64 = sys_mmap(8); let fe: *i64 = sys_mmap(8)
15 var ls: i64 = 0; var best: i64 = 0
16 while ls < len {
17 let le: i64 = slk_line_end(reg, len, ls)
18 if le > ls { if reg[ls] != (35 as u8) {
19 if slk_field(reg, ls, le, 0, fs, fe) == 1 {
20 let h0s: i64 = fs[0]; let h0e: i64 = fe[0]
21 if slk_eq(slk_at(reg, h0s), h0e - h0s, handle, hlen) == 1 {
22 if slk_field(reg, ls, le, 1, fs, fe) == 1 { best = slk_atoi(reg, fs[0], fe[0]) }
23 }
24 }
25 } }
26 ls = le + 1
27 }
28 return best
29}
30
31// THE DECISION. required = slk_resolve(site_locks, site, path); granted = ag_resolve_level(roles, handle).
32// returns 1=ALLOW, 0=DENY. fills out_req[0]/out_grant[0] (for honest 401/403 messaging) + out_realm.
33// public area (required 0) -> ALLOW (anyone, even no session)
34// gated area, granted >= required -> ALLOW
35// gated area, granted < required -> DENY (covers no-session=0 and under-privileged)
36func ag_allow(sl: *u8, sl_len: i64, roles: *u8, r_len: i64,
37 site: *u8, slen: i64, path: *u8, plen: i64, handle: *u8, hlen: i64,
38 out_req: *i64, out_grant: *i64, out_realm: *u8, realmcap: i64) -> i64 {
39 let lvlbox: *i64 = sys_mmap(8)
40 let locked: i64 = slk_resolve(sl, sl_len, site, slen, path, plen, out_realm, realmcap, lvlbox)
41 if locked == 0 { out_req[0] = 0; out_grant[0] = 0; return 1 } // public -> allow
42 let required: i64 = lvlbox[0]
43 var granted: i64 = 0
44 if hlen > 0 { granted = ag_resolve_level(roles, r_len, handle, hlen) }
45 out_req[0] = required; out_grant[0] = granted
46 if granted >= required { return 1 }
47 return 0
48}
49
50// ---- SHARED session-identity -> access-level path (the canonical uid-hash -> handle -> level chain). The mgmt API
51// AND the hub gateway compose THIS (one copy, not per-service duplicates) so every OPAQUE service grants access the
52// SAME way: a Modern-Auth session's uid hash -> the shared uid->handle index -> the roles registry -> level. ----
53func ag_hex(dst: *u8, off: i64, src: *u8, n: i64) -> i64 { let hx: *u8="0123456789abcdef" as *u8; var o: i64=off; var i: i64=0; while i<n { let c: i64=(src[i] as i64)&0xff; dst[o]=hx[(c>>4)&15]; dst[o+1]=hx[c&15]; o=o+2; i=i+1 } return o }
54// leak-free bounded read into a REUSED caller buffer; returns bytes (>=0) or -1.
55func ag_read_file(path: *u8, out: *u8, cap: i64) -> i64 {
56 let fd: i64 = sys_openat_rd(path)
57 if fd < 0 { return 0 - 1 }
58 var total: i64 = 0; var go: i64 = 1
59 while go == 1 {
60 let tail: *u8 = (out as i64 + total) as *u8
61 let nr: i64 = sys_read(fd, tail, cap - total)
62 if nr <= 0 { go = 0 }
63 if nr > 0 { total = total + nr }
64 if total >= cap { go = 0 }
65 }
66 sys_close(fd)
67 return total
68}
69// uidhex -> handle via the shared index TSV (uidhex<TAB>handle). returns handle length (0 = not found).
70func ag_idx_lookup(idxbuf: *u8, idxlen: i64, uidhex: *u8, uxn: i64, out_h: *u8, cap: i64) -> i64 {
71 let fs: *i64 = sys_mmap(8); let fe: *i64 = sys_mmap(8)
72 var ls: i64 = 0; var found: i64 = 0
73 while ls < idxlen {
74 let le: i64 = slk_line_end(idxbuf, idxlen, ls)
75 if found == 0 { if le > ls { if idxbuf[ls] != (35 as u8) {
76 if slk_field(idxbuf, ls, le, 0, fs, fe) == 1 {
77 if slk_eq(slk_at(idxbuf, fs[0]), fe[0] - fs[0], uidhex, uxn) == 1 {
78 if slk_field(idxbuf, ls, le, 1, fs, fe) == 1 {
79 var o: i64 = 0; let hl: i64 = fe[0] - fs[0]
80 while o < hl { if o < cap - 1 { out_h[o] = idxbuf[fs[0] + o] } o = o + 1 }
81 out_h[o] = 0 as u8; found = hl
82 }
83 }
84 }
85 } } }
86 ls = le + 1
87 }
88 return found
89}
90// THE canonical session-identity -> access level: uid HASH -> (index) handle -> (roles) level. 0 = deny-by-default
91// (uid not in the index, or handle absent from roles). The caller supplies the uid from nx_sa_validate_handle.
92func ag_uid_to_level(uid: *u8, uidn: i64, idx_path: *u8, roles_path: *u8) -> i64 {
93 let ux: *u8 = sys_mmap(160); let uxn: i64 = ag_hex(ux, 0, uid, uidn)
94 let idx: *u8 = sys_mmap(K_MAGIC_262144); let il: i64 = ag_read_file(idx_path, idx, K_MAGIC_262144)
95 if il <= 0 { return 0 }
96 let h: *u8 = sys_mmap(128); let hl: i64 = ag_idx_lookup(idx, il, ux, uxn, h, 128)
97 if hl <= 0 { return 0 }
98 let r: *u8 = sys_mmap(K_MAGIC_32768); var rl: i64 = ag_read_file(roles_path, r, K_MAGIC_32768)
99 if rl < 0 { rl = 0 }
100 return ag_resolve_level(r, rl, h, hl)
101}