1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 from __future__ import print_function
20
21 import csv
22 import os
23 from itertools import groupby
24
25 from trac.admin import AdminCommandError, IAdminCommandProvider, get_dir_list
26 from trac.cache import cached
27 from trac.config import ExtensionOption, OrderedExtensionsOption
28 from trac.core import *
29 from trac.resource import Resource, get_resource_name
30 from trac.util import file_or_std, lazy
31 from trac.util.datefmt import time_now
32 from trac.util.text import path_to_unicode, print_table, printout, \
33 stream_encoding, to_unicode, wrap
34 from trac.util.translation import _, N_
35
36 __all__ = ['IPermissionRequestor', 'IPermissionStore', 'IPermissionPolicy',
37 'IPermissionGroupProvider', 'PermissionError', 'PermissionSystem']
41 """Insufficient permissions to perform the operation."""
42
43 title = N_("Forbidden")
44
45 - def __init__(self, action=None, resource=None, env=None, msg=None):
46 self.action = action
47 self.resource = resource
48 self.env = env
49 if self.action:
50 if self.resource and self.resource.id:
51 msg = _("%(perm)s privileges are required to perform "
52 "this operation on %(resource)s. You don't have the "
53 "required permissions.",
54 perm=self.action,
55 resource=get_resource_name(self.env, self.resource))
56 else:
57 msg = _("%(perm)s privileges are required to perform this "
58 "operation. You don't have the required "
59 "permissions.", perm=self.action)
60 elif msg is None:
61 msg = _("Insufficient privileges to perform this operation.")
62 super(PermissionError, self).__init__(msg)
63
64 @property
67
70 """Thrown when a unique key constraint is violated."""
71
74 """Extension point interface for components that define actions."""
75
77 """Return a list of actions defined by this component.
78
79 The items in the list may either be simple strings, or
80 `(string, sequence)` tuples. The latter are considered to be "meta
81 permissions" that group several simple actions under one name for
82 convenience, adding to it if another component already defined that
83 name.
84 """
85
88 """Extension point interface for components that provide storage and
89 management of permissions.
90 """
91
93 """Return a list of permissions for the specified user.
94 """
95
97 """Return a list of users that have any of the specified
98 permissions.
99 """
100
102 """Return all permissions for all users.
103
104 The permissions are returned as a list of (subject, action)
105 formatted tuples.
106 """
107
109 """Grant a user permission to perform an action."""
110
112 """Revokes the permission of the given user to perform an action."""
113
116 """Extension point interface for components that provide information
117 about user groups.
118 """
119
121 """Return a list of names of the groups that the user with the
122 specified name is a member of.
123 """
124
127 """A security policy provider used for fine grained permission checks."""
128
130 """Check that the action can be performed by username on the resource
131
132 :param action: the name of the permission
133 :param username: the username string or 'anonymous' if there's no
134 authenticated user
135 :param resource: the resource on which the check applies.
136 Will be `None`, if the check is a global one and
137 not made on a resource in particular
138 :param perm: the permission cache for that username and resource,
139 which can be used for doing secondary checks on other
140 permissions. Care must be taken to avoid recursion.
141
142 :return: `True` if action is allowed, `False` if action is denied,
143 or `None` if indifferent. If `None` is returned, the next
144 policy in the chain will be consulted.
145
146 Note that when checking a permission on a realm resource (i.e. when
147 `.id` is `None`), this usually corresponds to some preliminary check
148 done before making a fine-grained check on some resource.
149 Therefore the `IPermissionPolicy` should be conservative and return:
150
151 * `True` if the action *can* be allowed for some resources in
152 that realm. Later, for specific resource, the policy will be able
153 to return `True` (allow), `False` (deny) or `None` (don't decide).
154 * `None` if the action *can not* be performed for *some* resources.
155 This corresponds to situation where the policy is only interested
156 in returning `False` or `None` on specific resources.
157 * `False` if the action *can not* be performed for *any* resource
158 in that realm (that's a very strong decision as that will usually
159 prevent any fine-grained check to even happen).
160
161 Note that performing permission checks on realm resources may seem
162 redundant for now as the action name itself contains the realm, but
163 this will probably change in the future (e.g. `'VIEW' in ...`).
164 """
165
168 """Default implementation of permission storage and group management.
169
170 This component uses the `permission` table in the database to store both
171 permissions and groups.
172 """
173 implements(IPermissionGroupProvider, IPermissionStore)
174
175 group_providers = ExtensionPoint(IPermissionGroupProvider)
176
177
178
180 """Return a list of names of the groups that the user with the
181 specified name is a member of.
182 """
183 return sorted(self._get_actions_and_groups({username})[1])
184
185
186
188 """Retrieve a list of permissions for the given user.
189
190 The permissions are stored in the database as (username, action)
191 records. There's simple support for groups by using lowercase
192 names for the action column: such a record represents a group and
193 not an actual permission, and declares that the user is part of
194 that group.
195 """
196 subjects = {username}
197 for provider in self.group_providers:
198 if provider is not self:
199 subjects.update(provider.get_permission_groups(username) or [])
200 return sorted(self._get_actions_and_groups(subjects)[0])
201
203 """Retrieve a list of users that have any of the specified
204 permissions
205
206 Users are returned as a list of usernames.
207 """
208
209
210
211 result = set()
212 users = {u[0] for u in self.env.get_known_users()}
213 for user in users:
214 user_perms = self.get_user_permissions(user)
215 for group in permissions:
216 if group in user_perms:
217 result.add(user)
218 return sorted(result)
219
221 """Return all permissions for all users.
222
223 The permissions are returned as a list of (subject, action)
224 formatted tuples.
225 """
226 return self._all_permissions
227
229 """Grants a user the permission to perform the specified action."""
230 self.env.db_transaction("INSERT INTO permission VALUES (%s, %s)",
231 (username, action))
232 self.log.info("Granted permission for %s to %s", action, username)
233
234
235 del self._all_permissions
236
238 """Revokes a users' permission to perform the specified action."""
239 self.env.db_transaction(
240 "DELETE FROM permission WHERE username=%s AND action=%s",
241 (username, action))
242 self.log.info("Revoked permission for %s to %s", action, username)
243
244
245 del self._all_permissions
246
247 @cached
249 return sorted(self.env.db_query("""
250 SELECT username, action FROM permission
251 """))
252
254 """Get actions and groups for `subjects`, an iterable of username
255 and groups that username is a member of.
256 """
257 actions = set()
258 groups = set()
259 perms = self._all_permissions
260 while True:
261 num_users = len(subjects)
262 num_actions = len(actions)
263 for user, action in perms:
264 if user in subjects:
265 if action.isupper():
266 actions.add(action)
267 if not action.isupper():
268 subjects.add(action)
269 groups.add(action)
270 if num_users == len(subjects) and num_actions == len(actions):
271 break
272 return actions, groups
273
276 """Permission group provider providing the basic builtin permission
277 groups 'anonymous' and 'authenticated'.
278 """
279
280 required = True
281
282 implements(IPermissionGroupProvider)
283
285 groups = ['anonymous']
286 if username and username != 'anonymous':
287 groups.append('authenticated')
288 return groups
289
325
328 """Permission management sub-system."""
329
330 required = True
331
332 implements(IPermissionRequestor)
333
334 requestors = ExtensionPoint(IPermissionRequestor)
335 group_providers = ExtensionPoint(IPermissionGroupProvider)
336
337 store = ExtensionOption('trac', 'permission_store', IPermissionStore,
338 'DefaultPermissionStore',
339 """Name of the component implementing `IPermissionStore`, which is
340 used for managing user and group permissions.""")
341
342 policies = OrderedExtensionsOption('trac', 'permission_policies',
343 IPermissionPolicy,
344 'DefaultWikiPolicy, DefaultTicketPolicy, DefaultPermissionPolicy, '
345 'LegacyAttachmentPolicy',
346 False,
347 """List of components implementing `IPermissionPolicy`, in the order
348 in which they will be applied. These components manage fine-grained
349 access control to Trac resources.""")
350
351
352 CACHE_EXPIRY = 5
353
354 CACHE_REAP_TIME = 60
355
357 self.permission_cache = {}
358 self.last_reap = time_now()
359
360
361
363 """Grant the user with the given name permission to perform to
364 specified action.
365
366 :raises PermissionExistsError: if user already has the permission
367 or is a member of the group.
368
369 :since 1.3.1: raises PermissionExistsError rather than IntegrityError
370 """
371 if action.isupper() and action not in self.get_actions():
372 raise TracError(_('%(name)s is not a valid action.', name=action))
373 elif not action.isupper() and action.upper() in self.get_actions():
374 raise TracError(_("Permission %(name)s differs from a defined "
375 "action by casing only, which is not allowed.",
376 name=action))
377
378 try:
379 self.store.grant_permission(username, action)
380 except self.env.db_exc.IntegrityError:
381 if action in self.get_actions():
382 raise PermissionExistsError(
383 _("The user %(user)s already has permission %(action)s.",
384 user=username, action=action))
385 else:
386 raise PermissionExistsError(
387 _("The user %(user)s is already in the group %(group)s.",
388 user=username, group=action))
389
394
396 """Get all actions from permission requestors as a `dict`.
397
398 The keys are the action names. The values are the additional actions
399 granted by each action. For simple actions, this is an empty list.
400 For meta actions, this is the list of actions covered by the action.
401
402 :since 1.0.17: added `skip` argument.
403 """
404 actions = {}
405 for requestor in self.requestors:
406 if requestor is skip:
407 continue
408 for action in requestor.get_permission_actions() or []:
409 if isinstance(action, tuple):
410 actions.setdefault(action[0], []).extend(action[1])
411 else:
412 actions.setdefault(action, [])
413 return actions
414
415 @lazy
418
420 """Get a list of all actions defined by permission requestors."""
421 actions = set()
422 for requestor in self.requestors:
423 if requestor is skip:
424 continue
425 for action in requestor.get_permission_actions() or []:
426 if isinstance(action, tuple):
427 actions.add(action[0])
428 else:
429 actions.add(action)
430 return sorted(actions)
431
433 """Get all groups as a `dict`.
434
435 The keys are the group names. The values are the group members.
436
437 :since: 1.1.3
438 """
439 groups = sorted((p for p in self.get_all_permissions()
440 if not p[1].isupper()), key=lambda p: p[1])
441
442 return {k: sorted(i[0] for i in list(g))
443 for k, g in groupby(groups, key=lambda p: p[1])}
444
446 """Get all users as a `dict`.
447
448 The keys are the user names. The values are the actions possessed
449 by the user.
450
451 :since: 1.1.3
452 """
453 perms = sorted((p for p in self.get_all_permissions()
454 if p[1].isupper()), key=lambda p: p[0])
455
456 return {k: sorted(i[1] for i in list(g))
457 for k, g in groupby(perms, key=lambda p: p[0])}
458
461 """Return the permissions of the specified user.
462
463 The return value is a dictionary containing all the actions
464 granted to the user mapped to `True`.
465
466 :param undefined: if `True`, include actions that are not defined
467 in any of the `IPermissionRequestor` implementations.
468 :param expand_meta: if `True`, expand meta permissions.
469
470 :since 1.3.1: added the `undefined` parameter.
471 :since 1.3.3: added the `expand_meta` parameter.
472 """
473 if not username:
474
475 return dict.fromkeys(self.get_actions(), True)
476
477
478 actions = self.get_actions_dict()
479 user_permissions = self.store.get_user_permissions(username) or []
480 if expand_meta:
481 return {p: True for p in self.expand_actions(user_permissions)
482 if undefined or p in actions}
483 else:
484 return {p: True for p in user_permissions
485 if undefined or p in actions}
486
488 """Return a sorted list of groups that `username` belongs to.
489
490 Groups are recursively expanded such that if `username` is a
491 member of `group1` and `group1` is a member of `group2`, both
492 `group1` and `group2` will be returned.
493
494 :since: 1.3.3
495 """
496 user_groups = set()
497 for provider in self.group_providers:
498 user_groups.update(provider.get_permission_groups(username) or [])
499
500 return sorted(user_groups)
501
503 """Return all permissions for all users.
504
505 The permissions are returned as a list of (subject, action)
506 formatted tuples.
507 """
508 return self.store.get_all_permissions() or []
509
511 """Return all users that have the specified permission.
512
513 Users are returned as a list of user names.
514 """
515 now = time_now()
516 if now - self.last_reap > self.CACHE_REAP_TIME:
517 self.permission_cache = {}
518 self.last_reap = now
519 timestamp, permissions = self.permission_cache.get(permission,
520 (0, None))
521 if now - timestamp <= self.CACHE_EXPIRY:
522 return permissions
523
524 parent_map = {}
525 for parent, children in self.get_actions_dict().iteritems():
526 for child in children:
527 parent_map.setdefault(child, set()).add(parent)
528
529 satisfying_perms = set()
530 def append_with_parents(action):
531 if action not in satisfying_perms:
532 satisfying_perms.add(action)
533 for action in parent_map.get(action, ()):
534 append_with_parents(action)
535 append_with_parents(permission)
536
537 perms = self.store.get_users_with_permissions(satisfying_perms) or []
538 self.permission_cache[permission] = (now, perms)
539 return perms
540
542 """Helper method for expanding all meta actions."""
543 all_actions = self.get_actions_dict()
544 expanded_actions = set()
545 def expand_action(action):
546 if action not in expanded_actions:
547 expanded_actions.add(action)
548 for a in all_actions.get(action, ()):
549 expand_action(a)
550 for a in actions:
551 expand_action(a)
552 return sorted(expanded_actions)
553
554 - def check_permission(self, action, username=None, resource=None,
555 perm=None):
556 """Return True if permission to perform action for the given
557 resource is allowed.
558 """
559 if username is None:
560 username = 'anonymous'
561 if resource and resource.realm is None:
562 resource = None
563 for policy in self.policies:
564 decision = policy.check_permission(action, username, resource,
565 perm)
566 if decision is not None:
567 self.log.debug("%s %s %s performing %s on %r",
568 policy.__class__.__name__,
569 'allows' if decision else 'denies',
570 username, action, resource)
571 return decision
572 self.log.debug("No policy allowed %s performing %s on %r",
573 username, action, resource)
574 return False
575
576
577
579 """Implement the global `TRAC_ADMIN` meta permission.
580 """
581 actions = self.get_actions(skip=self)
582 return [('TRAC_ADMIN', actions)]
583
586 """Cache that maintains the permissions of a single user.
587
588 Permissions are usually checked using the following syntax:
589
590 'WIKI_MODIFY' in perm
591
592 One can also apply more fine grained permission checks and
593 specify a specific resource for which the permission should be available:
594
595 'WIKI_MODIFY' in perm('wiki', 'WikiStart')
596
597 If there's already a `page` object available, the check is simply:
598
599 'WIKI_MODIFY' in perm(page.resource)
600
601 If instead of a check, one wants to assert that a given permission is
602 available, the following form should be used:
603
604 perm.require('WIKI_MODIFY')
605
606 or
607
608 perm('wiki', 'WikiStart').require('WIKI_MODIFY')
609
610 or
611
612 perm(page.resource).require('WIKI_MODIFY')
613
614 When using `require`, a `PermissionError` exception is raised if the
615 permission is missing.
616 """
617
618 __slots__ = ('env', 'username', '_resource', '_cache')
619
620 - def __init__(self, env, username=None, resource=None, cache=None,
621 groups=None):
628
630 if realm_or_resource:
631 return Resource(realm_or_resource, id, version)
632 else:
633 return self._resource
634
635 - def __call__(self, realm_or_resource, id=False, version=False):
636 """Convenience function for using thus:
637 'WIKI_VIEW' in perm(context)
638 or
639 'WIKI_VIEW' in perm(realm, id, version)
640 or
641 'WIKI_VIEW' in perm(resource)
642
643 """
644 resource = Resource(realm_or_resource, id, version) \
645 if realm_or_resource else None
646 if resource and self._resource and resource == self._resource:
647 return self
648 else:
649 return PermissionCache(self.env, self.username, resource,
650 self._cache)
651
652 - def has_permission(self, action, realm_or_resource=None, id=False,
653 version=False):
656
658 key = (self.username, hash(resource), action)
659 cached = self._cache.get(key)
660 if cached:
661 cache_decision, cache_resource = cached
662 if resource == cache_resource:
663 return cache_decision
664
665 self._cache[key] = (False, resource)
666 perm = self
667 if resource is not self._resource:
668 perm = PermissionCache(self.env, self.username, resource,
669 self._cache)
670 decision = PermissionSystem(self.env). \
671 check_permission(action, perm.username, resource, perm)
672 self._cache[key] = (decision, resource)
673 return decision
674
675 __contains__ = has_permission
676
677 - def require(self, action, realm_or_resource=None, id=False, version=False,
678 message=None):
685 assert_permission = require
686
689 """trac-admin command provider for permission system administration."""
690
691 implements(IAdminCommandProvider)
692
693
694
696 yield ('permission list', '[user]',
697 "List permission rules",
698 self._complete_list, self._do_list)
699 yield ('permission add', '<user> <action> [action] [...]',
700 "Add a new permission rule",
701 self._complete_add, self._do_add)
702 yield ('permission remove', '<user> <action> [action] [...]',
703 "Remove a permission rule",
704 self._complete_remove, self._do_remove)
705 yield ('permission export', '[file]',
706 "Export permission rules to a file or stdout as CSV",
707 self._complete_import_export, self._do_export)
708 yield ('permission import', '[file]',
709 "Import permission rules from a file or stdin as CSV",
710 self._complete_import_export, self._do_import)
711
715
720
724
731
737
741
762
763 - def _do_add(self, user, *actions):
780
781
782
783
784 actions_to_grant = list(actions)
785 while actions_to_grant:
786 action_that_failed = grant_actions_atomically(actions_to_grant)
787 if action_that_failed is None:
788 break
789 else:
790 actions_to_grant.