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