blob: 7856414e087f430de5a4a46c2d8b657bf28c0310 [file] [log] [blame]
Scott Baker5380c522014-06-06 14:49:43 -07001from fnmatch import fnmatch
2
Scott Baker3b678742014-06-09 13:11:54 -07003"""
4 A General-purpose ACL mechanism.
5
6 [allow | deny] <type_of_object> <text_pattern>
7
8 "allow all" and "deny all" are shorthand for allowing or denying all objects.
9 Lines are executed from top to bottom until a match was found, typical
10 iptables style. An implicit 'deny all' exists at the bottom of the list.
11
12 For example,
13 allow site Max Planck Institute
14 deny site Arizona
15 allow region US
16 deny user scott@onlab.us
17 allow user *@onlab.us
18"""
19
Scott Baker5380c522014-06-06 14:49:43 -070020class AccessControlList:
21 def __init__(self, aclText=None):
22 self.rules = []
23 if aclText:
24 self.import_text(aclText)
25
26 def import_text(self, aclText):
27 # allow either newline or ';' to separate rules
28 aclText = aclText.replace("\n", ";")
29 for line in aclText.split(";"):
30 line = line.strip()
31 if line.startswith("#"):
32 continue
33
34 if line=="":
35 continue
36
37 parts = line.split()
38
39 if len(parts)==2 and (parts[1]=="all"):
40 # "allow all" has no pattern
41 parts = (parts[0], parts[1], "")
42
43 if len(parts)!=3:
44 raise ACLValidationError(line)
45
46 (action, object, pattern) = parts
47
48 if action not in ["allow", "deny"]:
49 raise ACLValidationError(line)
50
51 if object not in ["site", "user", "all"]:
52 raise ACLValidationError(line)
53
54 self.rules.append( (action, object, pattern) )
55
56 def __str__(self):
57 lines = []
58 for rule in self.rules:
59 lines.append( " ".join(rule) )
60 return ";\n".join(lines)
61
Scott Baker3b678742014-06-09 13:11:54 -070062 def test(self, user, site=None):
Scott Baker5380c522014-06-06 14:49:43 -070063 for rule in self.rules:
64 if self.match_rule(rule, user):
65 return rule[0]
66 return "deny"
67
Scott Baker3b678742014-06-09 13:11:54 -070068 def match_rule(self, rule, user, site=None):
Scott Baker5380c522014-06-06 14:49:43 -070069 (action, object, pattern) = rule
70
Scott Baker3b678742014-06-09 13:11:54 -070071 if (site==None):
72 site = user.site
73
Scott Baker5380c522014-06-06 14:49:43 -070074 if (object == "site"):
Scott Baker3b678742014-06-09 13:11:54 -070075 if fnmatch(site.name, pattern):
Scott Baker5380c522014-06-06 14:49:43 -070076 return True
77 elif (object == "user"):
78 if fnmatch(user.email, pattern):
79 return True
80 elif (object == "all"):
81 return True
82
83 return False
84
85
86if __name__ == '__main__':
Scott Baker3b678742014-06-09 13:11:54 -070087 # self-test
88
Scott Baker5380c522014-06-06 14:49:43 -070089 class fakesite:
90 def __init__(self, siteName):
91 self.name = siteName
92
93 class fakeuser:
94 def __init__(self, email, siteName):
95 self.email = email
96 self.site = fakesite(siteName)
97
98 u_scott = fakeuser("scott@onlab.us", "ON.Lab")
99 u_bill = fakeuser("bill@onlab.us", "ON.Lab")
100 u_andy = fakeuser("acb@cs.princeton.edu", "Princeton")
101 u_john = fakeuser("jhh@cs.arizona.edu", "Arizona")
102 u_hacker = fakeuser("somehacker@foo.com", "Not A Real Site")
103
104 # check the "deny all" rule
105 acl = AccessControlList("deny all")
106 assert(acl.test(u_scott) == "deny")
107
108 # a blank ACL results in "deny all"
109 acl = AccessControlList("")
110 assert(acl.test(u_scott) == "deny")
111
112 # check the "allow all" rule
113 acl = AccessControlList("allow all")
114 assert(acl.test(u_scott) == "allow")
115
116 # allow only one site
117 acl = AccessControlList("allow site ON.Lab")
118 assert(acl.test(u_scott) == "allow")
119 assert(acl.test(u_andy) == "deny")
120
121 # some complicated ACL
122 acl = AccessControlList("""allow site Princeton
123 allow user *@cs.arizona.edu
124 deny site Arizona
125 deny user scott@onlab.us
126 allow site ON.Lab""")
127
128 assert(acl.test(u_scott) == "deny")
129 assert(acl.test(u_bill) == "allow")
130 assert(acl.test(u_andy) == "allow")
131 assert(acl.test(u_john) == "allow")
132 assert(acl.test(u_hacker) == "deny")
133
134 print acl
135