39
|
1 # -*- coding: utf-8 -*-
|
|
2 #
|
|
3 # Copyright (C) 2003-2005 Edgewall Software
|
|
4 # Copyright (C) 2003-2004 Jonas Borgström <jonas@edgewall.com>
|
|
5 # Copyright (C) 2005 Christopher Lenz <cmlenz@gmx.de>
|
|
6 # All rights reserved.
|
|
7 #
|
|
8 # This software is licensed as described in the file COPYING, which
|
|
9 # you should have received as part of this distribution. The terms
|
|
10 # are also available at http://trac.edgewall.com/license.html.
|
|
11 #
|
|
12 # This software consists of voluntary contributions made by many
|
|
13 # individuals. For the exact contribution history, see the revision
|
|
14 # history and logs, available at http://projects.edgewall.com/trac/.
|
|
15 #
|
|
16 # Author: Jonas Borgström <jonas@edgewall.com>
|
|
17 # Christopher Lenz <cmlenz@gmx.de>
|
|
18
|
|
19 """Management of permissions."""
|
|
20
|
|
21 from trac.config import ExtensionOption
|
|
22 from trac.core import *
|
|
23
|
|
24 __all__ = ['IPermissionRequestor', 'IPermissionStore',
|
|
25 'IPermissionGroupProvider', 'PermissionError', 'PermissionSystem']
|
|
26
|
|
27 class PermissionError(StandardError):
|
|
28 """Insufficient permissions to complete the operation"""
|
|
29
|
|
30 def __init__ (self, action):
|
|
31 StandardError.__init__(self)
|
|
32 self.action = action
|
|
33
|
|
34 def __str__ (self):
|
|
35 return '%s privileges are required to perform this operation' % self.action
|
|
36
|
|
37
|
|
38 class IPermissionRequestor(Interface):
|
|
39 """Extension point interface for components that define actions."""
|
|
40
|
|
41 def get_permission_actions():
|
|
42 """Return a list of actions defined by this component.
|
|
43
|
|
44 The items in the list may either be simple strings, or
|
|
45 `(string, sequence)` tuples. The latter are considered to be "meta
|
|
46 permissions" that group several simple actions under one name for
|
|
47 convenience.
|
|
48 """
|
|
49
|
|
50
|
|
51 class IPermissionStore(Interface):
|
|
52 """Extension point interface for components that provide storage and
|
|
53 management of permissions."""
|
|
54
|
|
55 def get_user_permissions(username):
|
|
56 """Return all permissions for the user with the specified name.
|
|
57
|
|
58 The permissions are returned as a dictionary where the key is the name
|
|
59 of the permission, and the value is either `True` for granted
|
|
60 permissions or `False` for explicitly denied permissions."""
|
|
61
|
|
62 def get_all_permissions():
|
|
63 """Return all permissions for all users.
|
|
64
|
|
65 The permissions are returned as a list of (subject, action)
|
|
66 formatted tuples."""
|
|
67
|
|
68 def grant_permission(username, action):
|
|
69 """Grant a user permission to perform an action."""
|
|
70
|
|
71 def revoke_permission(username, action):
|
|
72 """Revokes the permission of the given user to perform an action."""
|
|
73
|
|
74
|
|
75 class IPermissionGroupProvider(Interface):
|
|
76 """Extension point interface for components that provide information about
|
|
77 user groups.
|
|
78 """
|
|
79
|
|
80 def get_permission_groups(username):
|
|
81 """Return a list of names of the groups that the user with the specified
|
|
82 name is a member of."""
|
|
83
|
|
84
|
|
85 class DefaultPermissionStore(Component):
|
|
86 """Default implementation of permission storage and simple group management.
|
|
87
|
|
88 This component uses the `PERMISSION` table in the database to store both
|
|
89 permissions and groups.
|
|
90 """
|
|
91 implements(IPermissionStore)
|
|
92
|
|
93 group_providers = ExtensionPoint(IPermissionGroupProvider)
|
|
94
|
|
95 def get_user_permissions(self, username):
|
|
96 """Retrieve the permissions for the given user and return them in a
|
|
97 dictionary.
|
|
98
|
|
99 The permissions are stored in the database as (username, action)
|
|
100 records. There's simple support for groups by using lowercase names for
|
|
101 the action column: such a record represents a group and not an actual
|
|
102 permission, and declares that the user is part of that group.
|
|
103 """
|
|
104 subjects = [username]
|
|
105 for provider in self.group_providers:
|
|
106 subjects += list(provider.get_permission_groups(username))
|
|
107
|
|
108 actions = []
|
|
109 db = self.env.get_db_cnx()
|
|
110 cursor = db.cursor()
|
|
111 cursor.execute("SELECT username,action FROM permission")
|
|
112 rows = cursor.fetchall()
|
|
113 while True:
|
|
114 num_users = len(subjects)
|
|
115 num_actions = len(actions)
|
|
116 for user, action in rows:
|
|
117 if user in subjects:
|
|
118 if not action.islower() and action not in actions:
|
|
119 actions.append(action)
|
|
120 if action.islower() and action not in subjects:
|
|
121 # action is actually the name of the permission group
|
|
122 # here
|
|
123 subjects.append(action)
|
|
124 if num_users == len(subjects) and num_actions == len(actions):
|
|
125 break
|
|
126 return [action for action in actions if not action.islower()]
|
|
127
|
|
128 def get_all_permissions(self):
|
|
129 """Return all permissions for all users.
|
|
130
|
|
131 The permissions are returned as a list of (subject, action)
|
|
132 formatted tuples."""
|
|
133 db = self.env.get_db_cnx()
|
|
134 cursor = db.cursor()
|
|
135 cursor.execute("SELECT username,action FROM permission")
|
|
136 return [(row[0], row[1]) for row in cursor]
|
|
137
|
|
138 def grant_permission(self, username, action):
|
|
139 """Grants a user the permission to perform the specified action."""
|
|
140 db = self.env.get_db_cnx()
|
|
141 cursor = db.cursor()
|
|
142 cursor.execute("INSERT INTO permission VALUES (%s, %s)",
|
|
143 (username, action))
|
|
144 self.log.info('Granted permission for %s to %s' % (action, username))
|
|
145 db.commit()
|
|
146
|
|
147 def revoke_permission(self, username, action):
|
|
148 """Revokes a users' permission to perform the specified action."""
|
|
149 db = self.env.get_db_cnx()
|
|
150 cursor = db.cursor()
|
|
151 cursor.execute("DELETE FROM permission WHERE username=%s AND action=%s",
|
|
152 (username, action))
|
|
153 self.log.info('Revoked permission for %s to %s' % (action, username))
|
|
154 db.commit()
|
|
155
|
|
156
|
|
157 class DefaultPermissionGroupProvider(Component):
|
|
158 """Provides the basic builtin permission groups 'anonymous' and
|
|
159 'authenticated'."""
|
|
160
|
|
161 implements(IPermissionGroupProvider)
|
|
162
|
|
163 def get_permission_groups(self, username):
|
|
164 groups = ['anonymous']
|
|
165 if username and username != 'anonymous':
|
|
166 groups.append('authenticated')
|
|
167 return groups
|
|
168
|
|
169
|
|
170 class PermissionSystem(Component):
|
|
171 """Sub-system that manages user permissions."""
|
|
172
|
|
173 implements(IPermissionRequestor)
|
|
174
|
|
175 requestors = ExtensionPoint(IPermissionRequestor)
|
|
176
|
|
177 store = ExtensionOption('trac', 'permission_store', IPermissionStore,
|
|
178 'DefaultPermissionStore',
|
|
179 """Name of the component implementing `IPermissionStore`, which is used
|
|
180 for managing user and group permissions.""")
|
|
181
|
|
182 # Public API
|
|
183
|
|
184 def grant_permission(self, username, action):
|
|
185 """Grant the user with the given name permission to perform to specified
|
|
186 action."""
|
|
187 if action.isupper() and action not in self.get_actions():
|
|
188 raise TracError, '%s is not a valid action.' % action
|
|
189
|
|
190 self.store.grant_permission(username, action)
|
|
191
|
|
192 def revoke_permission(self, username, action):
|
|
193 """Revokes the permission of the specified user to perform an action."""
|
|
194 # TODO: Validate that this permission does in fact exist
|
|
195 if action.isupper() and action not in self.get_actions():
|
|
196 raise TracError, '%s is not a valid action.' % action
|
|
197
|
|
198 self.store.revoke_permission(username, action)
|
|
199
|
|
200 def get_actions(self):
|
|
201 actions = []
|
|
202 for requestor in self.requestors:
|
|
203 for action in requestor.get_permission_actions():
|
|
204 if isinstance(action, tuple):
|
|
205 actions.append(action[0])
|
|
206 else:
|
|
207 actions.append(action)
|
|
208 return actions
|
|
209
|
|
210 def get_user_permissions(self, username=None):
|
|
211 """Return the permissions of the specified user.
|
|
212
|
|
213 The return value is a dictionary containing all the actions as keys, and
|
|
214 a boolean value. `True` means that the permission is granted, `False`
|
|
215 means the permission is denied."""
|
|
216 actions = []
|
|
217 for requestor in self.requestors:
|
|
218 actions += list(requestor.get_permission_actions())
|
|
219 permissions = {}
|
|
220 if username:
|
|
221 # Return all permissions that the given user has
|
|
222 meta = {}
|
|
223 for action in actions:
|
|
224 if isinstance(action, tuple):
|
|
225 name, value = action
|
|
226 meta[name] = value
|
|
227 def _expand_meta(action):
|
|
228 permissions[action] = True
|
|
229 if meta.has_key(action):
|
|
230 [_expand_meta(perm) for perm in meta[action]]
|
|
231 for perm in self.store.get_user_permissions(username):
|
|
232 _expand_meta(perm)
|
|
233 else:
|
|
234 # Return all permissions available in the system
|
|
235 for action in actions:
|
|
236 if isinstance(action, tuple):
|
|
237 permissions[action[0]] = True
|
|
238 else:
|
|
239 permissions[action] = True
|
|
240 return permissions
|
|
241
|
|
242 def get_all_permissions(self):
|
|
243 """Return all permissions for all users.
|
|
244
|
|
245 The permissions are returned as a list of (subject, action)
|
|
246 formatted tuples."""
|
|
247 return self.store.get_all_permissions()
|
|
248
|
|
249 # IPermissionRequestor methods
|
|
250
|
|
251 def get_permission_actions(self):
|
|
252 """Implement the global `TRAC_ADMIN` meta permission."""
|
|
253 actions = []
|
|
254 for requestor in [r for r in self.requestors if r is not self]:
|
|
255 for action in requestor.get_permission_actions():
|
|
256 if isinstance(action, tuple):
|
|
257 actions.append(action[0])
|
|
258 else:
|
|
259 actions.append(action)
|
|
260 return [('TRAC_ADMIN', actions)]
|
|
261
|
|
262
|
|
263 class PermissionCache(object):
|
|
264 """Cache that maintains the permissions of a single user."""
|
|
265
|
|
266 def __init__(self, env, username):
|
|
267 self.perms = PermissionSystem(env).get_user_permissions(username)
|
|
268
|
|
269 def has_permission(self, action):
|
|
270 return self.perms.has_key(action)
|
|
271
|
|
272 def assert_permission(self, action):
|
|
273 if not self.perms.has_key(action):
|
|
274 raise PermissionError(action)
|
|
275
|
|
276 def permissions(self):
|
|
277 return self.perms.keys()
|
|
278
|
|
279
|
|
280 class NoPermissionCache(object):
|
|
281 """Permission cache for ''anonymous requests''."""
|
|
282
|
|
283 def has_permission(self, action):
|
|
284 return False
|
|
285
|
|
286 def assert_permission(self, action):
|
|
287 raise PermissionError(action)
|
|
288
|
|
289 def permissions(self):
|
|
290 return []
|