Revision ebdbac7a snf-pithos-backend/pithos/backends/modular.py

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):
......
370 410
                         include_user_defined=True):
371 411
        """Return a dictionary with the account metadata for the domain."""
372 412

  
413
        self._can_read_account(user, account)
373 414
        path, node = self._lookup_account(account, user == account)
374 415
        if user != account:
375
            if until or (node is None) or (account not
376
                                           in self._allowed_accounts(user)):
416
            if until or (node is None):
377 417
                raise NotAllowedError
378 418
        try:
379 419
            props = self._get_properties(node, until)
......
411 451
    def update_account_meta(self, user, account, domain, meta, replace=False):
412 452
        """Update the metadata associated with the account for the domain."""
413 453

  
414
        if user != account:
415
            raise NotAllowedError
454
        self._can_write_account(user, account)
416 455
        path, node = self._lookup_account(account, True)
417 456
        self._put_metadata(user, node, domain, meta, replace,
418 457
                           update_statistics_ancestors_depth=-1)
......
422 461
    def get_account_groups(self, user, account):
423 462
        """Return a dictionary with the user groups defined for the account."""
424 463

  
464
        self._can_read_account(user, account)
425 465
        if user != account:
426
            if account not in self._allowed_accounts(user):
427
                raise NotAllowedError
428 466
            return {}
429 467
        self._lookup_account(account, True)
430 468
        return self.permissions.group_dict(account)
......
434 472
    def update_account_groups(self, user, account, groups, replace=False):
435 473
        """Update the groups associated with the account."""
436 474

  
437
        if user != account:
438
            raise NotAllowedError
475
        self._can_write_account(user, account)
439 476
        self._lookup_account(account, True)
440 477
        self._check_groups(groups)
441 478
        if replace:
......
451 488
    def get_account_policy(self, user, account):
452 489
        """Return a dictionary with the account policy."""
453 490

  
491
        self._can_read_account(user, account)
454 492
        if user != account:
455
            if account not in self._allowed_accounts(user):
456
                raise NotAllowedError
457 493
            return {}
458 494
        path, node = self._lookup_account(account, True)
459 495
        policy = self._get_policy(node, is_account_policy=True)
......
467 503
    def update_account_policy(self, user, account, policy, replace=False):
468 504
        """Update the policy associated with the account."""
469 505

  
470
        if user != account:
471
            raise NotAllowedError
506
        self._can_write_account(user, account)
472 507
        path, node = self._lookup_account(account, True)
473 508
        self._check_policy(policy, is_account_policy=True)
474 509
        self._put_policy(node, policy, replace, is_account_policy=True)
......
479 514
        """Create a new account with the given name."""
480 515

  
481 516
        policy = policy or {}
482
        if user != account:
483
            raise NotAllowedError
517
        self._can_write_account(user, account)
484 518
        node = self.node.node_lookup(account)
485 519
        if node is not None:
486 520
            raise AccountExists('Account already exists')
......
495 529
    def delete_account(self, user, account):
496 530
        """Delete the account with the given name."""
497 531

  
498
        if user != account:
499
            raise NotAllowedError
532
        self._can_write_account(user, account)
500 533
        node = self.node.node_lookup(account)
501 534
        if node is None:
502 535
            return
......
505 538
            raise AccountNotEmpty('Account is not empty')
506 539
        self.permissions.group_destroy(account)
507 540

  
541
        # remove all the cached allowed paths
542
        # removing the specific path could be more expensive
543
        self._reset_allowed_paths()
544

  
508 545
    @debug_method
509 546
    @backend_method
510 547
    @list_method
......
512 549
                        shared=False, until=None, public=False):
513 550
        """Return a list of containers existing under an account."""
514 551

  
552
        self._can_read_account(user, account)
515 553
        if user != account:
516
            if until or account not in self._allowed_accounts(user):
554
            if until:
517 555
                raise NotAllowedError
518 556
            return self._allowed_containers(user, account)
519 557
        if shared or public:
......
535 573
                            until=None):
536 574
        """Return a list of the container's object meta keys for a domain."""
537 575

  
576
        self._can_read_container(user, account, container)
538 577
        allowed = []
539 578
        if user != account:
540 579
            if until:
541 580
                raise NotAllowedError
542
            allowed = self.permissions.access_list_paths(
543
                user, '/'.join((account, container)))
544
            if not allowed:
545
                raise NotAllowedError
546 581
        path, node = self._lookup_container(account, container)
547 582
        before = until if until is not None else inf
548 583
        allowed = self._get_formatted_paths(allowed)
......
555 590
                           include_user_defined=True):
556 591
        """Return a dictionary with the container metadata for the domain."""
557 592

  
593
        self._can_read_container(user, account, container)
558 594
        if user != account:
559
            if until or container not in self._allowed_containers(user,
560
                                                                  account):
595
            if until:
561 596
                raise NotAllowedError
562 597
        path, node = self._lookup_container(account, container)
563 598
        props = self._get_properties(node, until)
......
590 625
                              replace=False):
591 626
        """Update the metadata associated with the container for the domain."""
592 627

  
593
        if user != account:
594
            raise NotAllowedError
628
        self._can_write_container(user, account, container)
595 629
        path, node = self._lookup_container(account, container)
596 630
        src_version_id, dest_version_id = self._put_metadata(
597 631
            user, node, domain, meta, replace,
......
608 642
    def get_container_policy(self, user, account, container):
609 643
        """Return a dictionary with the container policy."""
610 644

  
645
        self._can_read_container(user, account, container)
611 646
        if user != account:
612
            if container not in self._allowed_containers(user, account):
613
                raise NotAllowedError
614 647
            return {}
615 648
        path, node = self._lookup_container(account, container)
616 649
        return self._get_policy(node, is_account_policy=False)
......
621 654
                                replace=False):
622 655
        """Update the policy associated with the container."""
623 656

  
624
        if user != account:
625
            raise NotAllowedError
657
        self._can_write_container(user, account, container)
626 658
        path, node = self._lookup_container(account, container)
627 659
        self._check_policy(policy, is_account_policy=False)
628 660
        self._put_policy(node, policy, replace, is_account_policy=False)
......
633 665
        """Create a new container with the given name."""
634 666

  
635 667
        policy = policy or {}
636
        if user != account:
637
            raise NotAllowedError
668
        self._can_write_container(user, account, container)
638 669
        try:
639 670
            path, node = self._lookup_container(account, container)
640 671
        except NameError:
......
655 686
                         delimiter=None):
656 687
        """Delete/purge the container with the given name."""
657 688

  
658
        if user != account:
659
            raise NotAllowedError
689
        self._can_write_container(user, account, container)
660 690
        path, node = self._lookup_container(account, container)
661 691

  
662 692
        if until is not None:
......
725 755
                paths.append(path)
726 756
            self.permissions.access_clear_bulk(paths)
727 757

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

  
728 762
    def _list_objects(self, user, account, container, prefix, delimiter,
729 763
                      marker, limit, virtual, domain, keys, shared, until,
730 764
                      size_range, all_props, public):
......
885 919
                        version=None, include_user_defined=True):
886 920
        """Return a dictionary with the object metadata for the domain."""
887 921

  
888
        self._can_read(user, account, container, name)
922
        self._can_read_object(user, account, container, name)
889 923
        path, node = self._lookup_object(account, container, name)
890 924
        props = self._get_version(node, version)
891 925
        if version is None:
......
923 957
                           replace=False):
924 958
        """Update object metadata for a domain and return the new version."""
925 959

  
926
        self._can_write(user, account, container, name)
960
        self._can_write_object(user, account, container, name)
927 961

  
928 962
        path, node = self._lookup_object(account, container, name,
929 963
                                         lock_container=True)
......
1006 1040
            self._report_sharing_change(user, account, path, {'members':
1007 1041
                                        self.permissions.access_members(path)})
1008 1042

  
1043
        # remove all the cached allowed paths
1044
        # filtering out only those affected could be more expensive
1045
        self._reset_allowed_paths()
1046

  
1009 1047
    @debug_method
1010 1048
    @backend_method
1011 1049
    def get_object_public(self, user, account, container, name):
1012 1050
        """Return the public id of the object if applicable."""
1013 1051

  
1014
        self._can_read(user, account, container, name)
1052
        self._can_read_object(user, account, container, name)
1015 1053
        path = self._lookup_object(account, container, name)[0]
1016 1054
        p = self.permissions.public_get(path)
1017 1055
        return p
......
1021 1059
    def update_object_public(self, user, account, container, name, public):
1022 1060
        """Update the public status of the object."""
1023 1061

  
1024
        self._can_write(user, account, container, name)
1062
        self._can_write_object(user, account, container, name)
1025 1063
        path = self._lookup_object(account, container, name,
1026 1064
                                   lock_container=True)[0]
1027 1065
        if not public:
......
1035 1073
    def get_object_hashmap(self, user, account, container, name, version=None):
1036 1074
        """Return the object's size and a list with partial hashes."""
1037 1075

  
1038
        self._can_read(user, account, container, name)
1076
        self._can_read_object(user, account, container, name)
1039 1077
        path, node = self._lookup_object(account, container, name)
1040 1078
        props = self._get_version(node, version)
1041 1079
        if props[self.HASH] is None:
......
1049 1087
                            is_copy=False, report_size_change=True):
1050 1088
        if permissions is not None and user != account:
1051 1089
            raise NotAllowedError
1052
        self._can_write(user, account, container, name)
1090
        self._can_write_object(user, account, container, name)
1053 1091
        if permissions is not None:
1054 1092
            path = '/'.join((account, container, name))
1055 1093
            self._check_permissions(path, permissions)
......
1149 1187

  
1150 1188
        # Update objects with greater version and same hashmap
1151 1189
        # and size (fix metadata updates).
1152
        self._can_write(user, account, container, name)
1190
        self._can_write_object(user, account, container, name)
1153 1191
        path, node = self._lookup_object(account, container, name,
1154 1192
                                         lock_container=True)
1155 1193
        props = self._get_version(node, version)
......
1170 1208
        report_size_change = not is_move
1171 1209
        dest_meta = dest_meta or {}
1172 1210
        dest_version_ids = []
1173
        self._can_read(user, src_account, src_container, src_name)
1211
        self._can_read_object(user, src_account, src_container, src_name)
1174 1212

  
1175 1213
        src_container_path = '/'.join((src_account, src_container))
1176 1214
        dest_container_path = '/'.join((dest_account, dest_container))
......
1360 1398
                paths.append(path)
1361 1399
            self.permissions.access_clear_bulk(paths)
1362 1400

  
1401
        # remove all the cached allowed paths
1402
        # removing the specific path could be more expensive
1403
        self._reset_allowed_paths()
1404

  
1363 1405
    @debug_method
1364 1406
    @backend_method
1365 1407
    def delete_object(self, user, account, container, name, until=None,
......
1373 1415
    def list_versions(self, user, account, container, name):
1374 1416
        """Return a list of all object (version, version_timestamp) tuples."""
1375 1417

  
1376
        self._can_read(user, account, container, name)
1418
        self._can_read_object(user, account, container, name)
1377 1419
        path, node = self._lookup_object(account, container, name)
1378 1420
        versions = self.node.node_get_versions(node)
1379 1421
        return [[x[self.SERIAL], x[self.MTIME]] for x in versions if
......
1390 1432
        path, serial = info
1391 1433
        account, container, name = path.split('/', 2)
1392 1434
        if check_permissions:
1393
            self._can_read(user, account, container, name)
1435
            self._can_read_object(user, account, container, name)
1394 1436
        return (account, container, name)
1395 1437

  
1396 1438
    @debug_method
......
1402 1444
        if path is None:
1403 1445
            raise NameError
1404 1446
        account, container, name = path.split('/', 2)
1405
        self._can_read(user, account, container, name)
1447
        self._can_read_object(user, account, container, name)
1406 1448
        return (account, container, name)
1407 1449

  
1408 1450
    def get_block(self, hash):
......
1814 1856

  
1815 1857
        return None
1816 1858

  
1817
    def _can_read(self, user, account, container, name):
1859
    def _reset_allowed_paths(self):
1860
        self.read_allowed_paths = defaultdict(set)
1861
        self.write_allowed_paths = defaultdict(set)
1862

  
1863
    @check_allowed_paths(action=0)
1864
    def _can_read_account(self, user, account):
1865
        if user != account:
1866
            if account not in self._allowed_accounts(user):
1867
                raise NotAllowedError
1868

  
1869
    @check_allowed_paths(action=1)
1870
    def _can_write_account(self, user, account):
1871
        if user != account:
1872
            raise NotAllowedError
1873

  
1874
    @check_allowed_paths(action=0)
1875
    def _can_read_container(self, user, account, container):
1876
        if user != account:
1877
            if container not in self._allowed_containers(user, account):
1878
                raise NotAllowedError
1879

  
1880
    @check_allowed_paths(action=1)
1881
    def _can_write_container(self, user, account, container):
1882
        if user != account:
1883
            raise NotAllowedError
1884

  
1885
    @check_allowed_paths(action=0)
1886
    def _can_read_object(self, user, account, container, name):
1818 1887
        if user == account:
1819 1888
            return True
1820 1889
        path = '/'.join((account, container, name))
......
1827 1896
                self.permissions.access_check(path, self.WRITE, user)):
1828 1897
            raise NotAllowedError
1829 1898

  
1830
    def _can_write(self, user, account, container, name):
1899
    @check_allowed_paths(action=1)
1900
    def _can_write_object(self, user, account, container, name):
1831 1901
        if user == account:
1832 1902
            return True
1833 1903
        path = '/'.join((account, container, name))
......
1840 1910
    def _allowed_accounts(self, user):
1841 1911
        allow = set()
1842 1912
        for path in self.permissions.access_list_paths(user):
1843
            allow.add(path.split('/', 1)[0])
1913
            p = path.split('/', 1)[0]
1914
            allow.add(p)
1915
        self.read_allowed_paths[user] |= allow
1844 1916
        return sorted(allow)
1845 1917

  
1846 1918
    def _allowed_containers(self, user, account):
1847 1919
        allow = set()
1848 1920
        for path in self.permissions.access_list_paths(user, account):
1849
            allow.add(path.split('/', 2)[1])
1921
            p = path.split('/', 2)[1]
1922
            allow.add(p)
1923
        self.read_allowed_paths[user] |= allow
1850 1924
        return sorted(allow)
1851 1925

  
1852 1926
    # Domain functions

Also available in: Unified diff