acl support for deployments
diff --git a/planetstack/core/acl.py b/planetstack/core/acl.py
new file mode 100644
index 0000000..7fc6a02
--- /dev/null
+++ b/planetstack/core/acl.py
@@ -0,0 +1,113 @@
+from fnmatch import fnmatch
+
+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):
+        for rule in self.rules:
+            if self.match_rule(rule, user):
+                return rule[0]
+        return "deny"
+
+    def match_rule(self, rule, user):
+        (action, object, pattern) = rule
+
+        if (object == "site"):
+            if fnmatch(user.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__':
+    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
+