Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi
[plstackapi.git] / planetstack / core / acl.py
1 from fnmatch import fnmatch
2
3 """
4     A General-purpose ACL mechanism.
5
6     [allow | deny] <type_of_object> <text_pattern>\r
7 \r
8     "allow all" and "deny all" are shorthand for allowing or denying all objects.\r
9     Lines are executed from top to bottom until a match was found, typical\r
10     iptables style. An implicit 'deny all' exists at the bottom of the list.\r
11 \r
12     For example,\r
13     allow site Max Planck Institute\r
14     deny site Arizona\r
15     allow region US\r
16     deny user scott@onlab.us\r
17     allow user *@onlab.us
18 """
19
20 class 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
62     def test(self, user, site=None):
63         for rule in self.rules:
64             if self.match_rule(rule, user):
65                 return rule[0]
66         return "deny"
67
68     def match_rule(self, rule, user, site=None):
69         (action, object, pattern) = rule
70
71         if (site==None):
72             site = user.site
73
74         if (object == "site"):
75             if fnmatch(site.name, pattern):
76                 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
86 if __name__ == '__main__':
87     # self-test
88
89     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