blob: 5b02055473b8d03b0c923fbd734abfcd94569f8e [file] [log] [blame]
from fnmatch import fnmatch
"""
A General-purpose ACL mechanism.
[allow | deny] <type_of_object> <text_pattern>
"allow all" and "deny all" are shorthand for allowing or denying all objects.
Lines are executed from top to bottom until a match was found, typical
iptables style. An implicit 'deny all' exists at the bottom of the list.
For example,
allow site Max Planck Institute
deny site Arizona
allow region US
deny user scott@onlab.us
allow user *@onlab.us
"""
class ACLValidationError(Exception): pass
class AccessControlList:
def __init__(self, aclText=None):
self.rules = []
if aclText:
self.import_text(aclText)
def import_text(self, aclText):
# allow either newline or ';' to separate rules
aclText = aclText.replace("\n", ";")
for line in aclText.split(";"):
line = line.strip()
if line.startswith("#"):
continue
if line=="":
continue
parts = line.split()
if len(parts)==2 and (parts[1]=="all"):
# "allow all" has no pattern
parts = (parts[0], parts[1], "")
if len(parts)!=3:
raise ACLValidationError(line)
(action, object, pattern) = parts
if action not in ["allow", "deny"]:
raise ACLValidationError(line)
if object not in ["site", "user", "all"]:
raise ACLValidationError(line)
self.rules.append( (action, object, pattern) )
def __str__(self):
lines = []
for rule in self.rules:
lines.append( " ".join(rule) )
return ";\n".join(lines)
def test(self, user, site=None):
for rule in self.rules:
if self.match_rule(rule, user):
return rule[0]
return "deny"
def match_rule(self, rule, user, site=None):
(action, object, pattern) = rule
if (site==None):
site = user.site
if (object == "site"):
if fnmatch(site.name, pattern):
return True
elif (object == "user"):
if fnmatch(user.email, pattern):
return True
elif (object == "all"):
return True
return False
if __name__ == '__main__':
# self-test
class fakesite:
def __init__(self, siteName):
self.name = siteName
class fakeuser:
def __init__(self, email, siteName):
self.email = email
self.site = fakesite(siteName)
u_scott = fakeuser("scott@onlab.us", "ON.Lab")
u_bill = fakeuser("bill@onlab.us", "ON.Lab")
u_andy = fakeuser("acb@cs.princeton.edu", "Princeton")
u_john = fakeuser("jhh@cs.arizona.edu", "Arizona")
u_hacker = fakeuser("somehacker@foo.com", "Not A Real Site")
# check the "deny all" rule
acl = AccessControlList("deny all")
assert(acl.test(u_scott) == "deny")
# a blank ACL results in "deny all"
acl = AccessControlList("")
assert(acl.test(u_scott) == "deny")
# check the "allow all" rule
acl = AccessControlList("allow all")
assert(acl.test(u_scott) == "allow")
# allow only one site
acl = AccessControlList("allow site ON.Lab")
assert(acl.test(u_scott) == "allow")
assert(acl.test(u_andy) == "deny")
# some complicated ACL
acl = AccessControlList("""allow site Princeton
allow user *@cs.arizona.edu
deny site Arizona
deny user scott@onlab.us
allow site ON.Lab""")
assert(acl.test(u_scott) == "deny")
assert(acl.test(u_bill) == "allow")
assert(acl.test(u_andy) == "allow")
assert(acl.test(u_john) == "allow")
assert(acl.test(u_hacker) == "deny")
print acl