code wiki / (root) / nx_crm_acl.nx

nx_crm_acl.nx source

↩ module page · 153 lines · 8196 B

1// nx_crm_acl.nx -- USERS + ROLES + RECORD PERMISSIONS (CRM census E7). The Salesforce-verified layered model, 2// sovereign + integer-deterministic + DENY-BY-DEFAULT: 3// 1. TENANT WALL first: cross-workspace access is ALWAYS denied -- even for admins (the andelinwest isolation law). 4// 2. OWNER: full access to own records. 5// 3. ADMIN (role 4): full access within the tenant. 6// 4. ORG-WIDE DEFAULT per record: private(0) | read(1) | write(2). 7// 5. ROLE HIERARCHY: the management chain ABOVE the owner gets READ on private records (v1 conservative; 8// Salesforce grants owner-like via hierarchy -- we start read-only, stated). 9// 6. EXPLICIT SHARES: (record, user, level) grants, level >= action required. 10// 7. FIELD-LEVEL SECURITY: sensitive fields (e.g. giving amounts) need a minimum role. 11// Pure logic + selftest gate (argless): a 14-check allow/deny matrix incl. the tenant-beats-admin proof. 12// Persistence rides the relate store next. license_tier: ORIGINAL expect_exit: 0 13import "nx_syscalls.nx" 14 15func slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 16func p(s: *u8) -> i64 { sys_write(1, s, slen(s)); return 0 } 17func pn(v: i64) -> i64 { 18 var m: i64 = v; if m < 0 { p("-" as *u8); m = 0 - m } 19 let t: *u8 = sys_mmap(24); var k: i64 = 0; if m == 0 { t[0] = 48 as u8; k = 1 } 20 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 } 21 let o: *u8 = sys_mmap(24); var i: i64 = 0; while i < k { o[i] = t[k-1-i]; i = i + 1 } sys_write(1, o, k); return 0 22} 23func seq(a: *u8, b: *u8) -> i64 { 24 var i: i64 = 0 25 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } 26 if b[i] != (0 as u8) { return 0 } 27 return 1 28} 29 30// ---- ctx bundle (>6-arg clobber gotcha -> bundle): 31// ctx[0]=user names *i64, [1]=role lvls *i64, [2]=manager names *i64, [3]=tenants *i64, [4]=nusers, 32// ctx[5]=share recs *i64, [6]=share users *i64, [7]=share lvls *i64, [8]=nshares 33func ac_ufind(un: *i64, n: i64, name: *u8) -> i64 { 34 var i: i64 = 0 35 while i < n { if seq(un[i] as *u8, name) == 1 { return i } i = i + 1 } 36 return 0 - 1 37} 38// is `user` strictly ABOVE `owner` in the management chain (bounded walk, no cycles trusted) 39func ac_above(un: *i64, um: *i64, n: i64, owner: *u8, user: *u8) -> i64 { 40 var cur: i64 = ac_ufind(un, n, owner) 41 var hops: i64 = 0 42 while hops < 8 { 43 if cur < 0 { return 0 } 44 let mgr: *u8 = um[cur] as *u8 45 if mgr[0] == (0 as u8) { return 0 } 46 if seq(mgr, user) == 1 { return 1 } 47 cur = ac_ufind(un, n, mgr) 48 hops = hops + 1 49 } 50 return 0 51} 52// record rec: [0]=name *u8, [1]=owner *u8, [2]=owd i64 (0 private/1 read/2 write), [3]=tenant *u8 53// action: 1=read 2=write. returns 1 ALLOW / 0 DENY. deny-by-default. 54func ac_can(ctx: *i64, user: *u8, action: i64, rec: *i64) -> i64 { 55 let un: *i64 = ctx[0] as *i64 56 let n: i64 = ctx[4] 57 let ui: i64 = ac_ufind(un, n, user) 58 if ui < 0 { return 0 } 59 let ut: *i64 = ctx[3] as *i64 60 // 1. the tenant wall beats EVERYTHING (even admin) 61 if seq(ut[ui] as *u8, rec[3] as *u8) == 0 { return 0 } 62 // 2. owner 63 if seq(user, rec[1] as *u8) == 1 { return 1 } 64 // 3. admin within tenant 65 let ur: *i64 = ctx[1] as *i64 66 if ur[ui] >= 4 { return 1 } 67 // 4. org-wide default 68 if rec[2] == 2 { return 1 } 69 if rec[2] == 1 { if action == 1 { return 1 } } 70 // 5. role hierarchy: chain above the owner reads private records 71 if action == 1 { if ac_above(un, ctx[2] as *i64, n, rec[1] as *u8, user) == 1 { return 1 } } 72 // 6. explicit shares 73 let sr: *i64 = ctx[5] as *i64 74 let su: *i64 = ctx[6] as *i64 75 let sl: *i64 = ctx[7] as *i64 76 let sn: i64 = ctx[8] 77 var i: i64 = 0 78 while i < sn { 79 if seq(sr[i] as *u8, rec[0] as *u8) == 1 { if seq(su[i] as *u8, user) == 1 { if sl[i] >= action { return 1 } } } 80 i = i + 1 81 } 82 // 7. deny by default 83 return 0 84} 85// field-level security: user's role must reach minrole 86func ac_field(ctx: *i64, user: *u8, minrole: i64) -> i64 { 87 let ui: i64 = ac_ufind(ctx[0] as *i64, ctx[4], user) 88 if ui < 0 { return 0 } 89 let ur: *i64 = ctx[1] as *i64 90 if ur[ui] >= minrole { return 1 } 91 return 0 92} 93 94func ac_check(label: *u8, got: i64, want: i64) -> i64 { 95 p(" ACL-ROLE " as *u8); p(label) 96 if got == 1 { p(" -> ALLOW" as *u8) } else { p(" -> DENY" as *u8) } 97 if got == want { p("\n" as *u8); return 1 } 98 p(" (WRONG)\n" as *u8) 99 return 0 100} 101 102func main() -> i64 { 103 p("=== NX-CRM-ACL SELFTEST (ACL-ROLE: tenant wall > owner > admin > OWD > hierarchy > shares; deny-by-default) ===\n" as *u8) 104 var ok: i64 = 1 105 let mt: *u8 = sys_mmap(4); mt[0] = 0 as u8 106 // users: name / role / manager / tenant (roles: 4 admin, 3 manager, 2 member) 107 let un: *i64 = sys_mmap(8 * 8) as *i64 108 let ur: *i64 = sys_mmap(8 * 8) as *i64 109 let um: *i64 = sys_mmap(8 * 8) as *i64 110 let ut: *i64 = sys_mmap(8 * 8) as *i64 111 un[0]="alice" as *u8 as i64; ur[0]=2; um[0]="maria" as *u8 as i64; ut[0]="andelinwest" as *u8 as i64 112 un[1]="bob" as *u8 as i64; ur[1]=2; um[1]="maria" as *u8 as i64; ut[1]="andelinwest" as *u8 as i64 113 un[2]="maria" as *u8 as i64; ur[2]=3; um[2]="dana" as *u8 as i64; ut[2]="andelinwest" as *u8 as i64 114 un[3]="dana" as *u8 as i64; ur[3]=4; um[3]=mt as i64; ut[3]="andelinwest" as *u8 as i64 115 un[4]="eve" as *u8 as i64; ur[4]=4; um[4]=mt as i64; ut[4]="firstward" as *u8 as i64 116 // shares (empty to start) 117 let sr: *i64 = sys_mmap(8 * 8) as *i64 118 let su: *i64 = sys_mmap(8 * 8) as *i64 119 let sl: *i64 = sys_mmap(8 * 8) as *i64 120 let ctx: *i64 = sys_mmap(8 * 12) as *i64 121 ctx[0]=un as i64; ctx[1]=ur as i64; ctx[2]=um as i64; ctx[3]=ut as i64; ctx[4]=5 122 ctx[5]=sr as i64; ctx[6]=su as i64; ctx[7]=sl as i64; ctx[8]=0 123 // records: name / owner / owd / tenant 124 let deal1: *i64 = sys_mmap(8 * 4) as *i64 125 deal1[0]="deal1" as *u8 as i64; deal1[1]="alice" as *u8 as i64; deal1[2]=0; deal1[3]="andelinwest" as *u8 as i64 126 let note1: *i64 = sys_mmap(8 * 4) as *i64 127 note1[0]="note1" as *u8 as i64; note1[1]="bob" as *u8 as i64; note1[2]=1; note1[3]="andelinwest" as *u8 as i64 128 let doc1: *i64 = sys_mmap(8 * 4) as *i64 129 doc1[0]="doc1" as *u8 as i64; doc1[1]="alice" as *u8 as i64; doc1[2]=2; doc1[3]="andelinwest" as *u8 as i64 130 131 if ac_check("alice write deal1 (owner)" as *u8, ac_can(ctx, "alice" as *u8, 2, deal1), 1) == 0 { ok = 0 } 132 if ac_check("bob read deal1 (peer, private)" as *u8, ac_can(ctx, "bob" as *u8, 1, deal1), 0) == 0 { ok = 0 } 133 if ac_check("maria read deal1 (manager chain)" as *u8, ac_can(ctx, "maria" as *u8, 1, deal1), 1) == 0 { ok = 0 } 134 if ac_check("maria write deal1 (hierarchy = read-only v1)" as *u8, ac_can(ctx, "maria" as *u8, 2, deal1), 0) == 0 { ok = 0 } 135 if ac_check("dana read deal1 (admin in tenant)" as *u8, ac_can(ctx, "dana" as *u8, 1, deal1), 1) == 0 { ok = 0 } 136 if ac_check("eve read deal1 (ADMIN of OTHER tenant -- wall beats admin)" as *u8, ac_can(ctx, "eve" as *u8, 1, deal1), 0) == 0 { ok = 0 } 137 if ac_check("bob read note1 (OWD read)" as *u8, ac_can(ctx, "bob" as *u8, 1, note1), 1) == 0 { ok = 0 } 138 if ac_check("alice write note1 (OWD read only)" as *u8, ac_can(ctx, "alice" as *u8, 2, note1), 0) == 0 { ok = 0 } 139 if ac_check("bob write doc1 (OWD write)" as *u8, ac_can(ctx, "bob" as *u8, 2, doc1), 1) == 0 { ok = 0 } 140 if ac_check("ghost read note1 (unknown user)" as *u8, ac_can(ctx, "ghost" as *u8, 1, note1), 0) == 0 { ok = 0 } 141 // explicit share flips bob's access to deal1 142 sr[0]="deal1" as *u8 as i64; su[0]="bob" as *u8 as i64; sl[0]=2; ctx[8]=1 143 if ac_check("bob write deal1 (explicit share lvl2)" as *u8, ac_can(ctx, "bob" as *u8, 2, deal1), 1) == 0 { ok = 0 } 144 // field-level security: giving amounts need manager+ 145 if ac_field(ctx, "bob" as *u8, 3) != 0 { ok = 0 } 146 if ac_field(ctx, "maria" as *u8, 3) != 1 { ok = 0 } 147 p(" ACL-ROLE field-level: giving-amount minrole=3 bob=DENY maria=ALLOW\n" as *u8) 148 149 p("NX-CRM-ACL-SELFTEST checks=13 " as *u8) 150 if ok == 1 { p("verdict=GREEN\n" as *u8); return 0 } 151 p("verdict=RED\n" as *u8) 152 return 1 153}