Permissions
Roles as sets of actions in a partial order; folders that grant roles and deny actions, inherited by every document below them with the reason for each action; and documents moved without their stored grants being refreshed, audited for grants too many and too few.
Every screen below was recorded under CPython. When this page was built, the EML interpreter replayed each session from the same input and printed the same bytes.
About
Roles as sets of actions, folders that grant roles to users and can deny actions, and documents that inherit from every folder above them. The program also shows what happens when a document is moved and its stored grants are not refreshed.
main.eml- the menu, the roles and how they compare, the folder tree, one user on one document, moves, writes and the auditperms.eml- the actions and roles, subset questions on lists, the order between roles, the folder chain, effective permissions, refresh and audit
How each part works:
- Roles: viewer (read), editor (read, write), auditor (read, export) and owner (all five actions). As in the corpus case
permission-set-algebra, one role is below another when every one of its actions is in the other. That is a partial order: editor and auditor each have an action the other lacks, so neither is below the other. - The program lists the roles directly above one another, and sorts the roles by "below" from two starting orders. Editor and auditor keep whichever order they came in, so the sort has no single answer.
- Effective permissions: the union of every role granted to the user on the document's folder and on every folder above it, minus every action denied there. A deny wins over any grant. Each allowed action names the nearest folder that gives it.
- Stored grants, as in the corpus case
inherited-permission-after-move: each document keeps what its folder gave each user when it was last written. A move changes the folder, not the document, so nothing is refreshed. The audit lists every grant where the stored answer and the folder's answer differ, too much or too little. Writing a document refreshes its grants.
What is checked: menu choices 1 to 7; one of the five users; one of the documents; one of the four folders; a move to a different folder. An empty answer cancels.
Sessions: sessions/basic.in gives: - the roles, their order, and the two sorts; - the folder tree; - ben on contract.pdf (denied in legal), cy (auditor), dee on plan.txt, and guest on plan.txt (nothing); - the audit with nothing out of date; - contract.pdf moved to public and brochure.pdf to legal, the tree again, guest on both, and the audit: 3 grants too many and 3 too few; - contract.pdf written, and the audit with its entries gone.
sessions/bad-input.in gives: - menu choices 0 and x; - a cancel, an unknown user, a cancel and an unknown document; - moves: a cancel, an unknown document, a cancel, an unknown folder, and a move to the folder it is in; - writes: a cancel and an unknown document.
Built on the verified corpus cases permission-set-algebra (for sets, "less than" means subset, and the order is partial) and inherited-permission-after-move (the grants stored on a document are refreshed on a write, and a move is not a write).
Recorded sessions
What the screen shows while someone uses the program. Each typed line appears after its prompt, the way a terminal shows it.
bad-input
interpreter: byte-equal== Permissions ==
Roles as sets of actions, folders that grant roles and deny actions, and documents that inherit from every folder above them.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 0
Pick a number from 1 to 7.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> x
Pick a number from 1 to 7.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)>
Cancelled.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> zed
Type ana, ben, cy, dee or guest.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> ana
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)>
Cancelled.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> ana
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> x.doc
Pick one of the documents listed.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document>
Cancelled.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document> x.doc
Pick one of the documents listed.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document> plan.txt
to folder (company, team, legal or public)>
Cancelled.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document> plan.txt
to folder (company, team, legal or public)> home
Type company, team, legal or public.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document> plan.txt
to folder (company, team, legal or public)> team
plan.txt is already in team.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 5
document>
Cancelled.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 5
document> x.doc
Pick one of the documents listed.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 7
Bye.
What was typed (30 lines)
0
x
3
3
zed
3
ana
3
ana
x.doc
4
4
x.doc
4
plan.txt
4
plan.txt
home
4
plan.txt
team
5
5
x.doc
7
basic
interpreter: byte-equal== Permissions ==
Roles as sets of actions, folders that grant roles and deny actions, and documents that inherit from every folder above them.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 1
viewer read
editor read, write
auditor read, export
owner read, write, export, delete, share
viewer < editor: every action of viewer is in editor
viewer < auditor: every action of viewer is in auditor
viewer < owner: every action of viewer is in owner
editor and auditor: neither - each has an action the other lacks, so <, <=, > and >= are all false
editor < owner: every action of editor is in owner
auditor < owner: every action of auditor is in owner
Directly above one another: viewer -> editor, viewer -> auditor, editor -> owner, auditor -> owner.
Sorted by <, from two starting orders: viewer, editor, auditor, owner / viewer, auditor, editor, owner. Roles that are neither keep whatever order they came in.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 2
company ana owner, ben viewer, cy viewer
team ben editor, dee editor
documents: notes.txt, plan.txt
legal cy auditor; deny ben read
documents: contract.pdf
public guest viewer
documents: brochure.pdf
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> ben
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> contract.pdf
ben on contract.pdf (in legal): nothing.
read denied
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> cy
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> contract.pdf
cy on contract.pdf (in legal): read, export.
read auditor on legal
export auditor on legal
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> dee
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> plan.txt
dee on plan.txt (in team): read, write.
read editor on team
write editor on team
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> guest
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> plan.txt
guest on plan.txt (in team): nothing.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 6
Every stored grant matches what the folders give now.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document> contract.pdf
to folder (company, team, legal or public)> public
contract.pdf moved from legal to public. Its stored grants were not refreshed: a move changes the folder, not the document.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 4
document> brochure.pdf
to folder (company, team, legal or public)> legal
brochure.pdf moved from public to legal. Its stored grants were not refreshed: a move changes the folder, not the document.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 2
company ana owner, ben viewer, cy viewer
team ben editor, dee editor
documents: notes.txt, plan.txt
legal cy auditor; deny ben read
documents: brochure.pdf
public guest viewer
documents: contract.pdf
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> guest
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> contract.pdf
guest on contract.pdf (in public): read.
read viewer on public
The grants stored on the document still say: nothing.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 3
user (ana, ben, cy, dee or guest)> guest
document (brochure.pdf, contract.pdf, notes.txt, plan.txt)> brochure.pdf
guest on brochure.pdf (in legal): nothing.
The grants stored on the document still say: read.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 6
brochure.pdf ben read stored yes, folder no - more than it should
brochure.pdf cy export stored no, folder yes - less than it should
brochure.pdf guest read stored yes, folder no - more than it should
contract.pdf ben read stored no, folder yes - less than it should
contract.pdf cy export stored yes, folder no - more than it should
contract.pdf guest read stored no, folder yes - less than it should
6 stored grants are out of date: 3 give more than the folder does, 3 less. Too little shows itself, as a refusal someone runs into; too much shows nothing at all.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 5
document> contract.pdf
contract.pdf written: its stored grants were refreshed from public.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 6
brochure.pdf ben read stored yes, folder no - more than it should
brochure.pdf cy export stored no, folder yes - less than it should
brochure.pdf guest read stored yes, folder no - more than it should
3 stored grants are out of date: 2 give more than the folder does, 1 less. Too little shows itself, as a refusal someone runs into; too much shows nothing at all.
1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit
choice> 7
Bye.
What was typed (33 lines)
1
2
3
ben
contract.pdf
3
cy
contract.pdf
3
dee
plan.txt
3
guest
plan.txt
6
4
contract.pdf
public
4
brochure.pdf
legal
2
3
guest
contract.pdf
3
guest
brochure.pdf
6
5
contract.pdf
6
7
Modules
The program as written, entry module first. Each module transpiles to its own Python file, which is what eml project run executes.
main.eml(entry)
eml# P109 permissions: roles compared as sets, folders that grant and deny,
# what a user may do with a document and why, and documents moved without
# their stored grants being refreshed.
import perms
def plain(s):
0 => i
len(s) => j
while i < j and s[i] == " ":
i + 1 => i
while j > i and s[j - 1] == " ":
j - 1 => j
return s[i:j]
def pad(s, n):
while len(s) < n:
s + " " => s
return s
def listed(xs):
if len(xs) == 0:
return "nothing"
"" => out
for x in xs:
if out != "":
out + ", " => out
out + x => out
return out
def show_roles():
perms.roles() => r
for n in perms.role_names():
(" " + pad(n, 9) + listed(r[n])) ^0
perms.role_names() => names
for i in [0:len(names) - 1]:
for j in [i + 1:len(names) - 1]:
perms.compare(names[i], names[j]) => c
if c == "below":
(" " + names[i] + " < " + names[j] + ": every action of " + names[i] + " is in " + names[j]) ^0
elif c == "above":
(" " + names[i] + " > " + names[j]) ^0
elif c == "neither":
(" " + names[i] + " and " + names[j] + ": neither - each has an action the other lacks, so <, <=, > and >= are all false") ^0
"" => line
for c in perms.covers():
if line != "":
line + ", " => line
line + c[0] + " -> " + c[1] => line
("Directly above one another: " + line + ".") ^0
perms.sort_by_less(["editor", "auditor", "viewer", "owner"]) => a
perms.sort_by_less(["auditor", "editor", "viewer", "owner"]) => b
("Sorted by <, from two starting orders: " + listed(a) + " / " + listed(b) + ". Roles that are neither keep whatever order they came in.") ^0
def show_tree(folders, docs):
for f in ["company", "team", "legal", "public"]:
folders[f] => fo
"" => g
for u in ["ana", "ben", "cy", "dee", "guest"]:
if u in fo[1]:
if g != "":
g + ", " => g
g + u + " " + fo[1][u] => g
for u in ["ana", "ben", "cy", "dee", "guest"]:
if u in fo[2]:
if g != "":
g + "; " => g
g + "deny " + u + " " + listed(fo[2][u]) => g
"" => indent
if fo[0] != "":
" " => indent
(" " + pad(indent + f, 11) + g) ^0
"" => here
for d in perms.sorted_docs(docs):
if docs[d][0] == f:
if here != "":
here + ", " => here
here + d => here
if here != "":
(" " + indent + " documents: " + here) ^0
def check(folders, docs):
plain(input("user (ana, ben, cy, dee or guest)> ")) => u
if u == "":
"Cancelled." ^0
return
if not perms.has(["ana", "ben", "cy", "dee", "guest"], u):
"Type ana, ben, cy, dee or guest." ^0
return
plain(input("document (" + listed(perms.sorted_docs(docs)) + ")> ")) => d
if d == "":
"Cancelled." ^0
return
if not (d in docs):
"Pick one of the documents listed." ^0
return
perms.effective(folders, docs[d][0], u) => e
(u + " on " + d + " (in " + docs[d][0] + "): " + listed(e[0]) + ".") ^0
for r in e[1]:
(" " + pad(r[0], 7) + r[1]) ^0
docs[d][1][u] => cached
if not perms.subset(cached, e[0]) or not perms.subset(e[0], cached):
(" The grants stored on the document still say: " + listed(perms.ordered(cached)) + ".") ^0
def move(folders, docs):
plain(input("document> ")) => d
if d == "":
"Cancelled." ^0
return
if not (d in docs):
"Pick one of the documents listed." ^0
return
plain(input("to folder (company, team, legal or public)> ")) => f
if f == "":
"Cancelled." ^0
return
if not (f in folders):
"Type company, team, legal or public." ^0
return
if docs[d][0] == f:
(d + " is already in " + f + ".") ^0
return
docs[d][0] => old
f => docs[d][0]
(d + " moved from " + old + " to " + f + ". Its stored grants were not refreshed: a move changes the folder, not the document.") ^0
def edit(folders, docs, users):
plain(input("document> ")) => d
if d == "":
"Cancelled." ^0
return
if not (d in docs):
"Pick one of the documents listed." ^0
return
perms.refresh(folders, docs, d, users)
(d + " written: its stored grants were refreshed from " + docs[d][0] + ".") ^0
def show_audit(folders, docs, users):
perms.audit(folders, docs, users) => a
if len(a) == 0:
"Every stored grant matches what the folders give now." ^0
return
0 => over
0 => under
for x in a:
if x[3] == "over":
over + 1 => over
(" " + pad(x[0], 13) + pad(x[1], 6) + pad(x[2], 7) + "stored yes, folder no - more than it should") ^0
else:
under + 1 => under
(" " + pad(x[0], 13) + pad(x[1], 6) + pad(x[2], 7) + "stored no, folder yes - less than it should") ^0
(str(len(a)) + " stored grants are out of date: " + str(over) + " give more than the folder does, " + str(under) + " less. Too little shows itself, as a refusal someone runs into; too much shows nothing at all.") ^0
{"company": ["", {"ana": "owner", "ben": "viewer", "cy": "viewer"}, {}], "team": ["company", {"ben": "editor", "dee": "editor"}, {}], "legal": ["company", {"cy": "auditor"}, {"ben": ["read"]}], "public": ["company", {"guest": "viewer"}, {}]} => folders
{"plan.txt": ["team", {}], "notes.txt": ["team", {}], "contract.pdf": ["legal", {}], "brochure.pdf": ["public", {}]} => docs
["ana", "ben", "cy", "dee", "guest"] => users
for d in perms.sorted_docs(docs):
perms.refresh(folders, docs, d, users)
"== Permissions ==" ^0
"Roles as sets of actions, folders that grant roles and deny actions, and documents that inherit from every folder above them." ^0
True => running
while running:
"" ^0
"1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit" ^0
plain(input("choice> ")) => choice
if choice == "1":
show_roles()
elif choice == "2":
show_tree(folders, docs)
elif choice == "3":
check(folders, docs)
elif choice == "4":
move(folders, docs)
elif choice == "5":
edit(folders, docs, users)
elif choice == "6":
show_audit(folders, docs, users)
elif choice == "7":
False => running
else:
"Pick a number from 1 to 7." ^0
"Bye." ^0
Python projection (main.py)
import perms
def plain(s):
i = 0
j = len(s)
while i < j and s[i] == " ":
i = i + 1
while j > i and s[j - 1] == " ":
j = j - 1
return s[i:j]
def pad(s, n):
while len(s) < n:
s = s + " "
return s
def listed(xs):
if len(xs) == 0:
return "nothing"
out = ""
for x in xs:
if out != "":
out = out + ", "
out = out + x
return out
def show_roles():
r = perms.roles()
for n in perms.role_names():
print(" " + pad(n, 9) + listed(r[n]))
names = perms.role_names()
for i in range(0, len(names)):
for j in range(i + 1, len(names)):
c = perms.compare(names[i], names[j])
if c == "below":
print(" " + names[i] + " < " + names[j] + ": every action of " + names[i] + " is in " + names[j])
elif c == "above":
print(" " + names[i] + " > " + names[j])
elif c == "neither":
print(" " + names[i] + " and " + names[j] + ": neither - each has an action the other lacks, so <, <=, > and >= are all false")
line = ""
for c in perms.covers():
if line != "":
line = line + ", "
line = line + c[0] + " -> " + c[1]
print("Directly above one another: " + line + ".")
a = perms.sort_by_less(["editor", "auditor", "viewer", "owner"])
b = perms.sort_by_less(["auditor", "editor", "viewer", "owner"])
print("Sorted by <, from two starting orders: " + listed(a) + " / " + listed(b) + ". Roles that are neither keep whatever order they came in.")
def show_tree(folders, docs):
for f in ["company", "team", "legal", "public"]:
fo = folders[f]
g = ""
for u in ["ana", "ben", "cy", "dee", "guest"]:
if u in fo[1]:
if g != "":
g = g + ", "
g = g + u + " " + fo[1][u]
for u in ["ana", "ben", "cy", "dee", "guest"]:
if u in fo[2]:
if g != "":
g = g + "; "
g = g + "deny " + u + " " + listed(fo[2][u])
indent = ""
if fo[0] != "":
indent = " "
print(" " + pad(indent + f, 11) + g)
here = ""
for d in perms.sorted_docs(docs):
if docs[d][0] == f:
if here != "":
here = here + ", "
here = here + d
if here != "":
print(" " + indent + " documents: " + here)
def check(folders, docs):
u = plain(input("user (ana, ben, cy, dee or guest)> "))
if u == "":
print("Cancelled.")
return
if not perms.has(["ana", "ben", "cy", "dee", "guest"], u):
print("Type ana, ben, cy, dee or guest.")
return
d = plain(input("document (" + listed(perms.sorted_docs(docs)) + ")> "))
if d == "":
print("Cancelled.")
return
if not d in docs:
print("Pick one of the documents listed.")
return
e = perms.effective(folders, docs[d][0], u)
print(u + " on " + d + " (in " + docs[d][0] + "): " + listed(e[0]) + ".")
for r in e[1]:
print(" " + pad(r[0], 7) + r[1])
cached = docs[d][1][u]
if not perms.subset(cached, e[0]) or not perms.subset(e[0], cached):
print(" The grants stored on the document still say: " + listed(perms.ordered(cached)) + ".")
def move(folders, docs):
d = plain(input("document> "))
if d == "":
print("Cancelled.")
return
if not d in docs:
print("Pick one of the documents listed.")
return
f = plain(input("to folder (company, team, legal or public)> "))
if f == "":
print("Cancelled.")
return
if not f in folders:
print("Type company, team, legal or public.")
return
if docs[d][0] == f:
print(d + " is already in " + f + ".")
return
old = docs[d][0]
docs[d][0] = f
print(d + " moved from " + old + " to " + f + ". Its stored grants were not refreshed: a move changes the folder, not the document.")
def edit(folders, docs, users):
d = plain(input("document> "))
if d == "":
print("Cancelled.")
return
if not d in docs:
print("Pick one of the documents listed.")
return
perms.refresh(folders, docs, d, users)
print(d + " written: its stored grants were refreshed from " + docs[d][0] + ".")
def show_audit(folders, docs, users):
a = perms.audit(folders, docs, users)
if len(a) == 0:
print("Every stored grant matches what the folders give now.")
return
over = 0
under = 0
for x in a:
if x[3] == "over":
over = over + 1
print(" " + pad(x[0], 13) + pad(x[1], 6) + pad(x[2], 7) + "stored yes, folder no - more than it should")
else:
under = under + 1
print(" " + pad(x[0], 13) + pad(x[1], 6) + pad(x[2], 7) + "stored no, folder yes - less than it should")
print(str(len(a)) + " stored grants are out of date: " + str(over) + " give more than the folder does, " + str(under) + " less. Too little shows itself, as a refusal someone runs into; too much shows nothing at all.")
folders = {"company": ["", {"ana": "owner", "ben": "viewer", "cy": "viewer"}, {}], "team": ["company", {"ben": "editor", "dee": "editor"}, {}], "legal": ["company", {"cy": "auditor"}, {"ben": ["read"]}], "public": ["company", {"guest": "viewer"}, {}]}
docs = {"plan.txt": ["team", {}], "notes.txt": ["team", {}], "contract.pdf": ["legal", {}], "brochure.pdf": ["public", {}]}
users = ["ana", "ben", "cy", "dee", "guest"]
for d in perms.sorted_docs(docs):
perms.refresh(folders, docs, d, users)
print("== Permissions ==")
print("Roles as sets of actions, folders that grant roles and deny actions, and documents that inherit from every folder above them.")
running = True
while running:
print("")
print("1) roles 2) folders 3) what a user may do 4) move a document 5) write a document 6) audit 7) quit")
choice = plain(input("choice> "))
if choice == "1":
show_roles()
elif choice == "2":
show_tree(folders, docs)
elif choice == "3":
check(folders, docs)
elif choice == "4":
move(folders, docs)
elif choice == "5":
edit(folders, docs, users)
elif choice == "6":
show_audit(folders, docs, users)
elif choice == "7":
running = False
else:
print("Pick a number from 1 to 7.")
print("Bye.")
perms.eml
eml# P109 permissions - roles as sets of actions, folders that grant roles to
# users and can deny actions, and documents that inherit from every folder
# above them. As in the corpus case permission-set-algebra, comparing two
# roles is a subset question and the answer can be "neither": a partial
# order. As in the corpus case inherited-permission-after-move, a
# document keeps its resolved grants and refreshes them on a write, and a
# move is not a write.
#
# Lists stand in for sets, always reported in a fixed order.
def actions():
return ["read", "write", "export", "delete", "share"]
def role_names():
return ["viewer", "editor", "auditor", "owner"]
def roles():
return {"viewer": ["read"], "editor": ["read", "write"], "auditor": ["read", "export"], "owner": ["read", "write", "export", "delete", "share"]}
def has(xs, x):
for y in xs:
if y == x:
return True
return False
def subset(a, b):
for x in a:
if not has(b, x):
return False
return True
def ordered(xs):
# actions in the fixed order
[] => out
for a in actions():
if has(xs, a):
out + [a] => out
return out
def compare(a, b):
# how role a stands to role b: "same", "below", "above" or "neither"
roles() => r
subset(r[a], r[b]) => ab
subset(r[b], r[a]) => ba
if ab and ba:
return "same"
if ab:
return "below"
if ba:
return "above"
return "neither"
def covers():
# [lower, upper]: upper holds lower and more, with no role in between
[] => out
role_names() => names
for a in names:
for b in names:
if compare(a, b) == "below":
True => direct
for c in names:
if compare(a, c) == "below" and compare(c, b) == "below":
False => direct
if direct:
out + [[a, b]] => out
return out
def sort_by_less(xs):
# an insertion sort that trusts "a is below b": with roles that are
# neither, the result follows the order they came in
[] => out
for x in xs:
out + [x] => out
for i in [1:len(out) - 1]:
i => j
while j > 0 and compare(out[j], out[j - 1]) == "below":
out[j] => t
out[j - 1] => out[j]
t => out[j - 1]
j - 1 => j
return out
def chain(folders, name):
# the folder and every folder above it, nearest first
[] => out
name => f
while f != "":
out + [f] => out
folders[f][0] => f
return out
def effective(folders, folder, user):
# [actions allowed, [[action, why], ...]]: every role granted to the
# user on this folder or above, minus every action denied there; a deny
# wins over any grant
roles() => r
[] => allowed
[] => denied
[] => why
for f in chain(folders, folder):
folders[f][1] => grants
if user in grants:
for a in r[grants[user]]:
if not has(allowed, a):
allowed + [a] => allowed
why + [[a, grants[user] + " on " + f]] => why
folders[f][2] => denies
if user in denies:
for a in denies[user]:
if not has(denied, a):
denied + [a] => denied
[] => out
[] => reasons
for a in actions():
if has(denied, a):
reasons + [[a, "denied"]] => reasons
elif has(allowed, a):
out + [a] => out
for w in why:
if w[0] == a:
reasons + [[a, w[1]]] => reasons
return [out, reasons]
def refresh(folders, docs, name, users):
# store what the document's folder gives each user now
{} => cache
for u in users:
effective(folders, docs[name][0], u)[0] => cache[u]
cache => docs[name][1]
def audit(folders, docs, users):
# [[doc, user, action, "over" or "under"], ...] where the cached grants
# differ from what the folder gives now: "over" when the cache allows
# and the folder does not
[] => out
for d in sorted_docs(docs):
for u in users:
docs[d][1][u] => cached
effective(folders, docs[d][0], u)[0] => now
for a in actions():
if has(cached, a) and not has(now, a):
out + [[d, u, a, "over"]] => out
elif has(now, a) and not has(cached, a):
out + [[d, u, a, "under"]] => out
return out
def sorted_docs(docs):
[] => out
for d in docs:
out + [d] => out
for i in [1:len(out) - 1]:
i => j
while j > 0 and out[j] < out[j - 1]:
out[j] => t
out[j - 1] => out[j]
t => out[j - 1]
j - 1 => j
return out
Python projection (perms.py)
def actions():
return ["read", "write", "export", "delete", "share"]
def role_names():
return ["viewer", "editor", "auditor", "owner"]
def roles():
return {"viewer": ["read"], "editor": ["read", "write"], "auditor": ["read", "export"], "owner": ["read", "write", "export", "delete", "share"]}
def has(xs, x):
for y in xs:
if y == x:
return True
return False
def subset(a, b):
for x in a:
if not has(b, x):
return False
return True
def ordered(xs):
out = []
for a in actions():
if has(xs, a):
out = out + [a]
return out
def compare(a, b):
r = roles()
ab = subset(r[a], r[b])
ba = subset(r[b], r[a])
if ab and ba:
return "same"
if ab:
return "below"
if ba:
return "above"
return "neither"
def covers():
out = []
names = role_names()
for a in names:
for b in names:
if compare(a, b) == "below":
direct = True
for c in names:
if compare(a, c) == "below" and compare(c, b) == "below":
direct = False
if direct:
out = out + [[a, b]]
return out
def sort_by_less(xs):
out = []
for x in xs:
out = out + [x]
for i in range(1, len(out)):
j = i
while j > 0 and compare(out[j], out[j - 1]) == "below":
t = out[j]
out[j] = out[j - 1]
out[j - 1] = t
j = j - 1
return out
def chain(folders, name):
out = []
f = name
while f != "":
out = out + [f]
f = folders[f][0]
return out
def effective(folders, folder, user):
r = roles()
allowed = []
denied = []
why = []
for f in chain(folders, folder):
grants = folders[f][1]
if user in grants:
for a in r[grants[user]]:
if not has(allowed, a):
allowed = allowed + [a]
why = why + [[a, grants[user] + " on " + f]]
denies = folders[f][2]
if user in denies:
for a in denies[user]:
if not has(denied, a):
denied = denied + [a]
out = []
reasons = []
for a in actions():
if has(denied, a):
reasons = reasons + [[a, "denied"]]
elif has(allowed, a):
out = out + [a]
for w in why:
if w[0] == a:
reasons = reasons + [[a, w[1]]]
return [out, reasons]
def refresh(folders, docs, name, users):
cache = {}
for u in users:
cache[u] = effective(folders, docs[name][0], u)[0]
docs[name][1] = cache
def audit(folders, docs, users):
out = []
for d in sorted_docs(docs):
for u in users:
cached = docs[d][1][u]
now = effective(folders, docs[d][0], u)[0]
for a in actions():
if has(cached, a) and not has(now, a):
out = out + [[d, u, a, "over"]]
elif has(now, a) and not has(cached, a):
out = out + [[d, u, a, "under"]]
return out
def sorted_docs(docs):
out = []
for d in docs:
out = out + [d]
for i in range(1, len(out)):
j = i
while j > 0 and out[j] < out[j - 1]:
t = out[j]
out[j] = out[j - 1]
out[j - 1] = t
j = j - 1
return out