Revision d20081ab

b/snf-pithos-backend/pithos/backends/modular.py
37 37
import hashlib
38 38
import binascii
39 39

  
40
from collections import defaultdict
40 41
from functools import wraps, partial
41 42
from traceback import format_exc
42 43

  
......
164 165
    return wrapper
165 166

  
166 167

  
168
def check_allowed_paths(action):
169
    """Decorator for backend methods checking path access granted to user.
170

  
171
    The 1st argument of the decorated method is expected to be a
172
    ModularBackend instance, the 2nd the user performing the request and
173
    the path join of the rest arguments is supposed to be the requested path.
174

  
175
    The decorator checks whether the requested path is among the user's allowed
176
    cached paths.
177
    If this is the case, the decorator returns immediately to reduce the
178
    interactions with the database.
179
    Otherwise, it proceeds with the execution of the decorated method and if
180
    the method returns successfully (no exceptions are raised), the requested
181
    path is added to the user's cached allowed paths.
182

  
183
    :param action: (int) 0 for reads / 1 for writes
184
    :raises NotAllowedError: the user does not have access to the path
185
    """
186
    def decorator(func):
187
        @wraps(func)
188
        def wrapper(self, *args):
189
            user = args[0]
190
            if action == self.READ:
191
                d = self.read_allowed_paths
192
            else:
193
                d = self.write_allowed_paths
194
            path = '/'.join(args[1:])
195
            if path in d.get(user, []):
196
                return  # access is already checked
197
            else:
198
                func(self, *args)   # proceed with access check
199
                d[user].add(path)  # add path in the allowed user paths
200
        return wrapper
201
    return decorator
202

  
203

  
167 204
def list_method(func):
168 205
    @wraps(func)
169 206
    def wrapper(self, *args, **kw):
......
293 330

  
294 331
        self.in_transaction = False
295 332

  
333
        self._reset_allowed_paths()
334

  
296 335
    def pre_exec(self, lock_container_path=False):
297 336
        self.lock_container_path = lock_container_path
298 337
        self.wrapper.execute()
299 338
        self.serials = []
339
        self._reset_allowed_paths()
300 340
        self.in_transaction = True
301 341

  
302 342
    def post_exec(self, success_status=True):
......
363 403
                         include_user_defined=True):
364 404
        """Return a dictionary with the account metadata for the domain."""
365 405

  
406
        self._can_read_account(user, account)
366 407
        path, node = self._lookup_account(account, user == account)
367 408
        if user != account:
368
            if until or (node is None) or (account not
369
                                           in self._allowed_accounts(user)):
409
            if until or (node is None):
370 410
                raise NotAllowedError
371 411
        try:
372 412
            props = self._get_properties(node, until)
......
404 444
    def update_account_meta(self, user, account, domain, meta, replace=False):
405 445
        """Update the metadata associated with the account for the domain."""
406 446

  
407
        if user != account:
408
            raise NotAllowedError
447
        self._can_write_account(user, account)
409 448
        path, node = self._lookup_account(account, True)
410 449
        self._put_metadata(user, node, domain, meta, replace,
411 450
                           update_statistics_ancestors_depth=-1)
......
415 454
    def get_account_groups(self, user, account):
416 455
        """Return a dictionary with the user groups defined for the account."""
417 456

  
457
        self._can_read_account(user, account)
418 458
        if user != account:
419
            if account not in self._allowed_accounts(user):
420
                raise NotAllowedError
421 459
            return {}
422 460
        self._lookup_account(account, True)
423 461
        return self.permissions.group_dict(account)
......
427 465
    def update_account_groups(self, user, account, groups, replace=False):
428 466
        """Update the groups associated with the account."""
429 467

  
430
        if user != account:
431
            raise NotAllowedError
468
        self._can_write_account(user, account)
432 469
        self._lookup_account(account, True)
433 470
        self._check_groups(groups)
434 471
        if replace:
......
444 481
    def get_account_policy(self, user, account):
445 482
        """Return a dictionary with the account policy."""
446 483

  
484
        self._can_read_account(user, account)
447 485
        if user != account:
448
            if account not in self._allowed_accounts(user):
449
                raise NotAllowedError
450 486
            return {}
451 487
        path, node = self._lookup_account(account, True)
452 488
        policy = self._get_policy(node, is_account_policy=True)
......
460 496
    def update_account_policy(self, user, account, policy, replace=False):
461 497
        """Update the policy associated with the account."""
462 498

  
463
        if user != account:
464
            raise NotAllowedError
499
        self._can_write_account(user, account)
465 500
        path, node = self._lookup_account(account, True)
466 501
        self._check_policy(policy, is_account_policy=True)
467 502
        self._put_policy(node, policy, replace, is_account_policy=True)
......
472 507
        """Create a new account with the given name."""
473 508

  
474 509
        policy = policy or {}
475
        if user != account:
476
            raise NotAllowedError
510
        self._can_write_account(user, account)
477 511
        node = self.node.node_lookup(account)
478 512
        if node is not None:
479 513
            raise AccountExists('Account already exists')
......
488 522
    def delete_account(self, user, account):
489 523
        """Delete the account with the given name."""
490 524

  
491
        if user != account:
492
            raise NotAllowedError
525
        self._can_write_account(user, account)
493 526
        node = self.node.node_lookup(account)
494 527
        if node is None:
495 528
            return
......
498 531
            raise AccountNotEmpty('Account is not empty')
499 532
        self.permissions.group_destroy(account)
500 533

  
534
        # remove all the cached allowed paths
535
        # removing the specific path could be more expensive
536
        self._reset_allowed_paths()
537

  
501 538
    @debug_method
502 539
    @backend_method
503 540
    @list_method
......
505 542
                        shared=False, until=None, public=False):
506 543
        """Return a list of containers existing under an account."""
507 544

  
545
        self._can_read_account(user, account)
508 546
        if user != account:
509
            if until or account not in self._allowed_accounts(user):
547
            if until:
510 548
                raise NotAllowedError
511 549
            return self._allowed_containers(user, account)
512 550
        if shared or public:
......
528 566
                            until=None):
529 567
        """Return a list of the container's object meta keys for a domain."""
530 568

  
569
        self._can_read_container(user, account, container)
531 570
        allowed = []
532 571
        if user != account:
533 572
            if until:
534 573
                raise NotAllowedError
535
            allowed = self.permissions.access_list_paths(
536
                user, '/'.join((account, container)))
537
            if not allowed:
538
                raise NotAllowedError
539 574
        path, node = self._lookup_container(account, container)
540 575
        before = until if until is not None else inf
541 576
        allowed = self._get_formatted_paths(allowed)
......
548 583
                           include_user_defined=True):
549 584
        """Return a dictionary with the container metadata for the domain."""
550 585

  
586
        self._can_read_container(user, account, container)
551 587
        if user != account:
552
            if until or container not in self._allowed_containers(user,
553
                                                                  account):
588
            if until:
554 589
                raise NotAllowedError
555 590
        path, node = self._lookup_container(account, container)
556 591
        props = self._get_properties(node, until)
......
583 618
                              replace=False):
584 619
        """Update the metadata associated with the container for the domain."""
585 620

  
586
        if user != account:
587
            raise NotAllowedError
621
        self._can_write_container(user, account, container)
588 622
        path, node = self._lookup_container(account, container)
589 623
        src_version_id, dest_version_id = self._put_metadata(
590 624
            user, node, domain, meta, replace,
......
601 635
    def get_container_policy(self, user, account, container):
602 636
        """Return a dictionary with the container policy."""
603 637

  
638
        self._can_read_container(user, account, container)
604 639
        if user != account:
605
            if container not in self._allowed_containers(user, account):
606
                raise NotAllowedError
607 640
            return {}
608 641
        path, node = self._lookup_container(account, container)
609 642
        return self._get_policy(node, is_account_policy=False)
......
614 647
                                replace=False):
615 648
        """Update the policy associated with the container."""
616 649

  
617
        if user != account:
618
            raise NotAllowedError
650
        self._can_write_container(user, account, container)
619 651
        path, node = self._lookup_container(account, container)
620 652
        self._check_policy(policy, is_account_policy=False)
621 653
        self._put_policy(node, policy, replace, is_account_policy=False)
......
626 658
        """Create a new container with the given name."""
627 659

  
628 660
        policy = policy or {}
629
        if user != account:
630
            raise NotAllowedError
661
        self._can_write_container(user, account, container)
631 662
        try:
632 663
            path, node = self._lookup_container(account, container)
633 664
        except NameError:
......
648 679
                         delimiter=None):
649 680
        """Delete/purge the container with the given name."""
650 681

  
651
        if user != account:
652
            raise NotAllowedError
682
        self._can_write_container(user, account, container)
653 683
        path, node = self._lookup_container(account, container)
654 684

  
655 685
        if until is not None:
......
718 748
                paths.append(path)
719 749
            self.permissions.access_clear_bulk(paths)
720 750

  
751
        # remove all the cached allowed paths
752
        # removing the specific path could be more expensive
753
        self._reset_allowed_paths()
754

  
721 755
    def _list_objects(self, user, account, container, prefix, delimiter,
722 756
                      marker, limit, virtual, domain, keys, shared, until,
723 757
                      size_range, all_props, public):
......
878 912
                        version=None, include_user_defined=True):
879 913
        """Return a dictionary with the object metadata for the domain."""
880 914

  
881
        self._can_read(user, account, container, name)
915
        self._can_read_object(user, account, container, name)
882 916
        path, node = self._lookup_object(account, container, name)
883 917
        props = self._get_version(node, version)
884 918
        if version is None:
......
916 950
                           replace=False):
917 951
        """Update object metadata for a domain and return the new version."""
918 952

  
919
        self._can_write(user, account, container, name)
953
        self._can_write_object(user, account, container, name)
920 954

  
921 955
        path, node = self._lookup_object(account, container, name,
922 956
                                         lock_container=True)
......
999 1033
            self._report_sharing_change(user, account, path, {'members':
1000 1034
                                        self.permissions.access_members(path)})
1001 1035

  
1036
        # remove all the cached allowed paths
1037
        # filtering out only those affected could be more expensive
1038
        self._reset_allowed_paths()
1039

  
1002 1040
    @debug_method
1003 1041
    @backend_method
1004 1042
    def get_object_public(self, user, account, container, name):
1005 1043
        """Return the public id of the object if applicable."""
1006 1044

  
1007
        self._can_read(user, account, container, name)
1045
        self._can_read_object(user, account, container, name)
1008 1046
        path = self._lookup_object(account, container, name)[0]
1009 1047
        p = self.permissions.public_get(path)
1010 1048
        return p
......
1014 1052
    def update_object_public(self, user, account, container, name, public):
1015 1053
        """Update the public status of the object."""
1016 1054

  
1017
        self._can_write(user, account, container, name)
1055
        self._can_write_object(user, account, container, name)
1018 1056
        path = self._lookup_object(account, container, name,
1019 1057
                                   lock_container=True)[0]
1020 1058
        if not public:
......
1028 1066
    def get_object_hashmap(self, user, account, container, name, version=None):
1029 1067
        """Return the object's size and a list with partial hashes."""
1030 1068

  
1031
        self._can_read(user, account, container, name)
1069
        self._can_read_object(user, account, container, name)
1032 1070
        path, node = self._lookup_object(account, container, name)
1033 1071
        props = self._get_version(node, version)
1034 1072
        if props[self.HASH] is None:
......
1042 1080
                            is_copy=False, report_size_change=True):
1043 1081
        if permissions is not None and user != account:
1044 1082
            raise NotAllowedError
1045
        self._can_write(user, account, container, name)
1083
        self._can_write_object(user, account, container, name)
1046 1084
        if permissions is not None:
1047 1085
            path = '/'.join((account, container, name))
1048 1086
            self._check_permissions(path, permissions)
......
1142 1180

  
1143 1181
        # Update objects with greater version and same hashmap
1144 1182
        # and size (fix metadata updates).
1145
        self._can_write(user, account, container, name)
1183
        self._can_write_object(user, account, container, name)
1146 1184
        path, node = self._lookup_object(account, container, name,
1147 1185
                                         lock_container=True)
1148 1186
        props = self._get_version(node, version)
......
1163 1201
        report_size_change = not is_move
1164 1202
        dest_meta = dest_meta or {}
1165 1203
        dest_version_ids = []
1166
        self._can_read(user, src_account, src_container, src_name)
1204
        self._can_read_object(user, src_account, src_container, src_name)
1167 1205

  
1168 1206
        src_container_path = '/'.join((src_account, src_container))
1169 1207
        dest_container_path = '/'.join((dest_account, dest_container))
......
1353 1391
                paths.append(path)
1354 1392
            self.permissions.access_clear_bulk(paths)
1355 1393

  
1394
        # remove all the cached allowed paths
1395
        # removing the specific path could be more expensive
1396
        self._reset_allowed_paths()
1397

  
1356 1398
    @debug_method
1357 1399
    @backend_method
1358 1400
    def delete_object(self, user, account, container, name, until=None,
......
1366 1408
    def list_versions(self, user, account, container, name):
1367 1409
        """Return a list of all object (version, version_timestamp) tuples."""
1368 1410

  
1369
        self._can_read(user, account, container, name)
1411
        self._can_read_object(user, account, container, name)
1370 1412
        path, node = self._lookup_object(account, container, name)
1371 1413
        versions = self.node.node_get_versions(node)
1372 1414
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if
......
1383 1425
        path, serial = info
1384 1426
        account, container, name = path.split('/', 2)
1385 1427
        if check_permissions:
1386
            self._can_read(user, account, container, name)
1428
            self._can_read_object(user, account, container, name)
1387 1429
        return (account, container, name)
1388 1430

  
1389 1431
    @debug_method
......
1395 1437
        if path is None:
1396 1438
            raise NameError
1397 1439
        account, container, name = path.split('/', 2)
1398
        self._can_read(user, account, container, name)
1440
        self._can_read_object(user, account, container, name)
1399 1441
        return (account, container, name)
1400 1442

  
1401 1443
    def get_block(self, hash):
......
1807 1849

  
1808 1850
        return None
1809 1851

  
1810
    def _can_read(self, user, account, container, name):
1852
    def _reset_allowed_paths(self):
1853
        self.read_allowed_paths = defaultdict(set)
1854
        self.write_allowed_paths = defaultdict(set)
1855

  
1856
    @check_allowed_paths(action=0)
1857
    def _can_read_account(self, user, account):
1858
        if user != account:
1859
            if account not in self._allowed_accounts(user):
1860
                raise NotAllowedError
1861

  
1862
    @check_allowed_paths(action=1)
1863
    def _can_write_account(self, user, account):
1864
        if user != account:
1865
            raise NotAllowedError
1866

  
1867
    @check_allowed_paths(action=0)
1868
    def _can_read_container(self, user, account, container):
1869
        if user != account:
1870
            if container not in self._allowed_containers(user, account):
1871
                raise NotAllowedError
1872

  
1873
    @check_allowed_paths(action=1)
1874
    def _can_write_container(self, user, account, container):
1875
        if user != account:
1876
            raise NotAllowedError
1877

  
1878
    @check_allowed_paths(action=0)
1879
    def _can_read_object(self, user, account, container, name):
1811 1880
        if user == account:
1812 1881
            return True
1813 1882
        path = '/'.join((account, container, name))
......
1820 1889
                self.permissions.access_check(path, self.WRITE, user)):
1821 1890
            raise NotAllowedError
1822 1891

  
1823
    def _can_write(self, user, account, container, name):
1892
    @check_allowed_paths(action=1)
1893
    def _can_write_object(self, user, account, container, name):
1824 1894
        if user == account:
1825 1895
            return True
1826 1896
        path = '/'.join((account, container, name))
......
1833 1903
    def _allowed_accounts(self, user):
1834 1904
        allow = set()
1835 1905
        for path in self.permissions.access_list_paths(user):
1836
            allow.add(path.split('/', 1)[0])
1906
            p = path.split('/', 1)[0]
1907
            allow.add(p)
1908
        self.read_allowed_paths[user] |= allow
1837 1909
        return sorted(allow)
1838 1910

  
1839 1911
    def _allowed_containers(self, user, account):
1840 1912
        allow = set()
1841 1913
        for path in self.permissions.access_list_paths(user, account):
1842
            allow.add(path.split('/', 2)[1])
1914
            p = path.split('/', 2)[1]
1915
            allow.add(p)
1916
        self.read_allowed_paths[user] |= allow
1843 1917
        return sorted(allow)
1844 1918

  
1845 1919
    # Domain functions

Also available in: Unified diff