forked from TrueCloudLab/frostfs-testcases
Add ACL and eACL PyTest tests
Signed-off-by: Vladimir Avdeev <v.avdeev@yadro.com>
This commit is contained in:
parent
590a5cfb0e
commit
6d040c6834
36 changed files with 979 additions and 1318 deletions
80
pytest_tests/testsuites/acl/conftest.py
Normal file
80
pytest_tests/testsuites/acl/conftest.py
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
import allure
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from common import ASSETS_DIR, IR_WALLET_CONFIG, IR_WALLET_PATH, WALLET_CONFIG
|
||||||
|
from python_keywords.acl import EACLRole
|
||||||
|
from python_keywords.container import create_container
|
||||||
|
from python_keywords.neofs_verbs import put_object
|
||||||
|
from python_keywords.utility_keywords import generate_file
|
||||||
|
from wallet import init_wallet
|
||||||
|
from wellknown_acl import PUBLIC_ACL
|
||||||
|
|
||||||
|
OBJECT_COUNT = 5
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Wallet:
|
||||||
|
wallet_path: Optional[str] = None
|
||||||
|
config_path: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Wallets:
|
||||||
|
wallets: Dict[EACLRole, List[Wallet]]
|
||||||
|
|
||||||
|
def get_wallet(self, role: EACLRole = EACLRole.USER) -> Wallet:
|
||||||
|
return self.wallets[role][0]
|
||||||
|
|
||||||
|
def get_wallets_list(self, role: EACLRole = EACLRole.USER) -> List[Wallet]:
|
||||||
|
return self.wallets[role]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def wallets(prepare_wallet_and_deposit):
|
||||||
|
yield Wallets(wallets={
|
||||||
|
EACLRole.USER: [
|
||||||
|
Wallet(
|
||||||
|
wallet_path=prepare_wallet_and_deposit,
|
||||||
|
config_path=WALLET_CONFIG
|
||||||
|
)],
|
||||||
|
EACLRole.OTHERS: [
|
||||||
|
Wallet(
|
||||||
|
wallet_path=init_wallet(ASSETS_DIR)[0],
|
||||||
|
config_path=WALLET_CONFIG
|
||||||
|
),
|
||||||
|
Wallet(
|
||||||
|
wallet_path=init_wallet(ASSETS_DIR)[0],
|
||||||
|
config_path=WALLET_CONFIG
|
||||||
|
)],
|
||||||
|
EACLRole.SYSTEM: [
|
||||||
|
Wallet(
|
||||||
|
wallet_path=IR_WALLET_PATH,
|
||||||
|
config_path=IR_WALLET_CONFIG
|
||||||
|
)],
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def file_path():
|
||||||
|
yield generate_file()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eacl_container_with_objects(wallets, file_path):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
with allure.step('Create eACL public container'):
|
||||||
|
cid = create_container(user_wallet.wallet_path, basic_acl=PUBLIC_ACL)
|
||||||
|
|
||||||
|
with allure.step('Add test objects to container'):
|
||||||
|
objects_oids = [
|
||||||
|
put_object(
|
||||||
|
user_wallet.wallet_path, file_path, cid,
|
||||||
|
attributes={'key1': 'val1', 'key': val, 'key2': 'abc'}) for val in range(OBJECT_COUNT)]
|
||||||
|
|
||||||
|
yield cid, objects_oids, file_path
|
||||||
|
|
||||||
|
# with allure.step('Delete eACL public container'):
|
||||||
|
# delete_container(user_wallet, cid)
|
|
@ -1,117 +1,102 @@
|
||||||
import os
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import wallet
|
from python_keywords.acl import EACLRole
|
||||||
from common import ASSETS_DIR
|
|
||||||
from grpc_responses import OBJECT_ACCESS_DENIED
|
|
||||||
from python_keywords.acl import set_eacl
|
|
||||||
from python_keywords.container import create_container
|
from python_keywords.container import create_container
|
||||||
from python_keywords.neofs_verbs import (delete_object, get_object, get_range,
|
from python_keywords.container_access import (check_full_access_to_container, check_no_access_to_container,
|
||||||
get_range_hash, head_object,
|
check_read_only_container)
|
||||||
put_object, search_object)
|
from python_keywords.neofs_verbs import put_object
|
||||||
from python_keywords.utility_keywords import generate_file, get_file_hash
|
from wellknown_acl import PRIVATE_ACL_F, PUBLIC_ACL_F, READONLY_ACL_F
|
||||||
|
|
||||||
RESOURCE_DIR = os.path.join(
|
|
||||||
os.path.dirname(os.path.realpath(__file__)),
|
|
||||||
'../../../robot/resources/files/',
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sanity
|
@pytest.mark.sanity
|
||||||
@pytest.mark.acl
|
@pytest.mark.acl
|
||||||
class TestACL:
|
@pytest.mark.acl_container
|
||||||
@pytest.fixture(autouse=True)
|
class TestACLBasic:
|
||||||
def create_two_wallets(self, prepare_wallet_and_deposit):
|
|
||||||
self.main_wallet = prepare_wallet_and_deposit
|
|
||||||
self.other_wallet = wallet.init_wallet(ASSETS_DIR)[0] # We need wallet file path only
|
|
||||||
|
|
||||||
@allure.title('Test basic ACL')
|
@pytest.fixture(scope='function')
|
||||||
def test_basic_acl(self):
|
def public_container(self, wallets):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
with allure.step('Create public container'):
|
||||||
|
cid_public = create_container(user_wallet.wallet_path, basic_acl=PUBLIC_ACL_F)
|
||||||
|
|
||||||
|
yield cid_public
|
||||||
|
|
||||||
|
# with allure.step('Delete public container'):
|
||||||
|
# delete_container(user_wallet.wallet_path, cid_public)
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def private_container(self, wallets):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
with allure.step('Create private container'):
|
||||||
|
cid_private = create_container(user_wallet.wallet_path, basic_acl=PRIVATE_ACL_F)
|
||||||
|
|
||||||
|
yield cid_private
|
||||||
|
|
||||||
|
# with allure.step('Delete private container'):
|
||||||
|
# delete_container(user_wallet.wallet_path, cid_private)
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def read_only_container(self, wallets):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
with allure.step('Create public readonly container'):
|
||||||
|
cid_read_only = create_container(user_wallet.wallet_path, basic_acl=READONLY_ACL_F)
|
||||||
|
|
||||||
|
yield cid_read_only
|
||||||
|
|
||||||
|
# with allure.step('Delete public readonly container'):
|
||||||
|
# delete_container(user_wallet.wallet_path, cid_read_only)
|
||||||
|
|
||||||
|
@allure.title('Test basic ACL on public container')
|
||||||
|
def test_basic_acl_public(self, wallets, public_container, file_path):
|
||||||
"""
|
"""
|
||||||
Test basic ACL set during container creation.
|
Test basic ACL set during public container creation.
|
||||||
"""
|
"""
|
||||||
file_name = generate_file()
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
|
||||||
|
cid = public_container
|
||||||
|
for wallet, desc in ((user_wallet, 'owner'), (other_wallet, 'other users')):
|
||||||
|
with allure.step('Add test objects to container'):
|
||||||
|
# We create new objects for each wallet because check_full_access_to_container deletes the object
|
||||||
|
owner_object_oid = put_object(user_wallet.wallet_path, file_path, cid, attributes={'created': 'owner'})
|
||||||
|
other_object_oid = put_object(other_wallet.wallet_path, file_path, cid, attributes={'created': 'other'})
|
||||||
|
with allure.step(f'Check {desc} has full access to public container'):
|
||||||
|
check_full_access_to_container(wallet.wallet_path, cid, owner_object_oid, file_path)
|
||||||
|
check_full_access_to_container(wallet.wallet_path, cid, other_object_oid, file_path)
|
||||||
|
|
||||||
with allure.step('Create public container and check access'):
|
@allure.title('Test basic ACL on private container')
|
||||||
cid_public = create_container(self.main_wallet, basic_acl='public-read-write')
|
def test_basic_acl_private(self, wallets, private_container, file_path):
|
||||||
self.check_full_access(cid_public, file_name)
|
"""
|
||||||
|
Test basic ACL set during private container creation.
|
||||||
with allure.step('Create private container and check only owner has access'):
|
"""
|
||||||
cid_private = create_container(self.main_wallet, basic_acl='private')
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
|
||||||
with allure.step('Check owner can put/get object into private container'):
|
cid = private_container
|
||||||
oid = put_object(wallet=self.main_wallet, path=file_name, cid=cid_private)
|
with allure.step('Add test objects to container'):
|
||||||
|
owner_object_oid = put_object(user_wallet.wallet_path, file_path, cid)
|
||||||
got_file = get_object(self.main_wallet, cid_private, oid)
|
|
||||||
assert get_file_hash(got_file) == get_file_hash(file_name)
|
|
||||||
|
|
||||||
|
with allure.step('Check only owner has full access to private container'):
|
||||||
with allure.step('Check no one except owner has access to operations with container'):
|
with allure.step('Check no one except owner has access to operations with container'):
|
||||||
self.check_no_access_to_container(self.other_wallet, cid_private, oid, file_name)
|
check_no_access_to_container(other_wallet.wallet_path, cid, owner_object_oid, file_path)
|
||||||
|
|
||||||
delete_object(self.main_wallet, cid_private, oid)
|
with allure.step('Check owner has full access to private container'):
|
||||||
|
check_full_access_to_container(
|
||||||
|
user_wallet.wallet_path, cid, owner_object_oid, file_path)
|
||||||
|
|
||||||
@allure.title('Test extended ACL')
|
@allure.title('Test basic ACL on readonly container')
|
||||||
def test_extended_acl(self):
|
def test_basic_acl_readonly(self, wallets, read_only_container, file_path):
|
||||||
"""
|
"""
|
||||||
Test basic extended ACL applied after container creation.
|
Test basic ACL Operations for Read-Only Container.
|
||||||
"""
|
"""
|
||||||
file_name = generate_file()
|
user_wallet = wallets.get_wallet()
|
||||||
deny_all_eacl = os.path.join(RESOURCE_DIR, 'eacl_tables/gen_eacl_deny_all_OTHERS')
|
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
|
||||||
|
cid = read_only_container
|
||||||
|
|
||||||
with allure.step('Create public container and check access'):
|
with allure.step('Add test objects to container'):
|
||||||
cid_public = create_container(self.main_wallet, basic_acl='eacl-public-read-write')
|
object_oid = put_object(user_wallet.wallet_path, file_path, cid)
|
||||||
oid = self.check_full_access(cid_public, file_name)
|
|
||||||
|
|
||||||
with allure.step('Set "deny all operations for other" for created container'):
|
with allure.step('Check other has read-only access to operations with container'):
|
||||||
set_eacl(self.main_wallet, cid_public, deny_all_eacl)
|
check_read_only_container(other_wallet.wallet_path, cid, object_oid, file_path)
|
||||||
|
|
||||||
with allure.step('Check no one except owner has access to operations with container'):
|
with allure.step('Check owner has full access to public container'):
|
||||||
self.check_no_access_to_container(self.other_wallet, cid_public, oid, file_name)
|
check_full_access_to_container(user_wallet.wallet_path, cid, object_oid, file_path)
|
||||||
|
|
||||||
with allure.step('Check owner has access to operations with container'):
|
|
||||||
self.check_full_access(cid_public, file_name, wallet_to_check=((self.main_wallet, 'owner'),))
|
|
||||||
|
|
||||||
delete_object(self.main_wallet, cid_public, oid)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def check_no_access_to_container(wallet: str, cid: str, oid: str, file_name: str):
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
get_object(wallet, cid, oid)
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
put_object(wallet, file_name, cid)
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
delete_object(wallet, cid, oid)
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
head_object(wallet, cid, oid)
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
get_range(wallet, cid, oid, bearer='', range_cut='0:10')
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
get_range_hash(wallet, cid, oid, bearer_token='', range_cut='0:10')
|
|
||||||
|
|
||||||
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
|
|
||||||
search_object(wallet, cid)
|
|
||||||
|
|
||||||
def check_full_access(self, cid: str, file_name: str, wallet_to_check: Tuple = None) -> str:
|
|
||||||
wallets = wallet_to_check or ((self.main_wallet, 'owner'), (self.other_wallet, 'not owner'))
|
|
||||||
for current_wallet, desc in wallets:
|
|
||||||
with allure.step(f'Check {desc} can put object into public container'):
|
|
||||||
oid = put_object(current_wallet, file_name, cid)
|
|
||||||
|
|
||||||
with allure.step(f'Check {desc} can execute operations on object from public container'):
|
|
||||||
got_file = get_object(current_wallet, cid, oid)
|
|
||||||
assert get_file_hash(got_file) == get_file_hash(file_name), 'Expected hashes are the same'
|
|
||||||
|
|
||||||
head_object(current_wallet, cid, oid)
|
|
||||||
get_range(current_wallet, cid, oid, bearer='', range_cut='0:10')
|
|
||||||
get_range_hash(current_wallet, cid, oid, bearer_token='', range_cut='0:10')
|
|
||||||
search_object(current_wallet, cid)
|
|
||||||
|
|
||||||
return oid
|
|
||||||
|
|
108
pytest_tests/testsuites/acl/test_bearer.py
Normal file
108
pytest_tests/testsuites/acl/test_bearer.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
import allure
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_keywords.acl import (EACLAccess, EACLOperation, EACLRole, EACLRule, create_eacl, form_bearertoken_file,
|
||||||
|
set_eacl, wait_for_cache_expired)
|
||||||
|
from python_keywords.container_access import (check_custom_access_to_container, check_full_access_to_container,
|
||||||
|
check_no_access_to_container)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sanity
|
||||||
|
@pytest.mark.acl
|
||||||
|
@pytest.mark.acl_bearer
|
||||||
|
class TestACLBearer:
|
||||||
|
@pytest.mark.parametrize('role', [EACLRole.USER, EACLRole.OTHERS])
|
||||||
|
def test_bearer_token_operations(self, wallets, eacl_container_with_objects, role):
|
||||||
|
allure.dynamic.title(f"Testcase to validate NeoFS operations with {role.value} BearerToken")
|
||||||
|
cid, objects_oids, file_path = eacl_container_with_objects
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
deny_wallet = wallets.get_wallet(role)
|
||||||
|
|
||||||
|
with allure.step(f'Check {role.value} has full access to container without bearer token'):
|
||||||
|
check_full_access_to_container(deny_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
wallet_config=deny_wallet.config_path)
|
||||||
|
|
||||||
|
with allure.step(f'Set deny all operations for {role.value} via eACL'):
|
||||||
|
eacl = [EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in EACLOperation]
|
||||||
|
eacl_file = create_eacl(cid, eacl)
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, eacl_file)
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step(f'Create bearer token for {role.value} with all operations allowed'):
|
||||||
|
bearer_token = form_bearertoken_file(user_wallet.wallet_path, cid, [
|
||||||
|
EACLRule(operation=op, access=EACLAccess.ALLOW, role=role)
|
||||||
|
for op in EACLOperation])
|
||||||
|
|
||||||
|
with allure.step(f'Check {role.value} without token has no access to all operations with container'):
|
||||||
|
check_no_access_to_container(
|
||||||
|
deny_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
wallet_config=deny_wallet.config_path)
|
||||||
|
|
||||||
|
with allure.step(f'Check {role.value} with token has access to all operations with container'):
|
||||||
|
check_full_access_to_container(deny_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
bearer=bearer_token, wallet_config=deny_wallet.config_path)
|
||||||
|
|
||||||
|
with allure.step(f'Set allow all operations for {role.value} via eACL'):
|
||||||
|
eacl = [EACLRule(access=EACLAccess.ALLOW, role=role, operation=op) for op in EACLOperation]
|
||||||
|
eacl_file = create_eacl(cid, eacl)
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, eacl_file)
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step(f'Check {role.value} without token has access to all operations with container'):
|
||||||
|
check_full_access_to_container(deny_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
wallet_config=deny_wallet.config_path)
|
||||||
|
|
||||||
|
@allure.title('BearerToken Operations for compound Operations')
|
||||||
|
def test_bearer_token_compound_operations(self, wallets, eacl_container_with_objects):
|
||||||
|
cid, objects_oids, file_path = eacl_container_with_objects
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(role=EACLRole.OTHERS)
|
||||||
|
|
||||||
|
# Operations that we will deny for each role via eACL
|
||||||
|
deny_map = {
|
||||||
|
EACLRole.USER: [EACLOperation.DELETE],
|
||||||
|
EACLRole.OTHERS: [EACLOperation.GET, EACLOperation.PUT, EACLOperation.GET_RANGE]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Operations that we will allow for each role with bearer token
|
||||||
|
bearer_map = {
|
||||||
|
EACLRole.USER: [EACLOperation.DELETE, EACLOperation.PUT, EACLOperation.GET_RANGE],
|
||||||
|
EACLRole.OTHERS: [EACLOperation.GET, EACLOperation.GET_RANGE],
|
||||||
|
}
|
||||||
|
|
||||||
|
deny_map_with_bearer = {
|
||||||
|
EACLRole.USER: [op for op in deny_map[EACLRole.USER] if op not in bearer_map[EACLRole.USER]],
|
||||||
|
EACLRole.OTHERS: [op for op in deny_map[EACLRole.OTHERS] if op not in bearer_map[EACLRole.OTHERS]],
|
||||||
|
}
|
||||||
|
|
||||||
|
eacl_deny = []
|
||||||
|
for role, operations in deny_map.items():
|
||||||
|
eacl_deny += [EACLRule(access=EACLAccess.DENY, role=role, operation=op) for op in operations]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, eacl_table_path=create_eacl(cid, eacl_deny))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step('Check rule consistency without bearer'):
|
||||||
|
check_custom_access_to_container(user_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
deny_operations=deny_map[EACLRole.USER],
|
||||||
|
wallet_config=user_wallet.config_path)
|
||||||
|
check_custom_access_to_container(other_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
deny_operations=deny_map[EACLRole.OTHERS],
|
||||||
|
wallet_config=other_wallet.config_path)
|
||||||
|
|
||||||
|
with allure.step('Check rule consistency with bearer'):
|
||||||
|
bearer_token_user = form_bearertoken_file(user_wallet.wallet_path, cid, [
|
||||||
|
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.USER)
|
||||||
|
for op in bearer_map[EACLRole.USER]])
|
||||||
|
|
||||||
|
bearer_token_other = form_bearertoken_file(user_wallet.wallet_path, cid, [
|
||||||
|
EACLRule(operation=op, access=EACLAccess.ALLOW, role=EACLRole.OTHERS)
|
||||||
|
for op in bearer_map[EACLRole.OTHERS]])
|
||||||
|
|
||||||
|
check_custom_access_to_container(user_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
deny_operations=deny_map_with_bearer[EACLRole.USER],
|
||||||
|
bearer=bearer_token_user,
|
||||||
|
wallet_config=user_wallet.config_path)
|
||||||
|
check_custom_access_to_container(other_wallet.wallet_path, cid, objects_oids.pop(), file_path,
|
||||||
|
deny_operations=deny_map_with_bearer[EACLRole.OTHERS],
|
||||||
|
bearer=bearer_token_other,
|
||||||
|
wallet_config=other_wallet.config_path)
|
105
pytest_tests/testsuites/acl/test_eacl.py
Normal file
105
pytest_tests/testsuites/acl/test_eacl.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import allure
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from common import NEOFS_NETMAP_DICT
|
||||||
|
from failover_utils import wait_object_replication_on_nodes
|
||||||
|
from python_keywords.acl import (EACLAccess, EACLOperation, EACLRole, EACLRule, create_eacl, set_eacl,
|
||||||
|
wait_for_cache_expired)
|
||||||
|
from python_keywords.container import create_container
|
||||||
|
from python_keywords.container_access import check_full_access_to_container, check_no_access_to_container
|
||||||
|
from python_keywords.neofs_verbs import put_object
|
||||||
|
from python_keywords.node_management import drop_object
|
||||||
|
from wellknown_acl import PUBLIC_ACL
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sanity
|
||||||
|
@pytest.mark.acl
|
||||||
|
@pytest.mark.acl_container
|
||||||
|
class TestEACLContainer:
|
||||||
|
NODE_COUNT = len(NEOFS_NETMAP_DICT.keys())
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eacl_full_placement_container_with_object(self, wallets, file_path):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
with allure.step('Create eACL public container with full placement rule'):
|
||||||
|
full_placement_rule = f'REP {self.NODE_COUNT} IN X CBF 1 SELECT {self.NODE_COUNT} FROM * AS X'
|
||||||
|
cid = create_container(user_wallet.wallet_path, full_placement_rule, basic_acl=PUBLIC_ACL)
|
||||||
|
|
||||||
|
with allure.step('Add test object to container'):
|
||||||
|
oid = put_object(user_wallet.wallet_path, file_path, cid)
|
||||||
|
wait_object_replication_on_nodes(user_wallet.wallet_path, cid, oid, self.NODE_COUNT)
|
||||||
|
|
||||||
|
yield cid, oid, file_path
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('deny_role', [EACLRole.USER, EACLRole.OTHERS])
|
||||||
|
def test_extended_acl_deny_all_operations(self, wallets, eacl_container_with_objects, deny_role):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
|
||||||
|
deny_role_wallet = other_wallet if deny_role == EACLRole.OTHERS else user_wallet
|
||||||
|
not_deny_role_wallet = user_wallet if deny_role == EACLRole.OTHERS else other_wallet
|
||||||
|
deny_role_str = 'all others' if deny_role == EACLRole.OTHERS else 'user'
|
||||||
|
not_deny_role_str = 'user' if deny_role == EACLRole.OTHERS else 'all others'
|
||||||
|
allure.dynamic.title(f'Testcase to deny NeoFS operations for {deny_role_str}.')
|
||||||
|
cid, object_oids, file_path = eacl_container_with_objects
|
||||||
|
|
||||||
|
with allure.step(f'Deny all operations for {deny_role_str} via eACL'):
|
||||||
|
eacl_deny = [EACLRule(access=EACLAccess.DENY, role=deny_role, operation=op) for op in EACLOperation]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl_deny))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step(f'Check only {not_deny_role_str} has full access to container'):
|
||||||
|
with allure.step(f'Check {deny_role_str} has not access to any operations with container'):
|
||||||
|
check_no_access_to_container(deny_role_wallet.wallet_path, cid, object_oids[0], file_path)
|
||||||
|
|
||||||
|
with allure.step(f'Check {not_deny_role_wallet} has full access to eACL public container'):
|
||||||
|
check_full_access_to_container(not_deny_role_wallet.wallet_path, cid, object_oids.pop(), file_path)
|
||||||
|
|
||||||
|
with allure.step(f'Allow all operations for {deny_role_str} via eACL'):
|
||||||
|
eacl_deny = [EACLRule(access=EACLAccess.ALLOW, role=deny_role, operation=op) for op in EACLOperation]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl_deny))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step(f'Check all have full access to eACL public container'):
|
||||||
|
check_full_access_to_container(user_wallet.wallet_path, cid, object_oids.pop(), file_path)
|
||||||
|
check_full_access_to_container(other_wallet.wallet_path, cid, object_oids.pop(), file_path)
|
||||||
|
|
||||||
|
@allure.title('Testcase to allow NeoFS operations for only one other pubkey.')
|
||||||
|
def test_extended_acl_deny_all_operations_exclude_pubkey(self, wallets, eacl_container_with_objects):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet, other_wallet_allow = wallets.get_wallets_list(EACLRole.OTHERS)[0:2]
|
||||||
|
cid, object_oids, file_path = eacl_container_with_objects
|
||||||
|
|
||||||
|
with allure.step('Deny all operations for others except single wallet via eACL'):
|
||||||
|
eacl = [EACLRule(access=EACLAccess.ALLOW, role=other_wallet_allow.wallet_path, operation=op)
|
||||||
|
for op in EACLOperation]
|
||||||
|
eacl += [EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op) for op in EACLOperation]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step('Check only owner and allowed other have full access to public container'):
|
||||||
|
with allure.step('Check other has not access to operations with container'):
|
||||||
|
check_no_access_to_container(other_wallet.wallet_path, cid, object_oids[0], file_path)
|
||||||
|
|
||||||
|
with allure.step('Check owner has full access to public container'):
|
||||||
|
check_full_access_to_container(user_wallet.wallet_path, cid, object_oids.pop(), file_path)
|
||||||
|
|
||||||
|
with allure.step('Check allowed other has full access to public container'):
|
||||||
|
check_full_access_to_container(other_wallet_allow.wallet_path, cid, object_oids.pop(), file_path)
|
||||||
|
|
||||||
|
@allure.title('Testcase to validate NeoFS replication with eACL deny rules.')
|
||||||
|
def test_extended_acl_deny_replication(self, wallets, eacl_full_placement_container_with_object, file_path):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
cid, oid, file_path = eacl_full_placement_container_with_object
|
||||||
|
|
||||||
|
with allure.step('Deny all operations for user via eACL'):
|
||||||
|
eacl_deny = [EACLRule(access=EACLAccess.DENY, role=EACLRole.USER, operation=op) for op in EACLOperation]
|
||||||
|
eacl_deny += [EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op) for op in EACLOperation]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl_deny))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
with allure.step('Drop object to check replication'):
|
||||||
|
drop_object(node_name=[*NEOFS_NETMAP_DICT][0], cid=cid, oid=oid)
|
||||||
|
|
||||||
|
storage_wallet_path = NEOFS_NETMAP_DICT[[*NEOFS_NETMAP_DICT][0]]["wallet_path"]
|
||||||
|
with allure.step('Wait for dropped object replicated'):
|
||||||
|
wait_object_replication_on_nodes(storage_wallet_path, cid, oid, self.NODE_COUNT)
|
197
pytest_tests/testsuites/acl/test_eacl_filters.py
Normal file
197
pytest_tests/testsuites/acl/test_eacl_filters.py
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
import allure
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from python_keywords.acl import (EACLAccess, EACLFilter, EACLFilters, EACLHeaderType, EACLMatchType, EACLOperation,
|
||||||
|
EACLRole, EACLRule, create_eacl, set_eacl, wait_for_cache_expired)
|
||||||
|
from python_keywords.container import create_container, delete_container
|
||||||
|
from python_keywords.container_access import check_full_access_to_container, check_no_access_to_container
|
||||||
|
from python_keywords.neofs_verbs import put_object
|
||||||
|
from python_keywords.object_access import can_get_object, can_get_head_object, can_put_object
|
||||||
|
from wellknown_acl import PUBLIC_ACL
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.sanity
|
||||||
|
@pytest.mark.acl
|
||||||
|
@pytest.mark.acl_container
|
||||||
|
class TestEACLFilters:
|
||||||
|
# SPEC: https://github.com/nspcc-dev/neofs-spec/blob/master/01-arch/07-acl.md
|
||||||
|
ATTRIBUTE = {'check_key': 'check_value'}
|
||||||
|
OTHER_ATTRIBUTE = {'check_key': 'other_value'}
|
||||||
|
SET_HEADERS = {'key_one': 'check_value', 'x_key': 'xvalue', 'check_key': 'check_value'}
|
||||||
|
OTHER_HEADERS = {'key_one': 'check_value', 'x_key': 'other_value', 'check_key': 'other_value'}
|
||||||
|
REQ_EQUAL_FILTER = EACLFilter(key='check_key', value='check_value', header_type=EACLHeaderType.REQUEST)
|
||||||
|
NOT_REQ_EQUAL_FILTER = EACLFilter(key='check_key', value='other_value', match_type=EACLMatchType.STRING_NOT_EQUAL,
|
||||||
|
header_type=EACLHeaderType.REQUEST)
|
||||||
|
OBJ_EQUAL_FILTER = EACLFilter(key='check_key', value='check_value', header_type=EACLHeaderType.OBJECT)
|
||||||
|
NOT_OBJ_EQUAL_FILTER = EACLFilter(key='check_key', value='other_value', match_type=EACLMatchType.STRING_NOT_EQUAL,
|
||||||
|
header_type=EACLHeaderType.OBJECT)
|
||||||
|
OBJECT_COUNT = 3
|
||||||
|
OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS = [EACLOperation.GET, EACLOperation.HEAD, EACLOperation.PUT]
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def eacl_container_with_objects(self, wallets, file_path):
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
with allure.step('Create eACL public container'):
|
||||||
|
cid = create_container(user_wallet.wallet_path, basic_acl=PUBLIC_ACL)
|
||||||
|
|
||||||
|
with allure.step('Add test objects to container'):
|
||||||
|
objects_with_header = [
|
||||||
|
put_object(user_wallet.wallet_path, file_path, cid,
|
||||||
|
attributes={**self.SET_HEADERS, 'key': val}) for val in range(self.OBJECT_COUNT)]
|
||||||
|
|
||||||
|
objects_with_other_header = [
|
||||||
|
put_object(user_wallet.wallet_path, file_path, cid,
|
||||||
|
attributes={**self.OTHER_HEADERS, 'key': val}) for val in range(self.OBJECT_COUNT)]
|
||||||
|
|
||||||
|
objects_without_header = [
|
||||||
|
put_object(user_wallet.wallet_path, file_path, cid) for _ in range(self.OBJECT_COUNT)]
|
||||||
|
|
||||||
|
yield cid, objects_with_header, objects_with_other_header, objects_without_header, file_path
|
||||||
|
|
||||||
|
with allure.step('Delete eACL public container'):
|
||||||
|
delete_container(user_wallet.wallet_path, cid)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('match_type', [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL])
|
||||||
|
def test_extended_acl_filters_request(self, wallets, eacl_container_with_objects, match_type):
|
||||||
|
allure.dynamic.title(f"Validate NeoFS operations with request filter: {match_type.name}")
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
|
||||||
|
cid, objects_with_header, objects_with_other_header, objects_without_header, file_path =\
|
||||||
|
eacl_container_with_objects
|
||||||
|
|
||||||
|
with allure.step('Deny all operations for other with eACL request filter'):
|
||||||
|
equal_filter = EACLFilter(**self.REQ_EQUAL_FILTER.__dict__)
|
||||||
|
equal_filter.match_type = match_type
|
||||||
|
eacl_deny = [EACLRule(access=EACLAccess.DENY,
|
||||||
|
role=EACLRole.OTHERS,
|
||||||
|
filters=EACLFilters([equal_filter]),
|
||||||
|
operation=op) for op in EACLOperation]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl_deny))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
# Filter denies requests where "check_key {match_type} ATTRIBUTE", so when match_type
|
||||||
|
# is STRING_EQUAL, then requests with "check_key=OTHER_ATTRIBUTE" will be allowed while
|
||||||
|
# requests with "check_key=ATTRIBUTE" will be denied, and vice versa
|
||||||
|
allow_headers = self.OTHER_ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.ATTRIBUTE
|
||||||
|
deny_headers = self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE
|
||||||
|
# We test on 3 groups of objects with various headers, but eACL rule should ignore object headers and
|
||||||
|
# work only based on request headers
|
||||||
|
for oid in (objects_with_header, objects_with_other_header, objects_without_header):
|
||||||
|
with allure.step('Check other has full access when sending request without headers'):
|
||||||
|
check_full_access_to_container(other_wallet.wallet_path, cid, oid.pop(), file_path)
|
||||||
|
|
||||||
|
with allure.step('Check other has full access when sending request with allowed headers'):
|
||||||
|
check_full_access_to_container(other_wallet.wallet_path, cid, oid.pop(), file_path, xhdr=allow_headers)
|
||||||
|
|
||||||
|
with allure.step('Check other has no access when sending request with denied headers'):
|
||||||
|
check_no_access_to_container(other_wallet.wallet_path, cid, oid.pop(), file_path, xhdr=deny_headers)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('match_type', [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL])
|
||||||
|
def test_extended_acl_deny_filters_object(self, wallets, eacl_container_with_objects, match_type):
|
||||||
|
allure.dynamic.title(f"Validate NeoFS operations with deny user headers filter: {match_type.name}")
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
|
||||||
|
cid, objects_with_header, objects_with_other_header, objs_without_header, file_path = \
|
||||||
|
eacl_container_with_objects
|
||||||
|
|
||||||
|
with allure.step('Deny all operations for other with object filter'):
|
||||||
|
equal_filter = EACLFilter(**self.OBJ_EQUAL_FILTER.__dict__)
|
||||||
|
equal_filter.match_type = match_type
|
||||||
|
eacl_deny = [EACLRule(access=EACLAccess.DENY,
|
||||||
|
role=EACLRole.OTHERS,
|
||||||
|
filters=EACLFilters([equal_filter]),
|
||||||
|
operation=op) for op in self.OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl_deny))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
allow_objects = objects_with_other_header if match_type == EACLMatchType.STRING_EQUAL else objects_with_header
|
||||||
|
deny_objects = objects_with_header if match_type == EACLMatchType.STRING_EQUAL else objects_with_other_header
|
||||||
|
|
||||||
|
# We will attempt requests with various headers, but eACL rule should ignore request headers and validate
|
||||||
|
# only object headers
|
||||||
|
for xhdr in (self.ATTRIBUTE, self.OTHER_ATTRIBUTE, None):
|
||||||
|
with allure.step(f'Check other have full access to objects without attributes'):
|
||||||
|
check_full_access_to_container(
|
||||||
|
other_wallet.wallet_path, cid, objs_without_header.pop(), file_path, xhdr=xhdr)
|
||||||
|
|
||||||
|
with allure.step(f'Check other have full access to objects without deny attribute'):
|
||||||
|
check_full_access_to_container(
|
||||||
|
other_wallet.wallet_path, cid, allow_objects.pop(), file_path, xhdr=xhdr)
|
||||||
|
|
||||||
|
with allure.step(f'Check other have no access to objects with deny attribute'):
|
||||||
|
oid = deny_objects.pop()
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_get_head_object(other_wallet.wallet_path, cid, oid, xhdr=xhdr)
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_get_object(other_wallet.wallet_path, cid, oid, file_path, xhdr=xhdr)
|
||||||
|
|
||||||
|
allow_attribute = self.OTHER_ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.ATTRIBUTE
|
||||||
|
with allure.step('Check other can PUT objects without denied attribute'):
|
||||||
|
assert can_put_object(other_wallet.wallet_path, cid, file_path, attributes=allow_attribute)
|
||||||
|
assert can_put_object(other_wallet.wallet_path, cid, file_path)
|
||||||
|
|
||||||
|
deny_attribute = self.ATTRIBUTE if match_type == EACLMatchType.STRING_EQUAL else self.OTHER_ATTRIBUTE
|
||||||
|
with allure.step('Check other can not PUT objects with denied attribute'):
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_put_object(other_wallet.wallet_path, cid, file_path, attributes=deny_attribute)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('match_type', [EACLMatchType.STRING_EQUAL, EACLMatchType.STRING_NOT_EQUAL])
|
||||||
|
def test_extended_acl_allow_filters_object(self, wallets, eacl_container_with_objects, match_type):
|
||||||
|
allure.dynamic.title(
|
||||||
|
f"Testcase to validate NeoFS operation with allow eACL user headers filters: {match_type.name}")
|
||||||
|
user_wallet = wallets.get_wallet()
|
||||||
|
other_wallet = wallets.get_wallet(EACLRole.OTHERS)
|
||||||
|
cid, objects_with_header, objects_with_other_header, objects_without_header, file_path = \
|
||||||
|
eacl_container_with_objects
|
||||||
|
|
||||||
|
with allure.step('Deny all operations for others except few operations allowed by object filter'):
|
||||||
|
equal_filter = EACLFilter(**self.OBJ_EQUAL_FILTER.__dict__)
|
||||||
|
equal_filter.match_type = match_type
|
||||||
|
eacl = [
|
||||||
|
EACLRule(access=EACLAccess.ALLOW,
|
||||||
|
role=EACLRole.OTHERS,
|
||||||
|
filters=EACLFilters([equal_filter]),
|
||||||
|
operation=op) for op in self.OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS
|
||||||
|
] + [
|
||||||
|
EACLRule(access=EACLAccess.DENY,
|
||||||
|
role=EACLRole.OTHERS,
|
||||||
|
operation=op) for op in self.OBJECT_ATTRIBUTES_FILTER_SUPPORTED_OPERATIONS
|
||||||
|
|
||||||
|
]
|
||||||
|
set_eacl(user_wallet.wallet_path, cid, create_eacl(cid, eacl))
|
||||||
|
wait_for_cache_expired()
|
||||||
|
|
||||||
|
if match_type == EACLMatchType.STRING_EQUAL:
|
||||||
|
allow_objects = objects_with_header
|
||||||
|
deny_objects = objects_with_other_header
|
||||||
|
allow_attribute = self.ATTRIBUTE
|
||||||
|
deny_attribute = self.OTHER_ATTRIBUTE
|
||||||
|
else:
|
||||||
|
allow_objects = objects_with_other_header
|
||||||
|
deny_objects = objects_with_header
|
||||||
|
allow_attribute = self.OTHER_ATTRIBUTE
|
||||||
|
deny_attribute = self.ATTRIBUTE
|
||||||
|
|
||||||
|
with allure.step(f'Check other cannot get and put objects without attributes'):
|
||||||
|
oid = objects_without_header.pop()
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_get_head_object(other_wallet.wallet_path, cid, oid)
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_get_object(other_wallet.wallet_path, cid, oid, file_path)
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_put_object(other_wallet.wallet_path, cid, file_path)
|
||||||
|
|
||||||
|
with allure.step(f'Check other can get objects with attributes matching the filter'):
|
||||||
|
oid = allow_objects.pop()
|
||||||
|
assert can_get_head_object(other_wallet.wallet_path, cid, oid)
|
||||||
|
assert can_get_object(other_wallet.wallet_path, cid, oid, file_path)
|
||||||
|
assert can_put_object(other_wallet.wallet_path, cid, file_path, attributes=allow_attribute)
|
||||||
|
|
||||||
|
with allure.step(f'Check other cannot get objects without attributes matching the filter'):
|
||||||
|
oid = deny_objects.pop()
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_get_head_object(other_wallet.wallet_path, cid, oid)
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_get_object(other_wallet.wallet_path, cid, oid, file_path)
|
||||||
|
with pytest.raises(AssertionError):
|
||||||
|
assert can_put_object(other_wallet.wallet_path, cid, file_path, attributes=deny_attribute)
|
|
@ -3,7 +3,6 @@ import json
|
||||||
import allure
|
import allure
|
||||||
import pytest
|
import pytest
|
||||||
from epoch import tick_epoch
|
from epoch import tick_epoch
|
||||||
from grpc_responses import CONTAINER_NOT_FOUND, error_matches_status
|
|
||||||
from python_keywords.container import (create_container, delete_container, get_container, list_containers,
|
from python_keywords.container import (create_container, delete_container, get_container, list_containers,
|
||||||
wait_for_container_creation, wait_for_container_deletion)
|
wait_for_container_creation, wait_for_container_deletion)
|
||||||
from utility import placement_policy_from_container
|
from utility import placement_policy_from_container
|
||||||
|
@ -28,7 +27,7 @@ def test_container_creation(prepare_wallet_and_deposit, name):
|
||||||
assert cid in containers, f'Expected container {cid} in containers: {containers}'
|
assert cid in containers, f'Expected container {cid} in containers: {containers}'
|
||||||
|
|
||||||
container_info: str = get_container(wallet, cid, json_mode=False)
|
container_info: str = get_container(wallet, cid, json_mode=False)
|
||||||
container_info = container_info.casefold() # To ignore case when comparing with expected values
|
container_info = container_info.casefold() # To ignore case when comparing with expected values
|
||||||
|
|
||||||
info_to_check = {
|
info_to_check = {
|
||||||
f'basic ACL: {PRIVATE_ACL_F} (private)',
|
f'basic ACL: {PRIVATE_ACL_F} (private)',
|
||||||
|
|
|
@ -45,8 +45,8 @@ def test_object_api(prepare_wallet_and_deposit, request, object_size):
|
||||||
|
|
||||||
with allure.step('Put objects'):
|
with allure.step('Put objects'):
|
||||||
oids.append(put_object(wallet=wallet, path=file_path, cid=cid))
|
oids.append(put_object(wallet=wallet, path=file_path, cid=cid))
|
||||||
oids.append(put_object(wallet=wallet, path=file_path, cid=cid, user_headers=file_usr_header))
|
oids.append(put_object(wallet=wallet, path=file_path, cid=cid, attributes=file_usr_header))
|
||||||
oids.append(put_object(wallet=wallet, path=file_path, cid=cid, user_headers=file_usr_header_oth))
|
oids.append(put_object(wallet=wallet, path=file_path, cid=cid, attributes=file_usr_header_oth))
|
||||||
|
|
||||||
with allure.step('Validate storage policy for objects'):
|
with allure.step('Validate storage policy for objects'):
|
||||||
for oid_to_check in oids:
|
for oid_to_check in oids:
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "SYSTEM"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "USER"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "ALLOW",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,123 +0,0 @@
|
||||||
{
|
|
||||||
"records": [
|
|
||||||
{
|
|
||||||
"operation": "GET",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "HEAD",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "PUT",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "DELETE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "SEARCH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGE",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operation": "GETRANGEHASH",
|
|
||||||
"action": "DENY",
|
|
||||||
"filters": [
|
|
||||||
{
|
|
||||||
"headerType": "REQUEST",
|
|
||||||
"matchType": "STRING_EQUAL",
|
|
||||||
"key": "a",
|
|
||||||
"value": "2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"role": "OTHERS"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,63 +1,119 @@
|
||||||
#!/usr/bin/python3.9
|
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import uuid
|
import uuid
|
||||||
from enum import Enum, auto
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from enum import Enum
|
||||||
|
from time import sleep
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
import allure
|
||||||
import base58
|
import base58
|
||||||
|
from cli import NeofsCli
|
||||||
from cli_helpers import _cmd_run
|
from cli_helpers import _cmd_run
|
||||||
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
|
||||||
from data_formatters import get_wallet_public_key
|
from data_formatters import get_wallet_public_key
|
||||||
from robot.api import logger
|
|
||||||
from robot.api.deco import keyword
|
|
||||||
|
|
||||||
"""
|
logger = logging.getLogger('NeoLogger')
|
||||||
Robot Keywords and helper functions for work with NeoFS ACL.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ROBOT_AUTO_KEYWORDS = False
|
|
||||||
EACL_LIFETIME = 100500
|
EACL_LIFETIME = 100500
|
||||||
|
NEOFS_CONTRACT_CACHE_TIMEOUT = 30
|
||||||
|
|
||||||
|
|
||||||
class AutoName(Enum):
|
class EACLOperation(Enum):
|
||||||
def _generate_next_value_(name, start, count, last_values):
|
PUT = 'put'
|
||||||
return name
|
GET = 'get'
|
||||||
|
HEAD = 'head'
|
||||||
|
GET_RANGE = 'getrange'
|
||||||
|
GET_RANGE_HASH = 'getrangehash'
|
||||||
|
SEARCH = 'search'
|
||||||
|
DELETE = 'delete'
|
||||||
|
|
||||||
|
|
||||||
class Role(AutoName):
|
class EACLAccess(Enum):
|
||||||
USER = auto()
|
ALLOW = 'allow'
|
||||||
SYSTEM = auto()
|
DENY = 'deny'
|
||||||
OTHERS = auto()
|
|
||||||
|
|
||||||
|
|
||||||
@keyword('Get eACL')
|
class EACLRole(Enum):
|
||||||
|
OTHERS = 'others'
|
||||||
|
USER = 'user'
|
||||||
|
SYSTEM = 'system'
|
||||||
|
|
||||||
|
|
||||||
|
class EACLHeaderType(Enum):
|
||||||
|
REQUEST = 'req' # Filter request headers
|
||||||
|
OBJECT = 'obj' # Filter object headers
|
||||||
|
SERVICE = 'SERVICE' # Filter service headers. These are not processed by NeoFS nodes and exist for service use only
|
||||||
|
|
||||||
|
|
||||||
|
class EACLMatchType(Enum):
|
||||||
|
STRING_EQUAL = '=' # Return true if strings are equal
|
||||||
|
STRING_NOT_EQUAL = '!=' # Return true if strings are different
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EACLFilter:
|
||||||
|
header_type: EACLHeaderType = EACLHeaderType.REQUEST
|
||||||
|
match_type: EACLMatchType = EACLMatchType.STRING_EQUAL
|
||||||
|
key: Optional[str] = None
|
||||||
|
value: Optional[str] = None
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {'headerType': self.header_type, 'matchType': self.match_type, 'key': self.key, 'value': self.value}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EACLFilters:
|
||||||
|
filters: Optional[List[EACLFilter]] = None
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ','.join(
|
||||||
|
[f'{filter.header_type.value}:{filter.key}{filter.match_type.value}{filter.value}'
|
||||||
|
for filter in self.filters]
|
||||||
|
) if self.filters else []
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EACLPubKey:
|
||||||
|
keys: Optional[List[str]] = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class EACLRule:
|
||||||
|
operation: Optional[EACLOperation] = None
|
||||||
|
access: Optional[EACLAccess] = None
|
||||||
|
role: Optional[Union[EACLRole, str]] = None
|
||||||
|
filters: Optional[EACLFilters] = None
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {'Operation': self.operation, 'Access': self.access, 'Role': self.role,
|
||||||
|
'Filters': self.filters or []}
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
role = self.role.value if isinstance(self.role, EACLRole) else f'pubkey:{get_wallet_public_key(self.role, "")}'
|
||||||
|
return f'{self.access.value} {self.operation.value} {self.filters or ""} {role}'
|
||||||
|
|
||||||
|
|
||||||
|
@allure.title('Get extended ACL')
|
||||||
def get_eacl(wallet_path: str, cid: str) -> Optional[str]:
|
def get_eacl(wallet_path: str, cid: str) -> Optional[str]:
|
||||||
cmd = (
|
cli = NeofsCli(config=WALLET_CONFIG)
|
||||||
f'{NEOFS_CLI_EXEC} --rpc-endpoint {NEOFS_ENDPOINT} --wallet {wallet_path} '
|
|
||||||
f'container get-eacl --cid {cid} --config {WALLET_CONFIG}'
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
output = _cmd_run(cmd)
|
output = cli.container.get_eacl(wallet=wallet_path, rpc_endpoint=NEOFS_ENDPOINT, cid=cid)
|
||||||
if re.search(r'extended ACL table is not set for this container', output):
|
|
||||||
return None
|
|
||||||
return output
|
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
logger.info("Extended ACL table is not set for this container")
|
logger.info("Extended ACL table is not set for this container")
|
||||||
logger.info(f"Got exception while getting eacl: {exc}")
|
logger.info(f"Got exception while getting eacl: {exc}")
|
||||||
return None
|
return None
|
||||||
|
if 'extended ACL table is not set for this container' in output:
|
||||||
|
return None
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
@keyword('Set eACL')
|
@allure.title('Set extended ACL')
|
||||||
def set_eacl(wallet_path: str, cid: str, eacl_table_path: str) -> None:
|
def set_eacl(wallet_path: str, cid: str, eacl_table_path: str) -> None:
|
||||||
cmd = (
|
cli = NeofsCli(config=WALLET_CONFIG, timeout=60)
|
||||||
f'{NEOFS_CLI_EXEC} --rpc-endpoint {NEOFS_ENDPOINT} --wallet {wallet_path} '
|
cli.container.set_eacl(wallet=wallet_path, rpc_endpoint=NEOFS_ENDPOINT, cid=cid, table=eacl_table_path,
|
||||||
f'container set-eacl --cid {cid} --table {eacl_table_path} --config {WALLET_CONFIG} --await'
|
await_mode=True)
|
||||||
)
|
|
||||||
_cmd_run(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def _encode_cid_for_eacl(cid: str) -> str:
|
def _encode_cid_for_eacl(cid: str) -> str:
|
||||||
|
@ -65,13 +121,9 @@ def _encode_cid_for_eacl(cid: str) -> str:
|
||||||
return base64.b64encode(cid_base58).decode("utf-8")
|
return base64.b64encode(cid_base58).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
@keyword('Create eACL')
|
def create_eacl(cid: str, rules_list: List[EACLRule]) -> str:
|
||||||
def create_eacl(cid: str, rules_list: list) -> str:
|
|
||||||
table_file_path = f"{os.getcwd()}/{ASSETS_DIR}/eacl_table_{str(uuid.uuid4())}.json"
|
table_file_path = f"{os.getcwd()}/{ASSETS_DIR}/eacl_table_{str(uuid.uuid4())}.json"
|
||||||
rules = " ".join(f"--rule '{rule}'" for rule in rules_list)
|
NeofsCli().acl.extended_create(cid=cid, out=table_file_path, rule=rules_list)
|
||||||
|
|
||||||
cmd = f"{NEOFS_CLI_EXEC} acl extended create --cid {cid} {rules} --out {table_file_path}"
|
|
||||||
_cmd_run(cmd)
|
|
||||||
|
|
||||||
with open(table_file_path, 'r') as file:
|
with open(table_file_path, 'r') as file:
|
||||||
table_data = file.read()
|
table_data = file.read()
|
||||||
|
@ -80,11 +132,10 @@ def create_eacl(cid: str, rules_list: list) -> str:
|
||||||
return table_file_path
|
return table_file_path
|
||||||
|
|
||||||
|
|
||||||
@keyword('Form BearerToken File')
|
def form_bearertoken_file(wif: str, cid: str, eacl_rule_list: List[Union[EACLRule, EACLPubKey]]) -> str:
|
||||||
def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
|
||||||
"""
|
"""
|
||||||
This function fetches eACL for given <cid> on behalf of <wif>,
|
This function fetches eACL for given <cid> on behalf of <wif>,
|
||||||
then extends it with filters taken from <eacl_records>, signs
|
then extends it with filters taken from <eacl_rules>, signs
|
||||||
with bearer token and writes to file
|
with bearer token and writes to file
|
||||||
"""
|
"""
|
||||||
enc_cid = _encode_cid_for_eacl(cid)
|
enc_cid = _encode_cid_for_eacl(cid)
|
||||||
|
@ -93,8 +144,7 @@ def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
||||||
eacl = get_eacl(wif, cid)
|
eacl = get_eacl(wif, cid)
|
||||||
json_eacl = dict()
|
json_eacl = dict()
|
||||||
if eacl:
|
if eacl:
|
||||||
eacl = eacl.replace('eACL: ', '')
|
eacl = eacl.replace('eACL: ', '').split('Signature')[0]
|
||||||
eacl = eacl.split('Signature')[0]
|
|
||||||
json_eacl = json.loads(eacl)
|
json_eacl = json.loads(eacl)
|
||||||
logger.info(json_eacl)
|
logger.info(json_eacl)
|
||||||
eacl_result = {
|
eacl_result = {
|
||||||
|
@ -117,32 +167,28 @@ def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if not eacl_records:
|
assert eacl_rules, 'Got empty eacl_records list'
|
||||||
raise (f"Got empty eacl_records list: {eacl_records}")
|
for rule in eacl_rule_list:
|
||||||
for record in eacl_records:
|
|
||||||
op_data = {
|
op_data = {
|
||||||
"operation": record['Operation'],
|
"operation": rule.operation.value.upper(),
|
||||||
"action": record['Access'],
|
"action": rule.access.value.upper(),
|
||||||
"filters": [],
|
"filters": rule.filters or [],
|
||||||
"targets": []
|
"targets": []
|
||||||
}
|
}
|
||||||
|
|
||||||
if Role(record['Role']):
|
if isinstance(rule.role, EACLRole):
|
||||||
op_data['targets'] = [
|
op_data['targets'] = [
|
||||||
{
|
{
|
||||||
"role": record['Role']
|
"role": rule.role.value.upper()
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
else:
|
elif isinstance(rule.role, EACLPubKey):
|
||||||
op_data['targets'] = [
|
op_data['targets'] = [
|
||||||
{
|
{
|
||||||
"keys": [record['Role']]
|
'keys': rule.role.keys
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
if 'Filters' in record.keys():
|
|
||||||
op_data["filters"].append(record['Filters'])
|
|
||||||
|
|
||||||
eacl_result["body"]["eaclTable"]["records"].append(op_data)
|
eacl_result["body"]["eaclTable"]["records"].append(op_data)
|
||||||
|
|
||||||
# Add records from current eACL
|
# Add records from current eACL
|
||||||
|
@ -158,7 +204,6 @@ def form_bearertoken_file(wif: str, cid: str, eacl_records: list) -> str:
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
|
||||||
@keyword('EACL Rules')
|
|
||||||
def eacl_rules(access: str, verbs: list, user: str) -> list[str]:
|
def eacl_rules(access: str, verbs: list, user: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
This function creates a list of eACL rules.
|
This function creates a list of eACL rules.
|
||||||
|
@ -188,3 +233,9 @@ def sign_bearer_token(wallet_path: str, eacl_rules_file: str) -> None:
|
||||||
f'--to {eacl_rules_file} --wallet {wallet_path} --config {WALLET_CONFIG} --json'
|
f'--to {eacl_rules_file} --wallet {wallet_path} --config {WALLET_CONFIG} --json'
|
||||||
)
|
)
|
||||||
_cmd_run(cmd)
|
_cmd_run(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
@allure.title('Wait for eACL cache expired')
|
||||||
|
def wait_for_cache_expired():
|
||||||
|
sleep(NEOFS_CONTRACT_CACHE_TIMEOUT)
|
||||||
|
return
|
||||||
|
|
47
robot/resources/lib/python_keywords/cli/acl.py
Normal file
47
robot/resources/lib/python_keywords/cli/acl.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .cli_command import NeofsCliCommandBase
|
||||||
|
|
||||||
|
|
||||||
|
class NeofsCliACL(NeofsCliCommandBase):
|
||||||
|
def extended_create(self, cid: str, out: str, file: Optional[str] = None, rule: Optional[list] = None) -> str:
|
||||||
|
|
||||||
|
"""Create extended ACL from the text representation.
|
||||||
|
|
||||||
|
Rule consist of these blocks: <action> <operation> [<filter1> ...] [<target1> ...]
|
||||||
|
Action is 'allow' or 'deny'.
|
||||||
|
Operation is an object service verb: 'get', 'head', 'put', 'search', 'delete', 'getrange', or 'getrangehash'.
|
||||||
|
|
||||||
|
Filter consists of <typ>:<key><match><value>
|
||||||
|
Typ is 'obj' for object applied filter or 'req' for request applied filter.
|
||||||
|
Key is a valid unicode string corresponding to object or request header key.
|
||||||
|
Well-known system object headers start with '$Object:' prefix.
|
||||||
|
User defined headers start without prefix.
|
||||||
|
Read more about filter keys at:
|
||||||
|
http://github.com/nspcc-dev/neofs-api/blob/master/proto-docs/acl.md#message-eaclrecordfilter
|
||||||
|
Match is '=' for matching and '!=' for non-matching filter.
|
||||||
|
Value is a valid unicode string corresponding to object or request header value.
|
||||||
|
|
||||||
|
Target is
|
||||||
|
'user' for container owner,
|
||||||
|
'system' for Storage nodes in container and Inner Ring nodes,
|
||||||
|
'others' for all other request senders,
|
||||||
|
'pubkey:<key1>,<key2>,...' for exact request sender, where <key> is a hex-encoded 33-byte public key.
|
||||||
|
|
||||||
|
When both '--rule' and '--file' arguments are used, '--rule' records will be placed higher in resulting
|
||||||
|
extended ACL table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cid: Container ID
|
||||||
|
file: Read list of extended ACL table records from from text file
|
||||||
|
out: Save JSON formatted extended ACL table in file
|
||||||
|
rule: Extended ACL table record to apply
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Command string
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._execute(
|
||||||
|
'acl extended create',
|
||||||
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
|
)
|
|
@ -3,6 +3,7 @@ from typing import Optional
|
||||||
from common import NEOFS_CLI_EXEC
|
from common import NEOFS_CLI_EXEC
|
||||||
|
|
||||||
from .accounting import NeofsCliAccounting
|
from .accounting import NeofsCliAccounting
|
||||||
|
from .acl import NeofsCliACL
|
||||||
from .cli_command import NeofsCliCommandBase
|
from .cli_command import NeofsCliCommandBase
|
||||||
from .container import NeofsCliContainer
|
from .container import NeofsCliContainer
|
||||||
from .object import NeofsCliObject
|
from .object import NeofsCliObject
|
||||||
|
@ -12,6 +13,7 @@ class NeofsCli:
|
||||||
neofs_cli_exec_path: Optional[str] = None
|
neofs_cli_exec_path: Optional[str] = None
|
||||||
config: Optional[str] = None
|
config: Optional[str] = None
|
||||||
accounting: Optional[NeofsCliAccounting] = None
|
accounting: Optional[NeofsCliAccounting] = None
|
||||||
|
acl: Optional[NeofsCliACL] = None
|
||||||
container: Optional[NeofsCliContainer] = None
|
container: Optional[NeofsCliContainer] = None
|
||||||
object: Optional[NeofsCliObject] = None
|
object: Optional[NeofsCliObject] = None
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@ class NeofsCli:
|
||||||
self.config = config # config(str): config file (default is $HOME/.config/neofs-cli/config.yaml)
|
self.config = config # config(str): config file (default is $HOME/.config/neofs-cli/config.yaml)
|
||||||
self.neofs_cli_exec_path = neofs_cli_exec_path or NEOFS_CLI_EXEC
|
self.neofs_cli_exec_path = neofs_cli_exec_path or NEOFS_CLI_EXEC
|
||||||
self.accounting = NeofsCliAccounting(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
self.accounting = NeofsCliAccounting(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
||||||
|
self.acl = NeofsCliACL(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
||||||
self.container = NeofsCliContainer(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
self.container = NeofsCliContainer(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
||||||
self.object = NeofsCliObject(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
self.object = NeofsCliObject(self.neofs_cli_exec_path, timeout=timeout, config=config)
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,21 @@ class NeofsCliCommandBase:
|
||||||
continue
|
continue
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
param_str.append(f'--{param}')
|
param_str.append(f'--{param}')
|
||||||
|
elif isinstance(value, int):
|
||||||
|
param_str.append(f'--{param} {value}')
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
param_str.append(f'--{param} \'{",".join(value)}\'')
|
for value_item in value:
|
||||||
|
val_str = str(value_item).replace("'", "\\'")
|
||||||
|
param_str.append(f"--{param} '{val_str}'")
|
||||||
elif isinstance(value, dict):
|
elif isinstance(value, dict):
|
||||||
param_str.append(f'--{param} \'{",".join(f"{key}={val}" for key, val in value.items())}\'')
|
param_str.append(f'--{param} \'{",".join(f"{key}={val}" for key, val in value.items())}\'')
|
||||||
else:
|
else:
|
||||||
value_str = str(value).replace("'", "\\'")
|
if "'" in str(value):
|
||||||
param_str.append(f"--{param} '{value_str}'")
|
value_str = str(value).replace('"', '\\"')
|
||||||
|
param_str.append(f'--{param} "{value_str}"')
|
||||||
|
else:
|
||||||
|
param_str.append(f"--{param} '{value}'")
|
||||||
|
|
||||||
param_str = ' '.join(param_str)
|
param_str = ' '.join(param_str)
|
||||||
|
|
||||||
return f'{self.neofs_cli_exec} {self.__base_params} {command or ""} {param_str}'
|
return f'{self.neofs_cli_exec} {self.__base_params} {command or ""} {param_str}'
|
||||||
|
|
|
@ -8,7 +8,7 @@ class NeofsCliContainer(NeofsCliCommandBase):
|
||||||
basic_acl: Optional[str] = None, await_mode: bool = False, disable_timestamp: bool = False,
|
basic_acl: Optional[str] = None, await_mode: bool = False, disable_timestamp: bool = False,
|
||||||
name: Optional[str] = None, nonce: Optional[str] = None, policy: Optional[str] = None,
|
name: Optional[str] = None, nonce: Optional[str] = None, policy: Optional[str] = None,
|
||||||
session: Optional[str] = None, subnet: Optional[str] = None, ttl: Optional[int] = None,
|
session: Optional[str] = None, subnet: Optional[str] = None, ttl: Optional[int] = None,
|
||||||
xhdr: Optional[list] = None) -> str:
|
xhdr: Optional[dict] = None) -> str:
|
||||||
"""Create a new container and register it in the NeoFS.
|
"""Create a new container and register it in the NeoFS.
|
||||||
It will be stored in the sidechain when the Inner Ring accepts it.
|
It will be stored in the sidechain when the Inner Ring accepts it.
|
||||||
|
|
||||||
|
@ -39,7 +39,8 @@ class NeofsCliContainer(NeofsCliCommandBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def delete(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None, await_mode: bool = False,
|
def delete(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None, await_mode: bool = False,
|
||||||
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[list] = None) -> str:
|
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None,
|
||||||
|
force: bool = False) -> str:
|
||||||
"""Delete an existing container.
|
"""Delete an existing container.
|
||||||
Only the owner of the container has permission to remove the container.
|
Only the owner of the container has permission to remove the container.
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ class NeofsCliContainer(NeofsCliCommandBase):
|
||||||
address: address of wallet account
|
address: address of wallet account
|
||||||
await_mode: block execution until container is removed
|
await_mode: block execution until container is removed
|
||||||
cid: container ID
|
cid: container ID
|
||||||
|
force: do not check whether container contains locks and remove immediately
|
||||||
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
|
rpc_endpoint: remote node address (as 'multiaddr' or '<host>:<port>')
|
||||||
session: path to a JSON-encoded container session token
|
session: path to a JSON-encoded container session token
|
||||||
ttl: TTL value in request meta header (default 2)
|
ttl: TTL value in request meta header (default 2)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from .cli_command import NeofsCliCommandBase
|
||||||
class NeofsCliObject(NeofsCliCommandBase):
|
class NeofsCliObject(NeofsCliCommandBase):
|
||||||
def delete(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
def delete(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
|
bearer: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
|
||||||
xhdr: Optional[list] = None, **params) -> str:
|
xhdr: Optional[dict] = None) -> str:
|
||||||
"""Delete object from NeoFS
|
"""Delete object from NeoFS
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -26,13 +26,13 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object delete',
|
'object delete',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
||||||
def get(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
def get(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, file: Optional[str] = None,
|
bearer: Optional[str] = None, file: Optional[str] = None,
|
||||||
header: Optional[str] = None, no_progress: bool = False, raw: bool = False,
|
header: Optional[str] = None, no_progress: bool = False, raw: bool = False,
|
||||||
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[list] = None, **params) -> str:
|
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
|
||||||
"""Get object from NeoFS
|
"""Get object from NeoFS
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -56,13 +56,12 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object get',
|
'object get',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
||||||
def hash(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
def hash(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, range: Optional[str] = None, salt: Optional[str] = None,
|
bearer: Optional[str] = None, range: Optional[str] = None, salt: Optional[str] = None,
|
||||||
ttl: Optional[int] = None, hash_type: Optional[str] = None, xhdr: Optional[list] = None,
|
ttl: Optional[int] = None, hash_type: Optional[str] = None, xhdr: Optional[dict] = None) -> str:
|
||||||
**params) -> str:
|
|
||||||
"""Get object hash
|
"""Get object hash
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -90,7 +89,7 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
def head(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
def head(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, file: Optional[str] = None,
|
bearer: Optional[str] = None, file: Optional[str] = None,
|
||||||
json_mode: bool = False, main_only: bool = False, proto: bool = False, raw: bool = False,
|
json_mode: bool = False, main_only: bool = False, proto: bool = False, raw: bool = False,
|
||||||
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[list] = None, **params) -> str:
|
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
|
||||||
"""Get object header
|
"""Get object header
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -116,12 +115,12 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object head',
|
'object head',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
||||||
def lock(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, lifetime: int, address: Optional[str] = None,
|
def lock(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, lifetime: int, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, session: Optional[str] = None,
|
bearer: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
|
||||||
ttl: Optional[int] = None, xhdr: Optional[list] = None, **params) -> str:
|
xhdr: Optional[dict] = None) -> str:
|
||||||
"""Lock object in container
|
"""Lock object in container
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -143,14 +142,14 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object lock',
|
'object lock',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
||||||
def put(self, rpc_endpoint: str, wallet: str, cid: str, file: str, address: Optional[str] = None,
|
def put(self, rpc_endpoint: str, wallet: str, cid: str, file: str, address: Optional[str] = None,
|
||||||
attributes: Optional[dict] = None, bearer: Optional[str] = None, disable_filename: bool = False,
|
attributes: Optional[dict] = None, bearer: Optional[str] = None, disable_filename: bool = False,
|
||||||
disable_timestamp: bool = False, expire_at: Optional[int] = None, no_progress: bool = False,
|
disable_timestamp: bool = False, expire_at: Optional[int] = None, no_progress: bool = False,
|
||||||
notify: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
|
notify: Optional[str] = None, session: Optional[str] = None, ttl: Optional[int] = None,
|
||||||
xhdr: Optional[list] = None, **params) -> str:
|
xhdr: Optional[dict] = None) -> str:
|
||||||
"""Put object to NeoFS
|
"""Put object to NeoFS
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -176,12 +175,12 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object put',
|
'object put',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
||||||
def range(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, range: str, address: Optional[str] = None,
|
def range(self, rpc_endpoint: str, wallet: str, cid: str, oid: str, range: str, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, file: Optional[str] = None, json_mode: bool = False, raw: bool = False,
|
bearer: Optional[str] = None, file: Optional[str] = None, json_mode: bool = False, raw: bool = False,
|
||||||
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[list] = None, **params) -> str:
|
session: Optional[str] = None, ttl: Optional[int] = None, xhdr: Optional[dict] = None) -> str:
|
||||||
"""Get payload range data of an object
|
"""Get payload range data of an object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -206,13 +205,13 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object range',
|
'object range',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
||||||
def search(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None,
|
def search(self, rpc_endpoint: str, wallet: str, cid: str, address: Optional[str] = None,
|
||||||
bearer: Optional[str] = None, filters: Optional[list] = None, oid: Optional[str] = None,
|
bearer: Optional[str] = None, filters: Optional[list] = None, oid: Optional[str] = None,
|
||||||
phy: bool = False, root: bool = False, session: Optional[str] = None, ttl: Optional[int] = None,
|
phy: bool = False, root: bool = False, session: Optional[str] = None, ttl: Optional[int] = None,
|
||||||
xhdr: Optional[list] = None, **params) -> str:
|
xhdr: Optional[dict] = None) -> str:
|
||||||
"""Search object
|
"""Search object
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -236,5 +235,5 @@ class NeofsCliObject(NeofsCliCommandBase):
|
||||||
"""
|
"""
|
||||||
return self._execute(
|
return self._execute(
|
||||||
'object search',
|
'object search',
|
||||||
**{param: param_value for param, param_value in locals().items() if param not in ['self', 'params']}
|
**{param: param_value for param, param_value in locals().items() if param not in ['self']}
|
||||||
)
|
)
|
||||||
|
|
|
@ -130,17 +130,18 @@ def get_container(wallet: str, cid: str, json_mode: bool = True) -> Union[dict,
|
||||||
@keyword('Delete Container')
|
@keyword('Delete Container')
|
||||||
# TODO: make the error message about a non-found container more user-friendly
|
# TODO: make the error message about a non-found container more user-friendly
|
||||||
# https://github.com/nspcc-dev/neofs-contract/issues/121
|
# https://github.com/nspcc-dev/neofs-contract/issues/121
|
||||||
def delete_container(wallet: str, cid: str) -> None:
|
def delete_container(wallet: str, cid: str, force: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
A wrapper for `neofs-cli container delete` call.
|
A wrapper for `neofs-cli container delete` call.
|
||||||
Args:
|
Args:
|
||||||
wallet (str): path to a wallet on whose behalf we delete the container
|
wallet (str): path to a wallet on whose behalf we delete the container
|
||||||
cid (str): ID of the container to delete
|
cid (str): ID of the container to delete
|
||||||
|
force (bool): do not check whether container contains locks and remove immediately
|
||||||
This function doesn't return anything.
|
This function doesn't return anything.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cli = NeofsCli(config=WALLET_CONFIG)
|
cli = NeofsCli(config=WALLET_CONFIG)
|
||||||
cli.container.delete(wallet=wallet, cid=cid, rpc_endpoint=NEOFS_ENDPOINT)
|
cli.container.delete(wallet=wallet, cid=cid, rpc_endpoint=NEOFS_ENDPOINT, force=force)
|
||||||
|
|
||||||
|
|
||||||
def _parse_cid(output: str) -> str:
|
def _parse_cid(output: str) -> str:
|
||||||
|
|
70
robot/resources/lib/python_keywords/container_access.py
Normal file
70
robot/resources/lib/python_keywords/container_access.py
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from acl import EACLOperation
|
||||||
|
from python_keywords.object_access import (can_get_object, can_put_object, can_delete_object, can_get_head_object,
|
||||||
|
can_get_range_hash_of_object, can_get_range_of_object, can_search_object)
|
||||||
|
|
||||||
|
|
||||||
|
def check_full_access_to_container(wallet: str, cid: str, oid: str, file_name: str,
|
||||||
|
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
|
||||||
|
xhdr: Optional[dict] = None):
|
||||||
|
assert can_put_object(wallet, cid, file_name, bearer, wallet_config, xhdr)
|
||||||
|
assert can_get_head_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert can_get_range_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert can_get_range_hash_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert can_search_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert can_get_object(wallet, cid, oid, file_name, bearer, wallet_config, xhdr)
|
||||||
|
assert can_delete_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
|
||||||
|
|
||||||
|
def check_no_access_to_container(wallet: str, cid: str, oid: str, file_name: str,
|
||||||
|
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
|
||||||
|
xhdr: Optional[dict] = None):
|
||||||
|
assert not can_put_object(wallet, cid, file_name, bearer, wallet_config, xhdr)
|
||||||
|
assert not can_get_head_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert not can_get_range_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert not can_get_range_hash_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert not can_search_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
assert not can_get_object(wallet, cid, oid, file_name, bearer, wallet_config, xhdr)
|
||||||
|
assert not can_delete_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
|
||||||
|
|
||||||
|
def check_custom_access_to_container(wallet: str, cid: str, oid: str, file_name: str,
|
||||||
|
deny_operations: Optional[List[EACLOperation]] = None,
|
||||||
|
ignore_operations: Optional[List[EACLOperation]] = None,
|
||||||
|
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
|
||||||
|
xhdr: Optional[dict] = None):
|
||||||
|
deny_operations = [op.value for op in deny_operations or []]
|
||||||
|
ignore_operations = [op.value for op in ignore_operations or []]
|
||||||
|
checks: dict = {}
|
||||||
|
if EACLOperation.PUT.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.PUT.value] = can_put_object(wallet, cid, file_name, bearer, wallet_config, xhdr)
|
||||||
|
if EACLOperation.HEAD.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.HEAD.value] = can_get_head_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
if EACLOperation.GET_RANGE.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.GET_RANGE.value] = can_get_range_of_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
if EACLOperation.GET_RANGE_HASH.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.GET_RANGE_HASH.value] = can_get_range_hash_of_object(wallet, cid, oid, bearer,
|
||||||
|
wallet_config, xhdr)
|
||||||
|
if EACLOperation.SEARCH.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.SEARCH.value] = can_search_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
if EACLOperation.GET.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.GET.value] = can_get_object(wallet, cid, oid, file_name, bearer, wallet_config, xhdr)
|
||||||
|
if EACLOperation.DELETE.value not in ignore_operations:
|
||||||
|
checks[EACLOperation.DELETE.value] = can_delete_object(wallet, cid, oid, bearer, wallet_config, xhdr)
|
||||||
|
|
||||||
|
failed_checks = (
|
||||||
|
[f'allowed {action} failed' for action, success in checks.items() if
|
||||||
|
not success and action not in deny_operations] +
|
||||||
|
[f'denied {action} succeeded' for action, success in checks.items() if
|
||||||
|
success and action in deny_operations])
|
||||||
|
|
||||||
|
assert not failed_checks, ", ".join(failed_checks)
|
||||||
|
|
||||||
|
|
||||||
|
def check_read_only_container(wallet: str, cid: str, oid: str, file_name: str,
|
||||||
|
bearer: Optional[str] = None, wallet_config: Optional[str] = None,
|
||||||
|
xhdr: Optional[dict] = None):
|
||||||
|
return check_custom_access_to_container(wallet, cid, oid, file_name,
|
||||||
|
deny_operations=[EACLOperation.PUT, EACLOperation.DELETE],
|
||||||
|
bearer=bearer, wallet_config=wallet_config, xhdr=xhdr)
|
|
@ -21,8 +21,8 @@ ROBOT_AUTO_KEYWORDS = False
|
||||||
|
|
||||||
@keyword('Get object')
|
@keyword('Get object')
|
||||||
def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = None, write_object: str = "",
|
def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = None, write_object: str = "",
|
||||||
endpoint: str = "", options: Optional[dict] = None, wallet_config: str = WALLET_CONFIG,
|
endpoint: str = "", xhdr: Optional[dict] = None, wallet_config: Optional[str] = None,
|
||||||
no_progress: bool = True):
|
no_progress: bool = True) -> str:
|
||||||
"""
|
"""
|
||||||
GET from NeoFS.
|
GET from NeoFS.
|
||||||
|
|
||||||
|
@ -35,11 +35,12 @@ def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = No
|
||||||
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
endpoint (optional, str): NeoFS endpoint to send request to, appends to `--rpc-endpoint` key
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
no_progress(optional, bool): do not show progress bar
|
no_progress(optional, bool): do not show progress bar
|
||||||
options (optional, str): any options which `neofs-cli object get` accepts
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
(str): path to downloaded file
|
(str): path to downloaded file
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
if not write_object:
|
if not write_object:
|
||||||
write_object = str(uuid.uuid4())
|
write_object = str(uuid.uuid4())
|
||||||
file_path = f"{ASSETS_DIR}/{write_object}"
|
file_path = f"{ASSETS_DIR}/{write_object}"
|
||||||
|
@ -49,7 +50,7 @@ def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = No
|
||||||
|
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
cli.object.get(rpc_endpoint=endpoint, wallet=wallet, cid=cid, oid=oid, file=file_path,
|
cli.object.get(rpc_endpoint=endpoint, wallet=wallet, cid=cid, oid=oid, file=file_path,
|
||||||
bearer=bearer_token, no_progress=no_progress, **options or {})
|
bearer=bearer_token, no_progress=no_progress, xhdr=xhdr)
|
||||||
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ def get_object(wallet: str, cid: str, oid: str, bearer_token: Optional[str] = No
|
||||||
# TODO: make `bearer_token` optional
|
# TODO: make `bearer_token` optional
|
||||||
@keyword('Get Range Hash')
|
@keyword('Get Range Hash')
|
||||||
def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut: str,
|
def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut: str,
|
||||||
wallet_config: str = WALLET_CONFIG, options: Optional[dict] = None):
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None):
|
||||||
"""
|
"""
|
||||||
GETRANGEHASH of given Object.
|
GETRANGEHASH of given Object.
|
||||||
|
|
||||||
|
@ -69,23 +70,24 @@ def get_range_hash(wallet: str, cid: str, oid: str, bearer_token: str, range_cut
|
||||||
value to pass to the `--range` parameter
|
value to pass to the `--range` parameter
|
||||||
bearer_token (optional, str): path to Bearer Token file, appends to `--bearer` key
|
bearer_token (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
options (optional, str): any options which `neofs-cli object hash` accepts
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
output = cli.object.hash(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut,
|
output = cli.object.hash(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut,
|
||||||
bearer=bearer_token, **options or {})
|
bearer=bearer_token, xhdr=xhdr)
|
||||||
|
|
||||||
# cutting off output about range offset and length
|
# cutting off output about range offset and length
|
||||||
return output.split(':')[1].strip()
|
return output.split(':')[1].strip()
|
||||||
|
|
||||||
|
|
||||||
@keyword('Put object')
|
@keyword('Put object')
|
||||||
def put_object(wallet: str, path: str, cid: str, bearer: str = "", user_headers: Optional[dict] = None,
|
def put_object(wallet: str, path: str, cid: str, bearer: str = "", attributes: Optional[dict] = None,
|
||||||
endpoint: str = "", wallet_config: str = WALLET_CONFIG, expire_at: Optional[int] = None,
|
xhdr: Optional[dict] = None, endpoint: str = "", wallet_config: Optional[str] = None,
|
||||||
no_progress: bool = True, options: Optional[dict] = None):
|
expire_at: Optional[int] = None, no_progress: bool = True):
|
||||||
"""
|
"""
|
||||||
PUT of given file.
|
PUT of given file.
|
||||||
|
|
||||||
|
@ -94,24 +96,24 @@ def put_object(wallet: str, path: str, cid: str, bearer: str = "", user_headers:
|
||||||
path (str): path to file to be PUT
|
path (str): path to file to be PUT
|
||||||
cid (str): ID of Container where we get the Object from
|
cid (str): ID of Container where we get the Object from
|
||||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||||
user_headers (optional, dict): Object attributes, append to `--attributes` key
|
attributes (optional, str): User attributes in form of Key1=Value1,Key2=Value2
|
||||||
endpoint(optional, str): NeoFS endpoint to send request to
|
endpoint(optional, str): NeoFS endpoint to send request to
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
no_progress(optional, bool): do not show progress bar
|
no_progress(optional, bool): do not show progress bar
|
||||||
options (optional, str): any options which `neofs-cli object put` accepts
|
|
||||||
expire_at (optional, int): Last epoch in the life of the object
|
expire_at (optional, int): Last epoch in the life of the object
|
||||||
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
(str): ID of uploaded Object
|
(str): ID of uploaded Object
|
||||||
"""
|
"""
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
if not endpoint:
|
if not endpoint:
|
||||||
endpoint = random.sample(NEOFS_NETMAP, 1)[0]
|
endpoint = random.sample(NEOFS_NETMAP, 1)[0]
|
||||||
if not endpoint:
|
if not endpoint:
|
||||||
logger.info(f'---DEB:\n{NEOFS_NETMAP}')
|
logger.info(f'---DEB:\n{NEOFS_NETMAP}')
|
||||||
|
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
output = cli.object.put(rpc_endpoint=endpoint, wallet=wallet, file=path, cid=cid, bearer=bearer,
|
output = cli.object.put(rpc_endpoint=endpoint, wallet=wallet, file=path, cid=cid, attributes=attributes,
|
||||||
expire_at=expire_at, no_progress=no_progress,
|
bearer=bearer, expire_at=expire_at, no_progress=no_progress, xhdr=xhdr)
|
||||||
attributes=user_headers or {}, **options or {})
|
|
||||||
|
|
||||||
# splitting CLI output to lines and taking the penultimate line
|
# splitting CLI output to lines and taking the penultimate line
|
||||||
id_str = output.strip().split('\n')[-2]
|
id_str = output.strip().split('\n')[-2]
|
||||||
|
@ -120,8 +122,8 @@ def put_object(wallet: str, path: str, cid: str, bearer: str = "", user_headers:
|
||||||
|
|
||||||
|
|
||||||
@keyword('Delete object')
|
@keyword('Delete object')
|
||||||
def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_config: str = WALLET_CONFIG,
|
def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_config: Optional[str] = None,
|
||||||
options: Optional[dict] = None):
|
xhdr: Optional[dict] = None):
|
||||||
"""
|
"""
|
||||||
DELETE an Object.
|
DELETE an Object.
|
||||||
|
|
||||||
|
@ -131,14 +133,15 @@ def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_conf
|
||||||
oid (str): ID of Object we are going to delete
|
oid (str): ID of Object we are going to delete
|
||||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
options (optional, dict): any options which `neofs-cli object delete` accepts
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
(str): Tombstone ID
|
(str): Tombstone ID
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
output = cli.object.delete(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, bearer=bearer,
|
output = cli.object.delete(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, bearer=bearer,
|
||||||
**options or {})
|
xhdr=xhdr)
|
||||||
|
|
||||||
id_str = output.split('\n')[1]
|
id_str = output.split('\n')[1]
|
||||||
tombstone = id_str.split(':')[1]
|
tombstone = id_str.split(':')[1]
|
||||||
|
@ -146,8 +149,8 @@ def delete_object(wallet: str, cid: str, oid: str, bearer: str = "", wallet_conf
|
||||||
|
|
||||||
|
|
||||||
@keyword('Get Range')
|
@keyword('Get Range')
|
||||||
def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: str = WALLET_CONFIG,
|
def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: Optional[str] = None,
|
||||||
bearer: str = "", options: Optional[dict] = None):
|
bearer: str = "", xhdr: Optional[dict] = None):
|
||||||
"""
|
"""
|
||||||
GETRANGE an Object.
|
GETRANGE an Object.
|
||||||
|
|
||||||
|
@ -158,15 +161,16 @@ def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: st
|
||||||
range_cut (str): range to take data from in the form offset:length
|
range_cut (str): range to take data from in the form offset:length
|
||||||
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
bearer (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
options (optional, dict): any options which `neofs-cli object range` accepts
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
(str, bytes) - path to the file with range content and content of this file as bytes
|
(str, bytes) - path to the file with range content and content of this file as bytes
|
||||||
"""
|
"""
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
range_file = f"{ASSETS_DIR}/{uuid.uuid4()}"
|
range_file = f"{ASSETS_DIR}/{uuid.uuid4()}"
|
||||||
|
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
cli.object.range(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut, file=range_file,
|
cli.object.range(rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid, range=range_cut, file=range_file,
|
||||||
bearer=bearer, **options or {})
|
bearer=bearer, xhdr=xhdr)
|
||||||
|
|
||||||
with open(range_file, 'rb') as fout:
|
with open(range_file, 'rb') as fout:
|
||||||
content = fout.read()
|
content = fout.read()
|
||||||
|
@ -175,8 +179,8 @@ def get_range(wallet: str, cid: str, oid: str, range_cut: str, wallet_config: st
|
||||||
|
|
||||||
@keyword('Search object')
|
@keyword('Search object')
|
||||||
def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dict] = None,
|
def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dict] = None,
|
||||||
expected_objects_list: Optional[list] = None, wallet_config: str = WALLET_CONFIG,
|
expected_objects_list: Optional[list] = None, wallet_config: Optional[str] = None,
|
||||||
options: Optional[dict] = None):
|
xhdr: Optional[dict] = None) -> list:
|
||||||
"""
|
"""
|
||||||
SEARCH an Object.
|
SEARCH an Object.
|
||||||
|
|
||||||
|
@ -187,16 +191,16 @@ def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dic
|
||||||
filters (optional, dict): key=value pairs to filter Objects
|
filters (optional, dict): key=value pairs to filter Objects
|
||||||
expected_objects_list (optional, list): a list of ObjectIDs to compare found Objects with
|
expected_objects_list (optional, list): a list of ObjectIDs to compare found Objects with
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
options(optional, str): any other options which `neofs-cli object search` might accept
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
(list): list of found ObjectIDs
|
(list): list of found ObjectIDs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
output = cli.object.search(
|
output = cli.object.search(
|
||||||
rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, bearer=bearer,
|
rpc_endpoint=NEOFS_ENDPOINT, wallet=wallet, cid=cid, bearer=bearer, xhdr=xhdr,
|
||||||
filters=[f'{filter_key} EQ {filter_val}' for filter_key, filter_val in filters.items()] if filters else None,
|
filters=[f'{filter_key} EQ {filter_val}' for filter_key, filter_val in filters.items()] if filters else None)
|
||||||
**options or {})
|
|
||||||
|
|
||||||
found_objects = re.findall(r'(\w{43,44})', output)
|
found_objects = re.findall(r'(\w{43,44})', output)
|
||||||
|
|
||||||
|
@ -213,8 +217,8 @@ def search_object(wallet: str, cid: str, bearer: str = "", filters: Optional[dic
|
||||||
|
|
||||||
@keyword('Head object')
|
@keyword('Head object')
|
||||||
def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "",
|
def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "",
|
||||||
options: Optional[dict] = None, endpoint: str = None, json_output: bool = True,
|
xhdr: Optional[dict] = None, endpoint: str = None, json_output: bool = True,
|
||||||
is_raw: bool = False, is_direct: bool = False, wallet_config: str = WALLET_CONFIG):
|
is_raw: bool = False, is_direct: bool = False, wallet_config: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
HEAD an Object.
|
HEAD an Object.
|
||||||
|
|
||||||
|
@ -223,7 +227,6 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "",
|
||||||
cid (str): ID of Container where we get the Object from
|
cid (str): ID of Container where we get the Object from
|
||||||
oid (str): ObjectID to HEAD
|
oid (str): ObjectID to HEAD
|
||||||
bearer_token (optional, str): path to Bearer Token file, appends to `--bearer` key
|
bearer_token (optional, str): path to Bearer Token file, appends to `--bearer` key
|
||||||
options (optional, str): any options which `neofs-cli object head` accepts
|
|
||||||
endpoint(optional, str): NeoFS endpoint to send request to
|
endpoint(optional, str): NeoFS endpoint to send request to
|
||||||
json_output(optional, bool): return reponse in JSON format or not; this flag
|
json_output(optional, bool): return reponse in JSON format or not; this flag
|
||||||
turns into `--json` key
|
turns into `--json` key
|
||||||
|
@ -232,6 +235,7 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "",
|
||||||
is_direct(optional, bool): send request directly to the node or not; this flag
|
is_direct(optional, bool): send request directly to the node or not; this flag
|
||||||
turns into `--ttl 1` key
|
turns into `--ttl 1` key
|
||||||
wallet_config(optional, str): path to the wallet config
|
wallet_config(optional, str): path to the wallet config
|
||||||
|
xhdr (optional, dict): Request X-Headers in form of Key=Value
|
||||||
Returns:
|
Returns:
|
||||||
depending on the `json_output` parameter value, the function returns
|
depending on the `json_output` parameter value, the function returns
|
||||||
(dict): HEAD response in JSON format
|
(dict): HEAD response in JSON format
|
||||||
|
@ -239,10 +243,11 @@ def head_object(wallet: str, cid: str, oid: str, bearer_token: str = "",
|
||||||
(str): HEAD response as a plain text
|
(str): HEAD response as a plain text
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
wallet_config = wallet_config or WALLET_CONFIG
|
||||||
cli = NeofsCli(config=wallet_config)
|
cli = NeofsCli(config=wallet_config)
|
||||||
output = cli.object.head(rpc_endpoint=endpoint or NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid,
|
output = cli.object.head(rpc_endpoint=endpoint or NEOFS_ENDPOINT, wallet=wallet, cid=cid, oid=oid,
|
||||||
bearer=bearer_token, json_mode=json_output, raw=is_raw,
|
bearer=bearer_token, json_mode=json_output, raw=is_raw,
|
||||||
ttl=1 if is_direct else None, **options or {})
|
ttl=1 if is_direct else None, xhdr=xhdr)
|
||||||
|
|
||||||
if not json_output:
|
if not json_output:
|
||||||
return output
|
return output
|
||||||
|
|
100
robot/resources/lib/python_keywords/object_access.py
Normal file
100
robot/resources/lib/python_keywords/object_access.py
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import allure
|
||||||
|
|
||||||
|
from grpc_responses import OBJECT_ACCESS_DENIED, error_matches_status
|
||||||
|
from python_keywords.neofs_verbs import (delete_object, get_object, get_range, get_range_hash, head_object, put_object,
|
||||||
|
search_object)
|
||||||
|
from python_keywords.utility_keywords import get_file_hash
|
||||||
|
|
||||||
|
OPERATION_ERROR_TYPE = RuntimeError
|
||||||
|
|
||||||
|
|
||||||
|
def can_get_object(wallet: str, cid: str, oid: str, file_name: str, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try get object from container'):
|
||||||
|
try:
|
||||||
|
got_file_path = get_object(wallet, cid, oid, bearer_token=bearer, wallet_config=wallet_config, xhdr=xhdr)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
assert get_file_hash(file_name) == get_file_hash(got_file_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_put_object(wallet: str, cid: str, file_name: str, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None, attributes: Optional[dict] = None,
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try put object to container'):
|
||||||
|
try:
|
||||||
|
put_object(wallet, file_name, cid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr,
|
||||||
|
attributes=attributes)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_delete_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try delete object from container'):
|
||||||
|
try:
|
||||||
|
delete_object(wallet, cid, oid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_get_head_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try get head of object'):
|
||||||
|
try:
|
||||||
|
head_object(wallet, cid, oid, bearer_token=bearer, wallet_config=wallet_config, xhdr=xhdr)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_get_range_of_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try get range of object'):
|
||||||
|
try:
|
||||||
|
get_range(wallet, cid, oid, bearer=bearer, range_cut='0:10', wallet_config=wallet_config,
|
||||||
|
xhdr=xhdr)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_get_range_hash_of_object(wallet: str, cid: str, oid: str, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try get range hash of object'):
|
||||||
|
try:
|
||||||
|
get_range_hash(wallet, cid, oid, bearer_token=bearer, range_cut='0:10', wallet_config=wallet_config,
|
||||||
|
xhdr=xhdr)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def can_search_object(wallet: str, cid: str, oid: Optional[str] = None, bearer: Optional[str] = None,
|
||||||
|
wallet_config: Optional[str] = None, xhdr: Optional[dict] = None
|
||||||
|
) -> bool:
|
||||||
|
with allure.step('Try search object in container'):
|
||||||
|
try:
|
||||||
|
oids = search_object(wallet, cid, bearer=bearer, wallet_config=wallet_config, xhdr=xhdr)
|
||||||
|
except OPERATION_ERROR_TYPE as err:
|
||||||
|
assert error_matches_status(err, OBJECT_ACCESS_DENIED), f'Expected {err} to match {OBJECT_ACCESS_DENIED}'
|
||||||
|
return False
|
||||||
|
if oid:
|
||||||
|
return oid in oids
|
||||||
|
return True
|
|
@ -11,11 +11,8 @@ ROBOT_AUTO_KEYWORDS = False
|
||||||
|
|
||||||
|
|
||||||
@keyword('Verify Head Tombstone')
|
@keyword('Verify Head Tombstone')
|
||||||
def verify_head_tombstone(wallet_path: str, cid: str, oid_ts: str, oid: str,
|
def verify_head_tombstone(wallet_path: str, cid: str, oid_ts: str, oid: str):
|
||||||
bearer: str = "", options: str = ""):
|
header = neofs_verbs.head_object(wallet_path, cid, oid_ts)
|
||||||
header = neofs_verbs.head_object(wallet_path, cid, oid_ts,
|
|
||||||
bearer_token=bearer,
|
|
||||||
options=options)
|
|
||||||
header = header['header']
|
header = header['header']
|
||||||
|
|
||||||
BuiltIn().should_be_equal(header["containerID"], cid,
|
BuiltIn().should_be_equal(header["containerID"], cid,
|
||||||
|
|
|
@ -27,7 +27,7 @@ GAS_HASH = '0xd2a4cff31913016155e38e474a2c06d08be276cf'
|
||||||
|
|
||||||
NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
|
NEOFS_CONTRACT = os.getenv("NEOFS_IR_CONTRACTS_NEOFS")
|
||||||
|
|
||||||
ASSETS_DIR = os.getenv("ASSETS_DIR", "TemporaryDir/")
|
ASSETS_DIR = os.getenv("ASSETS_DIR", "TemporaryDir")
|
||||||
DEVENV_PATH = os.getenv("DEVENV_PATH", "../neofs-dev-env")
|
DEVENV_PATH = os.getenv("DEVENV_PATH", "../neofs-dev-env")
|
||||||
CLI_CONFIGS_PATH = os.getenv("CLI_CONFIGS_PATH", f"{os.getcwd()}/neofs_cli_configs")
|
CLI_CONFIGS_PATH = os.getenv("CLI_CONFIGS_PATH", f"{os.getcwd()}/neofs_cli_configs")
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue