import repository from arizona
[raven.git] / lib / ravenlib / acl / mysqlacl.py
1 import MySQLdb
2 import MySQLdb.cursors
3
4 from baseacl import *
5
6 class MysqlAcl(BaseAcl):
7     def __init__(self, objectId, manager, tablename):
8         BaseAcl.__init__(self, objectId)
9         self.manager = manager
10         self.tablename = tablename
11         self.read_from_db()
12
13     def read_from_db(self):
14         try:
15             self.manager.execute("SELECT principal, rights FROM `" + self.tablename + "`;")
16         except MySQLdb.ProgrammingError, e:
17             # Error 1146 is what happens when a table does not exist. Catch it
18             # here in case we're in a race where an acl has disappeared while
19             # we're trying to read it.
20             if e[0] == 1146:
21                 raise AclDoesNotExistError(self.objectId)
22
23         dict_list = self.manager.cursor.fetchall()
24         for dict in dict_list:
25             principal = dict["principal"]
26             rights = dict["rights"].split(",")
27             self.rights[principal] = rights
28
29     def write_row_to_db(self, principal):
30         self.manager.execute("DELETE FROM `" + self.tablename + "` WHERE principal = %s;", (principal))
31         rights = ",".join(self.rights[principal])
32         self.manager.execute("INSERT INTO `" + self.tablename + "` (principal, rights) VALUES(%s, %s);", (principal, rights))
33
34     def write_all_to_db(self):
35         for principal in self.rights.keys():
36             self.write_row_to_db(principal)
37
38     def modified(self, principal):
39         self.write_row_to_db(principal)
40
41
42 class MysqlAclManager(BaseAclManager):
43     def __init__(self, dbname, dbuser, dbpasswd, dbaddress, prefix=None, nuke=False):
44         """
45              dbname = database name
46              dbuser = database user name
47              dbpasswd = database password
48              dbaddress = database address / hostname
49              prefix = prefix to use when creating tables
50              nuke = if true, delete everything
51         """
52         if not prefix:
53             prefix = "acl_"
54
55         self.dbname = dbname
56         self.dbuser = dbuser
57         self.dbpasswd = dbpasswd
58         self.dbaddress = dbaddress
59         self.prefix = prefix
60         self.master_table_name = prefix + "master"
61
62         self.connect(nuke=nuke)
63
64     def connect(self, nuke=False):
65         """
66              Connect to the database. If 'nuke' is true, then delete everything.
67              As a side effect, create the master table if it does not exist.
68         """
69         self.conn = MySQLdb.connect(host=self.dbaddress, user=self.dbuser, passwd=self.dbpasswd, db=self.dbname, cursorclass=MySQLdb.cursors.DictCursor)
70         self.cursor = self.conn.cursor()
71
72         if nuke:
73             self.execute("DROP TABLE IF EXISTS `" + self.master_table_name + "`;");
74
75         self.execute("CREATE TABLE IF NOT EXISTS `" + self.master_table_name + "` " + """
76                          (`objectid`           TEXT,
77                           `tablename`          TEXT);""");
78
79     def execute(self, query, args=None):
80         """
81             Execute a query using the current connection/cursor. This is just
82             a trap to let us put in a print for debugging if we need it.
83         """
84         if args is not None:
85              query = query % self.conn.literal(args)
86         # print "Query:", query
87         self.cursor.execute(query)
88
89     def lock(self):
90         """
91             Mutual-exclusion lock. Causes anyone else who calls lock() to block
92             until the lock is unlocked.
93         """
94         self.cursor.execute("SELECT GET_LOCK('acl_lock', 300);")  # 5 minute duration
95
96     def unlock(self):
97         """
98             Mutual-exclusion unlock.
99         """
100         self.cursor.execute("SELECT RELEASE_LOCK('acl_lock');")
101
102     def get_tablename(self, objectId):
103         """
104             Make up a tablename for an objectId
105         """
106         return self.prefix + objectId      # XXX escape this properly
107
108     def get_acl(self, objectId):
109         self.cursor.execute("SELECT tablename FROM `" + self.master_table_name + "` WHERE objectid=%s;", objectId)
110         rows = self.cursor.fetchall()
111
112         if (len(rows) == 0):
113             raise AclDoesNotExistError(objectId)
114
115         # Potential race here. The table could be deleted by delete_acl
116         # before we can read it. MysqlAcl() will catch this and raise a
117         # AclDoesNotExistError.
118
119         acl = MysqlAcl(objectId, self, rows[0]["tablename"])
120         return acl
121
122     def acl_exists(self, objectId):
123         self.cursor.execute("SELECT tablename FROM `" + self.master_table_name + "` WHERE objectid=%s;", objectId)
124         rows = self.cursor.fetchall()
125         return (len(rows) > 0)
126
127     def create_acl(self, objectId):
128         self.lock()
129         try:
130             if self.acl_exists(objectId):
131                 raise AclExistsError(objectId)
132
133             tablename = self.get_tablename(objectId)
134             self.execute("INSERT INTO `" + self.master_table_name + "` (objectid, tablename) VALUES(%s,%s);", (objectId, tablename))
135
136             self.execute("DROP TABLE IF EXISTS `" + tablename + "`;")
137             self.execute("CREATE TABLE `" + tablename + "` " + """
138                            (`principal`     TEXT,
139                             `rights`        TEXT);""");
140
141             acl = self.get_acl(objectId)
142             return acl
143         finally:
144             self.unlock()
145
146     def delete_acl(self, objectId):
147         self.lock()
148         try:
149             self.execute("SELECT tablename FROM `" + self.master_table_name + "` WHERE objectid=%s;", objectId)
150             rows = self.cursor.fetchall()
151
152             if (len(rows) == 0):
153                 raise AclDoesNotExistError(objectId)
154
155             # XXX potential race here
156
157             tablename = rows[0]["tablename"]
158
159             self.execute("DELETE FROM `" + self.master_table_name +"` WHERE objectid=%s;", objectId)
160             self.execute("DROP TABLE IF EXISTS `" + tablename + "`;");
161         finally:
162             self.unlock()
163