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}