permissions.ry
module permissions
purpose: Decide whether a set of roles grants everything an action requires.
import std.console
public type Permission is Text where value.matches("^[a-z]+:[a-z_]+$")
purpose: A permission name of the form resource:action, such as orders:read.
public type Role
purpose: A named bundle of permissions.
has name: Text
has granted: Set of Permission
end
public function granted_by(roles: List of Role) returns Set of Permission
purpose: The union of everything the roles grant.
example: granted_by([]) is [].to_set()
let mutable granted: Set of Permission be [].to_set()
for each role in roles
change granted to granted.union(role.granted)
end
return granted
end
public function missing(required: Set of Permission, roles: List of Role) returns Set of Permission
purpose: The required permissions that none of the roles grants.
tags: authorization, sets
example: missing(
required: [Permission("orders:read"), Permission("orders:write")].to_set(),
roles: [Role(name: "viewer", granted: [Permission("orders:read")].to_set())]
) is [Permission("orders:write")].to_set()
return required.difference(granted_by(roles))
end
public function allowed(required: Set of Permission, roles: List of Role) returns Boolean
purpose: Whether the roles together grant every required permission.
see also: missing
return required.is_subset_of(granted_by(roles))
end
public function shared(first_role: Role, second_role: Role) returns Set of Permission
purpose: The permissions both roles grant.
return first_role.granted.intersection(second_role.granted)
end
public function main() needs console
purpose: Check a sample viewer and editor against the permissions that publishing needs.
let viewer be Role(name: "viewer", granted: [Permission("posts:read")].to_set())
let editor be Role(
name: "editor",
granted: [Permission("posts:read"), Permission("posts:write")].to_set()
)
let publishing be [
Permission("posts:read"),
Permission("posts:write"),
Permission("posts:publish")
].to_set()
if allowed(required: publishing, roles: [viewer, editor]) then
console.print("the editor may publish")
otherwise
for each permission in missing(required: publishing, roles: [viewer, editor]).sorted()
console.print("missing: {permission}")
end
end
let both be shared(first_role: viewer, second_role: editor).sorted().join(", ")
console.print("shared: {both}")
end