authz.bend
bend-kit-authz@0.1.0.0 · 193 lines · 20 definitions
1
2import Base
3
4
5
6type Request is Data:
7 Request{subject: Maybe<&2, String>, roles: Maybe<&2, List<&2, String>>, owner: Maybe<&2, String>, action: String, resource: String}
8
9
10type Tri is Data:
11 Yes{}
12 No{}
13 Unknown{}
14
15type Target is Data:
16 Everyone{}
17 Nobody{}
18 Action{name: String}
19 Resource{name: String}
20 Subject{name: String}
21 Role{name: String}
22 Owner{}
23 AllOf{a: Target, b: Target}
24
25
26type Decision is Data:
27 Allow{}
28 Deny{}
29 Abstain{}
30
31type Policy is Data:
32 Permit{target: Target}
33 Forbid{target: Target}
34 DenyOverrides{a: Policy, b: Policy}
35 Intersect{a: Policy, b: Policy}
36
37def tri(b: Bool) -> Tri:
38 match b:
39 case True{}:
40 Yes{}
41 case False{}:
42 No{}
43
44def has(+name: String, xs: List<&2, String>) -> Bool:
45 match xs:
46 case Nil{}:
47 False{}
48 case x <> rest:
49 String.eq(x, name) || has(name, rest)
50
51def subject.is(name: String, s: Maybe<&2, String>) -> Tri:
52 match s:
53 case None{}:
54 Unknown{}
55 case Some{x}:
56 tri(String.eq(x, name))
57
58def role.is(name: String, r: Maybe<&2, List<&2, String>>) -> Tri:
59 match r:
60 case None{}:
61 Unknown{}
62 case Some{xs}:
63 tri(has(name, xs))
64
65def owner.is(x: String, o: Maybe<&2, String>) -> Tri:
66 match o:
67 case None{}:
68 Unknown{}
69 case Some{y}:
70 tri(String.eq(x, y))
71
72def owner.of(s: Maybe<&2, String>, o: Maybe<&2, String>) -> Tri:
73 match s:
74 case None{}:
75 Unknown{}
76 case Some{x}:
77 owner.is(x, o)
78
79
80def all(a: Tri, b: Tri) -> Tri:
81 match a:
82 case No{}:
83 No{}
84 case Yes{}:
85 b
86 case Unknown{}:
87 match b:
88 case No{}:
89 No{}
90 case Yes{}:
91 Unknown{}
92 case Unknown{}:
93 Unknown{}
94
95def matches(t: Target, +req: Request) -> Tri:
96 match t:
97 case Everyone{}:
98 Yes{}
99 case Nobody{}:
100 No{}
101 case Action{name}:
102 Request{s, r, o, action, resource} = req
103 tri(String.eq(action, name))
104 case Resource{name}:
105 Request{s, r, o, action, resource} = req
106 tri(String.eq(resource, name))
107 case Subject{name}:
108 Request{s, r, o, action, resource} = req
109 subject.is(name, s)
110 case Role{name}:
111 Request{s, r, o, action, resource} = req
112 role.is(name, r)
113 case Owner{}:
114 Request{s, r, o, action, resource} = req
115 owner.of(s, o)
116 case AllOf{a, b}:
117 all(matches(a, req), matches(b, req))
118
119
120def permit(m: Tri) -> Decision:
121 match m:
122 case Yes{}:
123 Allow{}
124 case No{}:
125 Abstain{}
126 case Unknown{}:
127 Abstain{}
128
129
130def forbid(m: Tri) -> Decision:
131 match m:
132 case Yes{}:
133 Deny{}
134 case No{}:
135 Abstain{}
136 case Unknown{}:
137 Deny{}
138
139
140def deny_overrides(a: Decision, b: Decision) -> Decision:
141 match a:
142 case Deny{}:
143 Deny{}
144 case Abstain{}:
145 b
146 case Allow{}:
147 match b:
148 case Deny{}:
149 Deny{}
150 case Allow{}:
151 Allow{}
152 case Abstain{}:
153 Allow{}
154
155
156def intersect(a: Decision, b: Decision) -> Decision:
157 match a:
158 case Deny{}:
159 Deny{}
160 case Allow{}:
161 b
162 case Abstain{}:
163 match b:
164 case Deny{}:
165 Deny{}
166 case Allow{}:
167 Abstain{}
168 case Abstain{}:
169 Abstain{}
170
171def decide(p: Policy, +req: Request) -> Decision:
172 match p:
173 case Permit{t}:
174 permit(matches(t, req))
175 case Forbid{t}:
176 forbid(matches(t, req))
177 case DenyOverrides{a, b}:
178 deny_overrides(decide(a, req), decide(b, req))
179 case Intersect{a, b}:
180 intersect(decide(a, req), decide(b, req))
181
182def allowed(d: Decision) -> Bool:
183 match d:
184 case Allow{}:
185 True{}
186 case Deny{}:
187 False{}
188 case Abstain{}:
189 False{}
190
191
192def granted(p: Policy, +req: Request) -> Bool:
193 allowed(decide(p, req))