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