code wiki / _hdl_build / nx_authz.nx
nx_authz.nx source
↩ module page · 72 lines · 3947 B
1// nx_authz.nx -- per-realm PERMISSION LEVELS (authorization), the layer ON TOP of OPAQUE authentication.
2// OPAQUE answers "who are you (valid session) + which realm"; THIS answers "what may THIS user SEE/DO" -- so
3// "all users" do NOT share the same access (operator 2026-06-17: nishifamily nsfw/private not shared by everyone;
4// andelinwest lawyers and clients on different permission levels). Model (data-driven, rule 11):
5// * each realm has a RESOURCE POLICY = parallel arrays (path-prefix, required_level)
6// * each user has a LEVEL within that realm (higher = more access)
7// * authz_level_allow = LONGEST-PREFIX matching rule's required_level <= user's level ? ALLOW : DENY,
8// DENY-BY-DEFAULT (an unlisted resource is denied). Realm-scoped: each realm supplies its OWN policy +
9// user tables, so a nishifamily level never grants an andelinwest resource.
10// Reuses nx_vault_acl (acl_is_prefix -- the same least-privilege longest-prefix primitive; no reinvention).
11// A daemon composes: nx_sa_validate (authn -> handle+realm) -> authz_level_of(handle) -> authz_level_allow(level,
12// resource). For finer per-user/per-record scoping (e.g. client A's case vs B's), use nx_vault_acl acl_eval with
13// a per-user policy; THIS organ is the hierarchical-level common case. license_tier: ORIGINAL
14import "nx_vault_acl.nx"
15import "nx_syscalls.nx"
16
17const AUTHZ_DENY: i64 = 0
18const AUTHZ_ALLOW: i64 = 1
19
20// ALLOW iff the LONGEST-PREFIX matching resource rule's required_level <= user_level; DENY-by-default.
21// paths/lens/req_levels = parallel arrays (paths[i] = *u8 cast to i64). A more specific (longer) rule overrides
22// a shorter one (so a public teaser under a private section can lower its own requirement).
23func authz_level_allow(user_level: i64, paths: *i64, lens: *i64, req_levels: *i64, nrules: i64, req: *u8, req_len: i64) -> i64 {
24 var best_len: i64 = 0 - 1
25 var best_req: i64 = 0
26 var i: i64 = 0
27 while i < nrules {
28 let rp: *u8 = paths[i] as *u8
29 if acl_is_prefix(rp, lens[i], req, req_len) == 1 {
30 if lens[i] > best_len { best_len = lens[i]; best_req = req_levels[i] }
31 }
32 i = i + 1
33 }
34 if best_len < 0 { return AUTHZ_DENY } // deny-by-default: an unlisted resource is denied
35 if user_level >= best_req { return AUTHZ_ALLOW }
36 return AUTHZ_DENY
37}
38
39// resolve a validated handle -> its level in this realm's user table. returns the level, or -1 if the user is
40// unknown (the caller then DENIES). u_handles[i] = *u8 cast to i64; u_hlens[i] = its length; u_levels[i] = level.
41func authz_level_of(handle: *u8, h_n: i64, u_handles: *i64, u_hlens: *i64, u_levels: *i64, n: i64) -> i64 {
42 var i: i64 = 0
43 while i < n {
44 if u_hlens[i] == h_n {
45 let uh: *u8 = u_handles[i] as *u8
46 var j: i64 = 0
47 var m: i64 = 1
48 while j < h_n { if (uh[j] as i64) != (handle[j] as i64) { m = 0; j = h_n } else { j = j + 1 } }
49 if m == 1 { return u_levels[i] }
50 }
51 i = i + 1
52 }
53 return 0 - 1
54}
55
56// LISTING FILTER (the "not even listed" property): copy into out[] ONLY the items the user (level) may view per
57// the policy; gated items (e.g. NSFW the user can't reach) are OMITTED entirely -- so a public/family listing
58// never even MENTIONS them (operator: "don't call out the nsfw till it's gated"). items[i]/item_lens[i] parallel;
59// out[i] = the surviving items[i] (the path ptr cast); returns the visible count.
60func authz_filter(user_level: i64, items: *i64, item_lens: *i64, n: i64, paths: *i64, lens: *i64, req_levels: *i64, npol: i64, out: *i64) -> i64 {
61 var cnt: i64 = 0
62 var i: i64 = 0
63 while i < n {
64 let it: *u8 = items[i] as *u8
65 if authz_level_allow(user_level, paths, lens, req_levels, npol, it, item_lens[i]) == AUTHZ_ALLOW {
66 out[cnt] = items[i]
67 cnt = cnt + 1
68 }
69 i = i + 1
70 }
71 return cnt
72}