Revision 0d573e18

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

  
204
def list_method(func):
205
    @wraps(func)
206
    def wrapper(self, *args, **kw):
207
        marker = kw.get('marker')
208
        limit = kw.get('limit')
209
        result = func(self, *args, **kw)
210
        start, limit = self._list_limits(result, marker, limit)
211
        return result[start:start + limit]
212
    return wrapper
213

  
214

  
167 215
class ModularBackend(BaseBackend):
168 216
    """A modular backend.
169 217

  
......
282 330

  
283 331
        self.in_transaction = False
284 332

  
333
        self._reset_allowed_paths()
334

  
285 335
    def pre_exec(self, lock_container_path=False):
286 336
        self.lock_container_path = lock_container_path
287 337
        self.wrapper.execute()
288 338
        self.serials = []
339
        self._reset_allowed_paths()
289 340
        self.in_transaction = True
290 341

  
291 342
    def post_exec(self, success_status=True):
......
353 404
                         include_user_defined=True):
354 405
        """Return a dictionary with the account metadata for the domain."""
355 406

  
407
        self._can_read_account(user, account)
356 408
        path, node = self._lookup_account(account, user == account)
357 409
        if user != account:
358
            if until or (node is None) or (account not
359
                                           in self._allowed_accounts(user)):
410
            if until or (node is None):
360 411
                raise NotAllowedError
361 412
        try:
362 413
            props = self._get_properties(node, until)
......
394 445
    def update_account_meta(self, user, account, domain, meta, replace=False):
395 446
        """Update the metadata associated with the account for the domain."""
396 447

  
397
        if user != account:
398
            raise NotAllowedError
448
        self._can_write_account(user, account)
399 449
        path, node = self._lookup_account(account, True)
400 450
        self._put_metadata(user, node, domain, meta, replace,
401 451
                           update_statistics_ancestors_depth=-1)
......
405 455
    def get_account_groups(self, user, account):
406 456
        """Return a dictionary with the user groups defined for the account."""
407 457

  
458
        self._can_read_account(user, account)
408 459
        if user != account:
409
            if account not in self._allowed_accounts(user):
410
                raise NotAllowedError
411 460
            return {}
412 461
        self._lookup_account(account, True)
413 462
        return self.permissions.group_dict(account)
......
417 466
    def update_account_groups(self, user, account, groups, replace=False):
418 467
        """Update the groups associated with the account."""
419 468

  
420
        if user != account:
421
            raise NotAllowedError
469
        self._can_write_account(user, account)
422 470
        self._lookup_account(account, True)
423 471
        self._check_groups(groups)
424 472
        if replace:
......
434 482
    def get_account_policy(self, user, account):
435 483
        """Return a dictionary with the account policy."""
436 484

  
485
        self._can_read_account(user, account)
437 486
        if user != account:
438
            if account not in self._allowed_accounts(user):
439
                raise NotAllowedError
440 487
            return {}
441 488
        path, node = self._lookup_account(account, True)
442 489
        policy = self._get_policy(node, is_account_policy=True)
......
450 497
    def update_account_policy(self, user, account, policy, replace=False):
451 498
        """Update the policy associated with the account."""
452 499

  
453
        if user != account:
454
            raise NotAllowedError
500
        self._can_write_account(user, account)
455 501
        path, node = self._lookup_account(account, True)
456 502
        self._check_policy(policy, is_account_policy=True)
457 503
        self._put_policy(node, policy, replace, is_account_policy=True)
......
462 508
        """Create a new account with the given name."""
463 509

  
464 510
        policy = policy or {}
465
        if user != account:
466
            raise NotAllowedError
511
        self._can_write_account(user, account)
467 512
        node = self.node.node_lookup(account)
468 513
        if node is not None:
469 514
            raise AccountExists('Account already exists')
......
478 523
    def delete_account(self, user, account):
479 524
        """Delete the account with the given name."""
480 525

  
481
        if user != account:
482
            raise NotAllowedError
526
        self._can_write_account(user, account)
483 527
        node = self.node.node_lookup(account)
484 528
        if node is None:
485 529
            return
......
488 532
            raise AccountNotEmpty('Account is not empty')
489 533
        self.permissions.group_destroy(account)
490 534

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

  
491 539
    @debug_method
492 540
    @backend_method
493 541
    def list_containers(self, user, account, marker=None, limit=10000,
494 542
                        shared=False, until=None, public=False):
495 543
        """Return a list of containers existing under an account."""
496 544

  
545
        self._can_read_account(user, account)
497 546
        if user != account:
498
            if until or account not in self._allowed_accounts(user):
547
            if until:
499 548
                raise NotAllowedError
500 549
            allowed = self._allowed_containers(user, account)
501 550
            start, limit = self._list_limits(allowed, marker, limit)
......
524 573
                            until=None):
525 574
        """Return a list of the container's object meta keys for a domain."""
526 575

  
576
        self._can_read_container(user, account, container)
527 577
        allowed = []
528 578
        if user != account:
529 579
            if until:
530 580
                raise NotAllowedError
531
            allowed = self.permissions.access_list_paths(
532
                user, '/'.join((account, container)))
533
            if not allowed:
534
                raise NotAllowedError
535 581
        path, node = self._lookup_container(account, container)
536 582
        before = until if until is not None else inf
537 583
        allowed = self._get_formatted_paths(allowed)
......
544 590
                           include_user_defined=True):
545 591
        """Return a dictionary with the container metadata for the domain."""
546 592

  
593
        self._can_read_container(user, account, container)
547 594
        if user != account:
548
            if until or container not in self._allowed_containers(user,
549
                                                                  account):
595
            if until:
550 596
                raise NotAllowedError
551 597
        path, node = self._lookup_container(account, container)
552 598
        props = self._get_properties(node, until)
......
579 625
                              replace=False):
580 626
        """Update the metadata associated with the container for the domain."""
581 627

  
582
        if user != account:
583
            raise NotAllowedError
628
        self._can_write_container(user, account, container)
584 629
        path, node = self._lookup_container(account, container)
585 630
        src_version_id, dest_version_id = self._put_metadata(
586 631
            user, node, domain, meta, replace,
......
597 642
    def get_container_policy(self, user, account, container):
598 643
        """Return a dictionary with the container policy."""
599 644

  
645
        self._can_read_container(user, account, container)
600 646
        if user != account:
601
            if container not in self._allowed_containers(user, account):
602
                raise NotAllowedError
603 647
            return {}
604 648
        path, node = self._lookup_container(account, container)
605 649
        return self._get_policy(node, is_account_policy=False)
......
610 654
                                replace=False):
611 655
        """Update the policy associated with the container."""
612 656

  
613
        if user != account:
614
            raise NotAllowedError
657
        self._can_write_container(user, account, container)
615 658
        path, node = self._lookup_container(account, container)
616 659
        self._check_policy(policy, is_account_policy=False)
617 660
        self._put_policy(node, policy, replace, is_account_policy=False)
......
622 665
        """Create a new container with the given name."""
623 666

  
624 667
        policy = policy or {}
625
        if user != account:
626
            raise NotAllowedError
668
        self._can_write_container(user, account, container)
627 669
        try:
628 670
            path, node = self._lookup_container(account, container)
629 671
        except NameError:
......
644 686
                         delimiter=None):
645 687
        """Delete/purge the container with the given name."""
646 688

  
647
        if user != account:
648
            raise NotAllowedError
689
        self._can_write_container(user, account, container)
649 690
        path, node = self._lookup_container(account, container)
650 691

  
651 692
        if until is not None:
......
714 755
                paths.append(path)
715 756
            self.permissions.access_clear_bulk(paths)
716 757

  
758
        # remove all the cached allowed paths
759
        # removing the specific path could be more expensive
760
        self._reset_allowed_paths()
761

  
717 762
    def _list_objects(self, user, account, container, prefix, delimiter,
718 763
                      marker, limit, virtual, domain, keys, shared, until,
719 764
                      size_range, all_props, public):
......
878 923
                        version=None, include_user_defined=True):
879 924
        """Return a dictionary with the object metadata for the domain."""
880 925

  
881
        self._can_read(user, account, container, name)
926
        self._can_read_object(user, account, container, name)
882 927
        path, node = self._lookup_object(account, container, name)
883 928
        props = self._get_version(node, version)
884 929
        if version is None:
......
916 961
                           replace=False):
917 962
        """Update object metadata for a domain and return the new version."""
918 963

  
919
        self._can_write(user, account, container, name)
964
        self._can_write_object(user, account, container, name)
920 965

  
921 966
        path, node = self._lookup_object(account, container, name,
922 967
                                         lock_container=True)
......
999 1044
            self._report_sharing_change(user, account, path, {'members':
1000 1045
                                        self.permissions.access_members(path)})
1001 1046

  
1047
        # remove all the cached allowed paths
1048
        # filtering out only those affected could be more expensive
1049
        self._reset_allowed_paths()
1050

  
1002 1051
    @debug_method
1003 1052
    @backend_method
1004 1053
    def get_object_public(self, user, account, container, name):
1005 1054
        """Return the public id of the object if applicable."""
1006 1055

  
1007
        self._can_read(user, account, container, name)
1056
        self._can_read_object(user, account, container, name)
1008 1057
        path = self._lookup_object(account, container, name)[0]
1009 1058
        p = self.permissions.public_get(path)
1010 1059
        return p
......
1014 1063
    def update_object_public(self, user, account, container, name, public):
1015 1064
        """Update the public status of the object."""
1016 1065

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

  
1031
        self._can_read(user, account, container, name)
1080
        self._can_read_object(user, account, container, name)
1032 1081
        path, node = self._lookup_object(account, container, name)
1033 1082
        props = self._get_version(node, version)
1034 1083
        if props[self.HASH] is None:
......
1042 1091
                            is_copy=False, report_size_change=True):
1043 1092
        if permissions is not None and user != account:
1044 1093
            raise NotAllowedError
1045
        self._can_write(user, account, container, name)
1094
        self._can_write_object(user, account, container, name)
1046 1095
        if permissions is not None:
1047 1096
            path = '/'.join((account, container, name))
1048 1097
            self._check_permissions(path, permissions)
......
1142 1191

  
1143 1192
        # Update objects with greater version and same hashmap
1144 1193
        # and size (fix metadata updates).
1145
        self._can_write(user, account, container, name)
1194
        self._can_write_object(user, account, container, name)
1146 1195
        path, node = self._lookup_object(account, container, name,
1147 1196
                                         lock_container=True)
1148 1197
        props = self._get_version(node, version)
......
1163 1212
        report_size_change = not is_move
1164 1213
        dest_meta = dest_meta or {}
1165 1214
        dest_version_ids = []
1166
        self._can_read(user, src_account, src_container, src_name)
1215
        self._can_read_object(user, src_account, src_container, src_name)
1167 1216

  
1168 1217
        src_container_path = '/'.join((src_account, src_container))
1169 1218
        dest_container_path = '/'.join((dest_account, dest_container))
......
1353 1402
                paths.append(path)
1354 1403
            self.permissions.access_clear_bulk(paths)
1355 1404

  
1405
        # remove all the cached allowed paths
1406
        # removing the specific path could be more expensive
1407
        self._reset_allowed_paths()
1408

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

  
1369
        self._can_read(user, account, container, name)
1422
        self._can_read_object(user, account, container, name)
1370 1423
        path, node = self._lookup_object(account, container, name)
1371 1424
        versions = self.node.node_get_versions(node)
1372 1425
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if
......
1383 1436
        path, serial = info
1384 1437
        account, container, name = path.split('/', 2)
1385 1438
        if check_permissions:
1386
            self._can_read(user, account, container, name)
1439
            self._can_read_object(user, account, container, name)
1387 1440
        return (account, container, name)
1388 1441

  
1389 1442
    @debug_method
......
1395 1448
        if path is None:
1396 1449
            raise NameError
1397 1450
        account, container, name = path.split('/', 2)
1398
        self._can_read(user, account, container, name)
1451
        self._can_read_object(user, account, container, name)
1399 1452
        return (account, container, name)
1400 1453

  
1401 1454
    def get_block(self, hash):
......
1807 1860

  
1808 1861
        return None
1809 1862

  
1810
    def _can_read(self, user, account, container, name):
1863
    def _reset_allowed_paths(self):
1864
        self.read_allowed_paths = defaultdict(set)
1865
        self.write_allowed_paths = defaultdict(set)
1866

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

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

  
1878
    @check_allowed_paths(action=0)
1879
    def _can_read_container(self, user, account, container):
1880
        if user != account:
1881
            if container not in self._allowed_containers(user, account):
1882
                raise NotAllowedError
1883

  
1884
    @check_allowed_paths(action=1)
1885
    def _can_write_container(self, user, account, container):
1886
        if user != account:
1887
            raise NotAllowedError
1888

  
1889
    @check_allowed_paths(action=0)
1890
    def _can_read_object(self, user, account, container, name):
1811 1891
        if user == account:
1812 1892
            return True
1813 1893
        path = '/'.join((account, container, name))
......
1820 1900
                self.permissions.access_check(path, self.WRITE, user)):
1821 1901
            raise NotAllowedError
1822 1902

  
1823
    def _can_write(self, user, account, container, name):
1903
    @check_allowed_paths(action=1)
1904
    def _can_write_object(self, user, account, container, name):
1824 1905
        if user == account:
1825 1906
            return True
1826 1907
        path = '/'.join((account, container, name))
......
1833 1914
    def _allowed_accounts(self, user):
1834 1915
        allow = set()
1835 1916
        for path in self.permissions.access_list_paths(user):
1836
            allow.add(path.split('/', 1)[0])
1917
            p = path.split('/', 1)[0]
1918
            allow.add(p)
1919
        self.read_allowed_paths[user] |= allow
1837 1920
        return sorted(allow)
1838 1921

  
1839 1922
    def _allowed_containers(self, user, account):
1840 1923
        allow = set()
1841 1924
        for path in self.permissions.access_list_paths(user, account):
1842
            allow.add(path.split('/', 2)[1])
1925
            p = path.split('/', 2)[1]
1926
            allow.add(p)
1927
        self.read_allowed_paths[user] |= allow
1843 1928
        return sorted(allow)
1844 1929

  
1845 1930
    # Domain functions

Also available in: Unified diff