package authz import future.keywords.in import future.keywords.every # A great playground: https://play.openpolicyagent.org/ # TODO: Add debug instructions to do in the cli. Running really short on time, the # playground is sufficient for now imo. In the future we can provide a tidy bash # script for running this against predefined input. # bool_flip lets you assign a value to an inverted bool. # You cannot do 'x := !false', but you can do 'x := bool_flip(false)' bool_flip(b) = flipped { b flipped = false } bool_flip(b) = flipped { not b flipped = true } # perms_grant returns a set of boolean values {true, false}. # True means a positive permission in the set, false is a negative permission. # It will only return `bool_flip(perm.negate)` for permissions that affect a given # resource_type, resource_id, and action. # The empty set is returned if no relevant permissions are found. perms_grant(permissions) = grants { # If there are no permissions, this value is the empty set {}. grants := { x | # All permissions ... perm := permissions[_] # Such that the permission action, type, and resource_id matches perm.action in [input.action, "*"] perm.resource_type in [input.object.type, "*"] perm.resource_id in [input.object.id, "*"] x := bool_flip(perm.negate) } } # Site & User are both very simple. We default both to the empty set '{}'. If no permissions are present, then the # result is the default value. default site = {} site = grant { # Boolean set for all site wide permissions. grant = { v | # Use set comprehension to remove duplicate values # For each role, grab the site permission. # Find the grants on this permission list. v = perms_grant(input.subject.roles[_].site)[_] } } default user = {} user = grant { # Only apply user permissions if the user owns the resource input.object.owner != "" input.object.owner == input.subject.id grant = { v | # For each role, grab the user permissions. # Find the grants on this permission list. v = perms_grant(input.subject.roles[_].user)[_] } } # Organizations are more complex. If the user has no roles that specifically indicate the org_id of the object, # then we want to block the action. This is because that means the user is not a member of the org. # A non-member cannot access any org resources. # org_member returns the set of permissions associated with a user if the user is a member of the # organization org_member = grant { input.object.org_owner != "" grant = { v | v = perms_grant(input.subject.roles[_].org[input.object.org_owner])[_] } } # If a user is not part of an organization, 'org_non_member' is set to true org_non_member { input.object.org_owner != "" # Identify if the user is in the org roles := input.subject.roles every role in roles { not role.org[input.object.org_owner] } } # org is two rules that equate to the following # if org_non_member { return {false} } # else { org_member } # # It is important both rules cannot be true, as the `org` rules cannot produce multiple outputs. default org = {} org = set { # We have to do !org_non_member because rego rules must evaluate to 'true' # to have a value set. # So we do "not not-org-member" which means "subject is in org" not org_non_member set = org_member } org = set { org_non_member set = {false} } # The allow block is quite simple. Any set with `false` cascades down in levels. # Authorization looks for any `allow` statement that is true. Multiple can be true! # Note that the absense of `allow` means "unauthorized". # An explicit `"allow": true` is required. # site allow allow { # No site wide deny not false in site # And all permissions are positive site[_] } # OR # org allow allow { # No site or org deny not false in site not false in org # And all permissions are positive org[_] } # OR # user allow allow { # No site, org, or user deny not false in site not false in org not false in user # And all permissions are positive user[_] }