Package trac :: Module perm

Source Code for Module trac.perm

  1  # -*- coding: utf-8 -*- 
  2  # 
  3  # Copyright (C) 2003-2020 Edgewall Software 
  4  # Copyright (C) 2003-2004 Jonas Borgström <[email protected]> 
  5  # Copyright (C) 2005 Christopher Lenz <[email protected]> 
  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 https://trac.edgewall.org/wiki/TracLicense. 
 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 https://trac.edgewall.org/log/. 
 15  # 
 16  # Author: Jonas Borgström <[email protected]> 
 17  #         Christopher Lenz <[email protected]> 
 18   
 19  from __future__ import with_statement 
 20   
 21  import csv 
 22  import os 
 23   
 24  from trac.admin import AdminCommandError, IAdminCommandProvider, get_dir_list 
 25  from trac.cache import cached 
 26  from trac.config import ExtensionOption, OrderedExtensionsOption 
 27  from trac.core import * 
 28  from trac.resource import Resource, get_resource_name 
 29  from trac.util import file_or_std 
 30  from trac.util.datefmt import time_now 
 31  from trac.util.text import path_to_unicode, print_table, printout, \ 
 32                             stream_encoding, to_unicode, wrap 
 33  from trac.util.translation import _, N_ 
 34   
 35  __all__ = ['IPermissionRequestor', 'IPermissionStore', 'IPermissionPolicy', 
 36             'IPermissionGroupProvider', 'PermissionError', 'PermissionSystem'] 
37 38 39 -class PermissionError(StandardError):
40 """Insufficient permissions to perform the operation. 41 42 :since 1.0.5: the `msg` attribute is deprecated and will be removed in 43 1.3.1. Use the `message` property instead. 44 """ 45 46 title = N_("Forbidden") 47
48 - def __init__(self, action=None, resource=None, env=None, msg=None):
49 self.action = action 50 self.resource = resource 51 self.env = env 52 if self.action: 53 if self.resource: 54 msg = _("%(perm)s privileges are required to perform " 55 "this operation on %(resource)s. You don't have the " 56 "required permissions.", 57 perm=self.action, 58 resource=get_resource_name(self.env, self.resource)) 59 else: 60 msg = _("%(perm)s privileges are required to perform this " 61 "operation. You don't have the required " 62 "permissions.", perm=self.action) 63 elif msg is None: 64 msg = _("Insufficient privileges to perform this operation.") 65 self.msg = msg 66 super(PermissionError, self).__init__(msg)
67 68 @property
69 - def message(self):
70 return self.args[0]
71
72 73 -class IPermissionRequestor(Interface):
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
86 87 -class IPermissionStore(Interface):
88 """Extension point interface for components that provide storage and 89 management of permissions.""" 90
91 - def get_user_permissions(username):
92 """Return all permissions for the user with the specified name. 93 94 The permissions are returned as a dictionary where the key is the name 95 of the permission, and the value is either `True` for granted 96 permissions or `False` for explicitly denied permissions."""
97
98 - def get_users_with_permissions(permissions):
99 """Retrieve a list of users that have any of the specified permissions. 100 101 Users are returned as a list of usernames. 102 """
103
105 """Return all permissions for all users. 106 107 The permissions are returned as a list of (subject, action) 108 formatted tuples."""
109
110 - def grant_permission(username, action):
111 """Grant a user permission to perform an action."""
112
113 - def revoke_permission(username, action):
114 """Revokes the permission of the given user to perform an action."""
115
116 117 -class IPermissionGroupProvider(Interface):
118 """Extension point interface for components that provide information about 119 user groups. 120 """ 121
122 - def get_permission_groups(username):
123 """Return a list of names of the groups that the user with the specified 124 name is a member of."""
125
126 127 -class IPermissionPolicy(Interface):
128 """A security policy provider used for fine grained permission checks.""" 129
130 - def check_permission(action, username, resource, perm):
131 """Check that the action can be performed by username on the resource 132 133 :param action: the name of the permission 134 :param username: the username string or 'anonymous' if there's no 135 authenticated user 136 :param resource: the resource on which the check applies. 137 Will be `None`, if the check is a global one and 138 not made on a resource in particular 139 :param perm: the permission cache for that username and resource, 140 which can be used for doing secondary checks on other 141 permissions. Care must be taken to avoid recursion. 142 143 :return: `True` if action is allowed, `False` if action is denied, 144 or `None` if indifferent. If `None` is returned, the next 145 policy in the chain will be used, and so on. 146 147 Note that when checking a permission on a realm resource (i.e. when 148 `.id` is `None`), this usually corresponds to some preliminary check 149 done before making a fine-grained check on some resource. 150 Therefore the `IPermissionPolicy` should be conservative and return: 151 152 * `True` if the action *can* be allowed for some resources in 153 that realm. Later, for specific resource, the policy will be able 154 to return `True` (allow), `False` (deny) or `None` (don't decide). 155 * `None` if the action *can not* be performed for *some* resources. 156 This corresponds to situation where the policy is only interested 157 in returning `False` or `None` on specific resources. 158 * `False` if the action *can not* be performed for *any* resource in 159 that realm (that's a very strong decision as that will usually 160 prevent any fine-grained check to even happen). 161 162 Note that performing permission checks on realm resources may seem 163 redundant for now as the action name itself contains the realm, but 164 this will probably change in the future (e.g. `'VIEW' in ...`). 165 """
166
167 168 -class DefaultPermissionStore(Component):
169 """Default implementation of permission storage and group management. 170 171 This component uses the `permission` table in the database to store both 172 permissions and groups. 173 """ 174 implements(IPermissionStore) 175 176 group_providers = ExtensionPoint(IPermissionGroupProvider) 177
178 - def get_user_permissions(self, username):
179 """Retrieve the permissions for the given user and return them in a 180 dictionary. 181 182 The permissions are stored in the database as (username, action) 183 records. There's simple support for groups by using lowercase names for 184 the action column: such a record represents a group and not an actual 185 permission, and declares that the user is part of that group. 186 """ 187 subjects = set([username]) 188 for provider in self.group_providers: 189 subjects.update(provider.get_permission_groups(username) or []) 190 191 actions = set() 192 perms = self._all_permissions 193 while True: 194 num_users = len(subjects) 195 num_actions = len(actions) 196 for user, action in perms: 197 if user in subjects: 198 if action.isupper() and action not in actions: 199 actions.add(action) 200 if not action.isupper() and action not in subjects: 201 # action is actually the name of the permission 202 # group here 203 subjects.add(action) 204 if num_users == len(subjects) and num_actions == len(actions): 205 break 206 return list(actions)
207
208 - def get_users_with_permissions(self, permissions):
209 """Retrieve a list of users that have any of the specified permissions 210 211 Users are returned as a list of usernames. 212 """ 213 # get_user_permissions() takes care of the magic 'authenticated' group. 214 # The optimized loop we had before didn't. This is very inefficient, 215 # but it works. 216 result = set() 217 users = set([u[0] for u in self.env.get_known_users()]) 218 for user in users: 219 userperms = self.get_user_permissions(user) 220 for group in permissions: 221 if group in userperms: 222 result.add(user) 223 return list(result)
224
225 - def get_all_permissions(self):
226 """Return all permissions for all users. 227 228 The permissions are returned as a list of (subject, action) 229 formatted tuples.""" 230 return self._all_permissions
231 232 233 @cached
234 - def _all_permissions(self):
235 return [(username, action) for username, action in 236 self.env.db_query("SELECT username, action FROM permission")]
237
238 - def grant_permission(self, username, action):
239 """Grants a user the permission to perform the specified action.""" 240 self.env.db_transaction("INSERT INTO permission VALUES (%s, %s)", 241 (username, action)) 242 self.log.info("Granted permission for %s to %s", action, username) 243 244 # Invalidate cached property 245 del self._all_permissions
246
247 - def revoke_permission(self, username, action):
248 """Revokes a users' permission to perform the specified action.""" 249 self.env.db_transaction( 250 "DELETE FROM permission WHERE username=%s AND action=%s", 251 (username, action)) 252 self.log.info("Revoked permission for %s to %s", action, username) 253 254 # Invalidate cached property 255 del self._all_permissions
256
257 258 -class DefaultPermissionGroupProvider(Component):
259 """Permission group provider providing the basic builtin permission groups 260 'anonymous' and 'authenticated'.""" 261 262 required = True 263 264 implements(IPermissionGroupProvider) 265
266 - def get_permission_groups(self, username):
267 groups = ['anonymous'] 268 if username and username != 'anonymous': 269 groups.append('authenticated') 270 return groups
271
272 273 -class DefaultPermissionPolicy(Component):
274 """Default permission policy using the IPermissionStore system.""" 275 276 implements(IPermissionPolicy) 277 278 # Number of seconds a cached user permission set is valid for. 279 CACHE_EXPIRY = 5 280 # How frequently to clear the entire permission cache 281 CACHE_REAP_TIME = 60 282
283 - def __init__(self):
284 self.permission_cache = {} 285 self.last_reap = time_now()
286 287 # IPermissionPolicy methods 288
289 - def check_permission(self, action, username, resource, perm):
290 now = time_now() 291 292 if now - self.last_reap > self.CACHE_REAP_TIME: 293 self.permission_cache = {} 294 self.last_reap = time_now() 295 296 timestamp, permissions = self.permission_cache.get(username, (0, None)) 297 298 # Cache hit? 299 if now - timestamp > self.CACHE_EXPIRY: 300 # No, pull permissions from database. 301 permissions = PermissionSystem(self.env). \ 302 get_user_permissions(username) 303 self.permission_cache[username] = (now, permissions) 304 305 return action in permissions or None
306
307 308 309 -class PermissionSystem(Component):
310 """Permission management sub-system.""" 311 312 required = True 313 314 implements(IPermissionRequestor) 315 316 requestors = ExtensionPoint(IPermissionRequestor) 317 318 store = ExtensionOption('trac', 'permission_store', IPermissionStore, 319 'DefaultPermissionStore', 320 """Name of the component implementing `IPermissionStore`, which is used 321 for managing user and group permissions.""") 322 323 policies = OrderedExtensionsOption('trac', 'permission_policies', 324 IPermissionPolicy, 325 'DefaultPermissionPolicy, LegacyAttachmentPolicy', 326 False, 327 """List of components implementing `IPermissionPolicy`, in the order in 328 which they will be applied. These components manage fine-grained access 329 control to Trac resources. 330 Defaults to the DefaultPermissionPolicy (pre-0.11 behavior) and 331 LegacyAttachmentPolicy (map ATTACHMENT_* permissions to realm specific 332 ones)""") 333 334 # Number of seconds a cached user permission set is valid for. 335 CACHE_EXPIRY = 5 336 # How frequently to clear the entire permission cache 337 CACHE_REAP_TIME = 60 338
339 - def __init__(self):
340 self.permission_cache = {} 341 self.last_reap = time_now()
342 343 # Public API 344
345 - def grant_permission(self, username, action):
346 """Grant the user with the given name permission to perform to specified 347 action.""" 348 if action.isupper() and action not in self.get_actions(): 349 raise TracError(_('%(name)s is not a valid action.', name=action)) 350 351 self.store.grant_permission(username, action)
352
353 - def revoke_permission(self, username, action):
354 """Revokes the permission of the specified user to perform an action.""" 355 self.store.revoke_permission(username, action)
356
357 - def get_actions_dict(self, skip=None):
358 """Get all actions from permission requestors as a `dict`. 359 360 The keys are the action names. The values are the additional actions 361 granted by each action. For simple actions, this is an empty list. 362 For meta actions, this is the list of actions covered by the action. 363 364 :since 1.0.17: added `skip` argument. 365 """ 366 actions = {} 367 for requestor in self.requestors: 368 if requestor is skip: 369 continue 370 for action in requestor.get_permission_actions() or []: 371 if isinstance(action, tuple): 372 actions.setdefault(action[0], []).extend(action[1]) 373 else: 374 actions.setdefault(action, []) 375 return actions
376
377 - def get_actions(self, skip=None):
378 """Get a list of all actions defined by permission requestors.""" 379 actions = set() 380 for requestor in self.requestors: 381 if requestor is skip: 382 continue 383 for action in requestor.get_permission_actions() or []: 384 if isinstance(action, tuple): 385 actions.add(action[0]) 386 else: 387 actions.add(action) 388 return list(actions)
389
390 - def get_user_permissions(self, username=None):
391 """Return the permissions of the specified user. 392 393 The return value is a dictionary containing all the actions granted to 394 the user mapped to `True`. If an action is missing as a key, or has 395 `False` as a value, permission is denied.""" 396 if not username: 397 # Return all permissions available in the system 398 return dict.fromkeys(self.get_actions(), True) 399 400 # Return all permissions that the given user has 401 actions = self.get_actions_dict() 402 permissions = {} 403 def expand_meta(action): 404 if action not in permissions: 405 permissions[action] = True 406 for a in actions.get(action, ()): 407 expand_meta(a)
408 for perm in self.store.get_user_permissions(username) or []: 409 expand_meta(perm) 410 return permissions
411
412 - def get_all_permissions(self):
413 """Return all permissions for all users. 414 415 The permissions are returned as a list of (subject, action) 416 formatted tuples.""" 417 return self.store.get_all_permissions() or []
418
419 - def get_users_with_permission(self, permission):
420 """Return all users that have the specified permission. 421 422 Users are returned as a list of user names. 423 """ 424 now = time_now() 425 if now - self.last_reap > self.CACHE_REAP_TIME: 426 self.permission_cache = {} 427 self.last_reap = now 428 timestamp, permissions = self.permission_cache.get(permission, 429 (0, None)) 430 if now - timestamp <= self.CACHE_EXPIRY: 431 return permissions 432 433 parent_map = {} 434 for parent, children in self.get_actions_dict().iteritems(): 435 for child in children: 436 parent_map.setdefault(child, set()).add(parent) 437 438 satisfying_perms = set() 439 def append_with_parents(action): 440 if action not in satisfying_perms: 441 satisfying_perms.add(action) 442 for action in parent_map.get(action, ()): 443 append_with_parents(action)
444 append_with_parents(permission) 445 446 perms = self.store.get_users_with_permissions(satisfying_perms) or [] 447 self.permission_cache[permission] = (now, perms) 448 return perms 449
450 - def expand_actions(self, actions):
451 """Helper method for expanding all meta actions.""" 452 all_actions = self.get_actions_dict() 453 expanded_actions = set() 454 def expand_action(action): 455 if action not in expanded_actions: 456 expanded_actions.add(action) 457 for a in all_actions.get(action, ()): 458 expand_action(a)
459 for a in actions: 460 expand_action(a) 461 return expanded_actions 462
463 - def check_permission(self, action, username=None, resource=None, perm=None):
464 """Return True if permission to perform action for the given resource 465 is allowed.""" 466 if username is None: 467 username = 'anonymous' 468 if resource and resource.realm is None: 469 resource = None 470 for policy in self.policies: 471 decision = policy.check_permission(action, username, resource, 472 perm) 473 if decision is not None: 474 self.log.debug("%s %s %s performing %s on %r", 475 policy.__class__.__name__, 476 'allows' if decision else 'denies', 477 username, action, resource) 478 return decision 479 self.log.debug("No policy allowed %s performing %s on %r", 480 username, action, resource) 481 return False
482 483 # IPermissionRequestor methods 484
485 - def get_permission_actions(self):
486 """Implement the global `TRAC_ADMIN` meta permission. 487 488 Implements also the `EMAIL_VIEW` permission which allows for 489 showing email addresses even if `[trac] show_email_addresses` 490 is `false`. 491 """ 492 actions = self.get_actions(skip=self) 493 actions.append('EMAIL_VIEW') 494 return [('TRAC_ADMIN', actions), 'EMAIL_VIEW']
495
496 497 -class PermissionCache(object):
498 """Cache that maintains the permissions of a single user. 499 500 Permissions are usually checked using the following syntax: 501 502 'WIKI_MODIFY' in perm 503 504 One can also apply more fine grained permission checks and 505 specify a specific resource for which the permission should be available: 506 507 'WIKI_MODIFY' in perm('wiki', 'WikiStart') 508 509 If there's already a `page` object available, the check is simply: 510 511 'WIKI_MODIFY' in perm(page.resource) 512 513 If instead of a check, one wants to assert that a given permission is 514 available, the following form should be used: 515 516 perm.require('WIKI_MODIFY') 517 518 or 519 520 perm('wiki', 'WikiStart').require('WIKI_MODIFY') 521 522 or 523 524 perm(page.resource).require('WIKI_MODIFY') 525 526 When using `require`, a `PermissionError` exception is raised if the 527 permission is missing. 528 """ 529 530 __slots__ = ('env', 'username', '_resource', '_cache') 531
532 - def __init__(self, env, username=None, resource=None, cache=None, 533 groups=None):
534 self.env = env 535 self.username = username or 'anonymous' 536 self._resource = resource 537 if cache is None: 538 cache = {} 539 self._cache = cache
540
541 - def _normalize_resource(self, realm_or_resource, id, version):
542 if realm_or_resource: 543 return Resource(realm_or_resource, id, version) 544 else: 545 return self._resource
546
547 - def __call__(self, realm_or_resource, id=False, version=False):
548 """Convenience function for using thus: 549 'WIKI_VIEW' in perm(context) 550 or 551 'WIKI_VIEW' in perm(realm, id, version) 552 or 553 'WIKI_VIEW' in perm(resource) 554 555 """ 556 resource = Resource(realm_or_resource, id, version) 557 if resource and self._resource and resource == self._resource: 558 return self 559 else: 560 return PermissionCache(self.env, self.username, resource, 561 self._cache)
562
563 - def has_permission(self, action, realm_or_resource=None, id=False, 564 version=False):
565 resource = self._normalize_resource(realm_or_resource, id, version) 566 return self._has_permission(action, resource)
567
568 - def _has_permission(self, action, resource):
569 key = (self.username, hash(resource), action) 570 cached = self._cache.get(key) 571 if cached: 572 cache_decision, cache_resource = cached 573 if resource == cache_resource: 574 return cache_decision 575 perm = self 576 if resource is not self._resource: 577 perm = PermissionCache(self.env, self.username, resource, 578 self._cache) 579 decision = PermissionSystem(self.env). \ 580 check_permission(action, perm.username, resource, perm) 581 self._cache[key] = (decision, resource) 582 return decision
583 584 __contains__ = has_permission 585
586 - def require(self, action, realm_or_resource=None, id=False, version=False, 587 message=None):
588 resource = self._normalize_resource(realm_or_resource, id, version) 589 if not self._has_permission(action, resource): 590 if message is None: 591 raise PermissionError(action, resource, self.env) 592 else: 593 raise PermissionError(msg=message)
594 assert_permission = require 595
596 - def permissions(self):
597 """Deprecated (but still used by the HDF compatibility layer)""" 598 self.env.log.warning("perm.permissions() is deprecated and " 599 "is only present for HDF compatibility") 600 perm = PermissionSystem(self.env) 601 actions = perm.get_user_permissions(self.username) 602 return [action for action in actions if action in self]
603
604 605 -class PermissionAdmin(Component):
606 """trac-admin command provider for permission system administration.""" 607 608 implements(IAdminCommandProvider) 609 610 # IAdminCommandProvider methods 611
612 - def get_admin_commands(self):
613 yield ('permission list', '[user]', 614 'List permission rules', 615 self._complete_list, self._do_list) 616 yield ('permission add', '<user> <action> [action] [...]', 617 'Add a new permission rule', 618 self._complete_add, self._do_add) 619 yield ('permission remove', '<user> <action> [action] [...]', 620 'Remove a permission rule', 621 self._complete_remove, self._do_remove) 622 yield ('permission export', '[file]', 623 'Export permission rules to a file or stdout as CSV', 624 self._complete_import_export, self._do_export) 625 yield ('permission import', '[file]', 626 'Import permission rules from a file or stdin as CSV', 627 self._complete_import_export, self._do_import)
628
629 - def get_user_list(self):
630 return set(user for (user, action) in 631 PermissionSystem(self.env).get_all_permissions())
632
633 - def get_user_perms(self, user):
634 return [action for (subject, action) in 635 PermissionSystem(self.env).get_all_permissions() 636 if subject == user]
637
638 - def _complete_list(self, args):
639 if len(args) == 1: 640 return self.get_user_list()
641
642 - def _complete_add(self, args):
643 if len(args) == 1: 644 return self.get_user_list() 645 elif len(args) >= 2: 646 return (set(PermissionSystem(self.env).get_actions()) 647 - set(self.get_user_perms(args[0])) - set(args[1:-1]))
648
649 - def _complete_remove(self, args):
650 if len(args) == 1: 651 return self.get_user_list() 652 elif len(args) >= 2: 653 return set(self.get_user_perms(args[0])) - set(args[1:-1])
654
655 - def _complete_import_export(self, args):
656 if len(args) == 1: 657 return get_dir_list(args[-1])
658
659 - def _do_list(self, user=None):
660 permsys = PermissionSystem(self.env) 661 if user: 662 rows = [] 663 perms = permsys.get_user_permissions(user) 664 for action in perms: 665 if perms[action]: 666 rows.append((user, action)) 667 else: 668 rows = permsys.get_all_permissions() 669 rows.sort() 670 print_table(rows, [_('User'), _('Action')]) 671 print 672 printout(_("Available actions:")) 673 actions = permsys.get_actions() 674 actions.sort() 675 text = ', '.join(actions) 676 printout(wrap(text, initial_indent=' ', subsequent_indent=' ', 677 linesep='\n')) 678 print
679
680 - def _do_add(self, user, *actions):
681 permsys = PermissionSystem(self.env) 682 if user.isupper(): 683 raise AdminCommandError(_('All upper-cased tokens are reserved ' 684 'for permission names')) 685 for action in actions: 686 try: 687 permsys.grant_permission(user, action) 688 except self.env.db_exc.IntegrityError: 689 printout(_("The user %(user)s already has permission " 690 "%(action)s.", user=user, action=action))
691
692 - def _do_remove(self, user, *actions):
693 permsys = PermissionSystem(self.env) 694 rows = permsys.get_all_permissions() 695 for action in actions: 696 found = False 697 for u, a in rows: 698 if user in (u, '*') and action in (a, '*'): 699 permsys.revoke_permission(u, a) 700 found = True 701 if not found: 702 if user in self.get_user_list() and \ 703 action in permsys.get_user_permissions(user): 704 msg = _("Cannot remove permission %(action)s for user " 705 "%(user)s. The permission is granted through " 706 "a meta-permission or group.", action=action, 707 user=user) 708 else: 709 msg = _("Cannot remove permission %(action)s for user " 710 "%(user)s. The user has not been granted the " 711 "permission.", action=action, user=user) 712 raise AdminCommandError(msg)
713
714 - def _do_export(self, filename=None):
715 try: 716 with file_or_std(filename, 'wb') as f: 717 encoding = stream_encoding(f) 718 linesep = os.linesep if filename else '\n' 719 writer = csv.writer(f, lineterminator=linesep) 720 users = self.get_user_list() 721 for user in sorted(users): 722 actions = sorted(self.get_user_perms(user)) 723 writer.writerow([s.encode(encoding, 'replace') 724 for s in [user] + actions]) 725 except IOError, e: 726 raise AdminCommandError( 727 _("Cannot export to %(filename)s: %(error)s", 728 filename=path_to_unicode(filename or 'stdout'), 729 error=e.strerror))
730
731 - def _do_import(self, filename=None):
732 permsys = PermissionSystem(self.env) 733 try: 734 with file_or_std(filename, 'rb') as f: 735 encoding = stream_encoding(f) 736 linesep = os.linesep if filename else '\n' 737 reader = csv.reader(f, lineterminator=linesep) 738 for row in reader: 739 if len(row) < 2: 740 raise AdminCommandError( 741 _("Invalid row %(line)d. Expected <user>, " 742 "<action>, [action], [...]", 743 line=reader.line_num)) 744 user = to_unicode(row[0], encoding) 745 actions = [to_unicode(action, encoding) 746 for action in row[1:]] 747 if user.isupper(): 748 raise AdminCommandError( 749 _("Invalid user %(user)s on line %(line)d: All " 750 "upper-cased tokens are reserved for permission " 751 "names.", user=user, line=reader.line_num)) 752 old_actions = self.get_user_perms(user) 753 for action in set(actions) - set(old_actions): 754 permsys.grant_permission(user, action) 755 except csv.Error, e: 756 raise AdminCommandError( 757 _("Cannot import from %(filename)s line %(line)d: %(error)s ", 758 filename=path_to_unicode(filename or 'stdin'), 759 line=reader.line_num, error=e)) 760 except IOError, e: 761 raise AdminCommandError( 762 _("Cannot import from %(filename)s: %(error)s", 763 filename=path_to_unicode(filename or 'stdin'), 764 error=e.strerror))
765