blob: 5b02055473b8d03b0c923fbd734abfcd94569f8e [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"""
S.Çağlar Onurd718fe52015-02-09 15:58:00 -050019class ACLValidationError(Exception): pass
Scott Baker3b678742014-06-09 13:11:54 -070020
Scott Baker5380c522014-06-06 14:49:43 -070021class AccessControlList:
22 def __init__(self, aclText=None):
23 self.rules = []
24 if aclText:
25 self.import_text(aclText)
26
27 def import_text(self, aclText):
28 # allow either newline or ';' to separate rules
29 aclText = aclText.replace("\n", ";")
30 for line in aclText.split(";"):
31 line = line.strip()
32 if line.startswith("#"):
33 continue
34
35 if line=="":
36 continue
37
38 parts = line.split()
39
40 if len(parts)==2 and (parts[1]=="all"):
41 # "allow all" has no pattern
42 parts = (parts[0], parts[1], "")
43
44 if len(parts)!=3:
45 raise ACLValidationError(line)
46
47 (action, object, pattern) = parts
48
49 if action not in ["allow", "deny"]:
50 raise ACLValidationError(line)
51
52 if object not in ["site", "user", "all"]:
53 raise ACLValidationError(line)
54
55 self.rules.append( (action, object, pattern) )
56
57 def __str__(self):
58 lines = []
59 for rule in self.rules:
60 lines.append( " ".join(rule) )
61 return ";\n".join(lines)
62
Scott Baker3b678742014-06-09 13:11:54 -070063 def test(self, user, site=None):
Scott Baker5380c522014-06-06 14:49:43 -070064 for rule in self.rules:
65 if self.match_rule(rule, user):
66 return rule[0]
67 return "deny"
68
Scott Baker3b678742014-06-09 13:11:54 -070069 def match_rule(self, rule, user, site=None):
Scott Baker5380c522014-06-06 14:49:43 -070070 (action, object, pattern) = rule
71
Scott Baker3b678742014-06-09 13:11:54 -070072 if (site==None):
73 site = user.site
74
Scott Baker5380c522014-06-06 14:49:43 -070075 if (object == "site"):
Scott Baker3b678742014-06-09 13:11:54 -070076 if fnmatch(site.name, pattern):
Scott Baker5380c522014-06-06 14:49:43 -070077 return True
78 elif (object == "user"):
79 if fnmatch(user.email, pattern):
80 return True
81 elif (object == "all"):
82 return True
83
84 return False
85
86
87if __name__ == '__main__':
Scott Baker3b678742014-06-09 13:11:54 -070088 # self-test
89
Scott Baker5380c522014-06-06 14:49:43 -070090 class fakesite:
91 def __init__(self, siteName):
92 self.name = siteName
93
94 class fakeuser:
95 def __init__(self, email, siteName):
96 self.email = email
97 self.site = fakesite(siteName)
98
99 u_scott = fakeuser("scott@onlab.us", "ON.Lab")
100 u_bill = fakeuser("bill@onlab.us", "ON.Lab")
101 u_andy = fakeuser("acb@cs.princeton.edu", "Princeton")
102 u_john = fakeuser("jhh@cs.arizona.edu", "Arizona")
103 u_hacker = fakeuser("somehacker@foo.com", "Not A Real Site")
104
105 # check the "deny all" rule
106 acl = AccessControlList("deny all")
107 assert(acl.test(u_scott) == "deny")
108
109 # a blank ACL results in "deny all"
110 acl = AccessControlList("")
111 assert(acl.test(u_scott) == "deny")
112
113 # check the "allow all" rule
114 acl = AccessControlList("allow all")
115 assert(acl.test(u_scott) == "allow")
116
117 # allow only one site
118 acl = AccessControlList("allow site ON.Lab")
119 assert(acl.test(u_scott) == "allow")
120 assert(acl.test(u_andy) == "deny")
121
122 # some complicated ACL
123 acl = AccessControlList("""allow site Princeton
124 allow user *@cs.arizona.edu
125 deny site Arizona
126 deny user scott@onlab.us
127 allow site ON.Lab""")
128
129 assert(acl.test(u_scott) == "deny")
130 assert(acl.test(u_bill) == "allow")
131 assert(acl.test(u_andy) == "allow")
132 assert(acl.test(u_john) == "allow")
133 assert(acl.test(u_hacker) == "deny")
134
135 print acl
136