11package authz
2- import future.keywords
2+
3+ import rego.v1
4+
35# A great playground: https://play.openpolicyagent.org/
46# Helpful cli commands to debug.
57# opa eval --format=pretty 'data.authz.allow' -d policy.rego -i input.json
@@ -29,67 +31,74 @@ import future.keywords
2931
3032# bool_flip lets you assign a value to an inverted bool.
3133# You cannot do 'x := !false', but you can do 'x := bool_flip(false)'
32- bool_flip (b) = flipped {
33- b
34- flipped = false
34+ bool_flip (b) : = flipped if {
35+ b
36+ flipped = false
3537}
3638
37- bool_flip (b) = flipped {
38- not b
39- flipped = true
39+ bool_flip (b) : = flipped if {
40+ not b
41+ flipped = true
4042}
4143
4244# number is a quick way to get a set of {true, false} and convert it to
4345# -1: {false, true} or {false}
4446# 0: {}
4547# 1: {true}
46- number (set) = c {
48+ number (set) : = c if {
4749 count (set) == 0
48- c := 0
50+ c := 0
4951}
5052
51- number (set) = c {
53+ number (set) : = c if {
5254 false in set
53- c := - 1
55+ c := - 1
5456}
5557
56- number (set) = c {
58+ number (set) : = c if {
5759 not false in set
5860 set[_]
59- c := 1
61+ c := 1
6062}
6163
6264# site, org, and user rules are all similar. Each rule should return a number
6365# from [-1, 1]. The number corresponds to "negative", "abstain", and "positive"
6466# for the given level. See the 'allow' rules for how these numbers are used.
65- default site = 0
67+ default site := 0
68+
6669site := site_allow (input.subject.roles)
70+
6771default scope_site := 0
72+
6873scope_site := site_allow ([input.subject.scope])
6974
70- site_allow (roles) := num {
75+ site_allow (roles) := num if {
7176 # allow is a set of boolean values without duplicates.
72- allow := { x |
77+ allow := {x |
7378 # Iterate over all site permissions in all roles
74- perm := roles[_].site[_]
75- perm.action in [input.action, " *" ]
79+ perm := roles[_].site[_]
80+ perm.action in [input.action, " *" ]
7681 perm.resource_type in [input.object.type, " *" ]
82+
7783 # x is either 'true' or 'false' if a matching permission exists.
78- x := bool_flip (perm.negate)
79- }
80- num := number (allow)
84+ x := bool_flip (perm.negate)
85+ }
86+ num := number (allow)
8187}
8288
8389# org_members is the list of organizations the actor is apart of.
84- org_members := { orgID |
90+ org_members := {orgID |
8591 input.subject.roles[_].org[orgID]
8692}
8793
8894# org is the same as 'site' except we need to iterate over each organization
8995# that the actor is a member of.
90- default org = 0
96+ default org := 0
97+
9198org := org_allow (input.subject.roles)
99+
92100default scope_org := 0
101+
93102scope_org := org_allow ([input.scope])
94103
95104# org_allow_set is a helper function that iterates over all orgs that the actor
@@ -102,10 +111,10 @@ scope_org := org_allow([input.scope])
102111# The reason we calculate this for all orgs, and not just the input.object.org_owner
103112# is that sometimes the input.object.org_owner is unknown. In those cases
104113# we have a list of org_ids that can we use in a SQL 'WHERE' clause.
105- org_allow_set (roles) := allow_set {
106- allow_set := { id: num |
114+ org_allow_set (roles) := allow_set if {
115+ allow_set := {id: num |
107116 id := org_members[_]
108- set := { x |
117+ set := {x |
109118 perm := roles[_].org[id][_]
110119 perm.action in [input.action, " *" ]
111120 perm.resource_type in [input.object.type, " *" ]
@@ -115,7 +124,7 @@ org_allow_set(roles) := allow_set {
115124 }
116125}
117126
118- org_allow (roles) := num {
127+ org_allow (roles) := num if {
119128 # If the object has "any_org" set to true, then use the other
120129 # org_allow block.
121130 not input.object.any_org
@@ -135,78 +144,82 @@ org_allow(roles) := num {
135144# This is useful for UI elements when we want to conclude, "Can the user create
136145# a new template in any organization?"
137146# It is easier than iterating over every organization the user is apart of.
138- org_allow (roles) := num {
147+ org_allow (roles) := num if {
139148 input.object.any_org # if this is false, this code block is not used
140149 allow := org_allow_set (roles)
141150
142-
143151 # allow is a map of {"<org_id>": <number>}. We only care about values
144152 # that are 1, and ignore the rest.
145153 num := number ([
146- keep |
147- # for every value in the mapping
148- value := allow[_]
149- # only keep values > 0.
150- # 1 = allow, 0 = abstain, -1 = deny
151- # We only need 1 explicit allow to allow the action.
152- # deny's and abstains are intentionally ignored.
153- value > 0
154- # result set is a set of [true,false,...]
155- # which "number()" will convert to a number.
156- keep := true
154+ keep |
155+ # for every value in the mapping
156+ value := allow[_]
157+
158+ # only keep values > 0.
159+ # 1 = allow, 0 = abstain, -1 = deny
160+ # We only need 1 explicit allow to allow the action.
161+ # deny's and abstains are intentionally ignored.
162+ value > 0
163+
164+ # result set is a set of [true,false,...]
165+ # which "number()" will convert to a number.
166+ keep := true
157167 ])
158168}
159169
160170# 'org_mem' is set to true if the user is an org member
161171# If 'any_org' is set to true, use the other block to determine org membership.
162- org_mem := true {
172+ org_mem if {
163173 not input.object.any_org
164174 input.object.org_owner != " "
165175 input.object.org_owner in org_members
166176}
167177
168- org_mem := true {
178+ org_mem if {
169179 input.object.any_org
170180 count (org_members) > 0
171181}
172182
173- org_ok {
183+ org_ok if {
174184 org_mem
175185}
176186
177187# If the object has no organization, then the user is also considered part of
178188# the non-existent org.
179- org_ok {
189+ org_ok if {
180190 input.object.org_owner == " "
181191 not input.object.any_org
182192}
183193
184194# User is the same as the site, except it only applies if the user owns the object and
185195# the user is apart of the org (if the object has an org).
186- default user = 0
196+ default user := 0
197+
187198user := user_allow (input.subject.roles)
199+
188200default user_scope := 0
201+
189202scope_user := user_allow ([input.scope])
190203
191- user_allow (roles) := num {
192- input.object.owner != " "
193- input.subject.id = input.object.owner
194- allow := { x |
195- perm := roles[_].user[_]
196- perm.action in [input.action, " *" ]
204+ user_allow (roles) := num if {
205+ input.object.owner != " "
206+ input.subject.id = input.object.owner
207+ allow := {x |
208+ perm := roles[_].user[_]
209+ perm.action in [input.action, " *" ]
197210 perm.resource_type in [input.object.type, " *" ]
198- x := bool_flip (perm.negate)
199- }
200- num := number (allow)
211+ x := bool_flip (perm.negate)
212+ }
213+ num := number (allow)
201214}
202215
203216# Scope allow_list is a list of resource IDs explicitly allowed by the scope.
204217# If the list is '*', then all resources are allowed.
205- scope_allow_list {
218+ scope_allow_list if {
206219 " *" in input.subject.scope.allow_list
207220}
208221
209- scope_allow_list {
222+ scope_allow_list if {
210223 # If the wildcard is listed in the allow_list, we do not care about the
211224 # object.id. This line is included to prevent partial compilations from
212225 # ever needing to include the object.id.
@@ -226,66 +239,70 @@ scope_allow_list {
226239# Allow query:
227240# data.authz.role_allow = true data.authz.scope_allow = true
228241
229- role_allow {
242+ role_allow if {
230243 site = 1
231244}
232245
233- role_allow {
246+ role_allow if {
234247 not site = - 1
235248 org = 1
236249}
237250
238- role_allow {
251+ role_allow if {
239252 not site = - 1
240253 not org = - 1
254+
241255 # If we are not a member of an org, and the object has an org, then we are
242256 # not authorized. This is an "implied -1" for not being in the org.
243257 org_ok
244258 user = 1
245259}
246260
247- scope_allow {
261+ scope_allow if {
248262 scope_allow_list
249263 scope_site = 1
250264}
251265
252- scope_allow {
266+ scope_allow if {
253267 scope_allow_list
254268 not scope_site = - 1
255269 scope_org = 1
256270}
257271
258- scope_allow {
272+ scope_allow if {
259273 scope_allow_list
260274 not scope_site = - 1
261275 not scope_org = - 1
276+
262277 # If we are not a member of an org, and the object has an org, then we are
263278 # not authorized. This is an "implied -1" for not being in the org.
264279 org_ok
265280 scope_user = 1
266281}
267282
268283# ACL for users
269- acl_allow {
284+ acl_allow if {
270285 # Should you have to be a member of the org too?
271286 perms := input.object.acl_user_list[input.subject.id]
287+
272288 # Either the input action or wildcard
273289 [input.action, " *" ][_] in perms
274290}
275291
276292# ACL for groups
277- acl_allow {
293+ acl_allow if {
278294 # If there is no organization owner, the object cannot be owned by an
279295 # org_scoped team.
280296 org_mem
281297 group := input.subject.groups[_]
282298 perms := input.object.acl_group_list[group]
299+
283300 # Either the input action or wildcard
284301 [input.action, " *" ][_] in perms
285302}
286303
287304# ACL for 'all_users' special group
288- acl_allow {
305+ acl_allow if {
289306 org_mem
290307 perms := input.object.acl_group_list[input.object.org_owner]
291308 [input.action, " *" ][_] in perms
@@ -296,13 +313,13 @@ acl_allow {
296313# The role or the ACL must allow the action. Scopes can be used to limit,
297314# so scope_allow must always be true.
298315
299- allow {
316+ allow if {
300317 role_allow
301318 scope_allow
302319}
303320
304321# ACL list must also have the scope_allow to pass
305- allow {
322+ allow if {
306323 acl_allow
307324 scope_allow
308325}
0 commit comments