Add static session token container tests

Signed-off-by: Vladimir Avdeev <v.avdeev@yadro.com>
This commit is contained in:
Vladimir Avdeev 2022-11-30 11:26:38 +03:00 committed by Vladimir Avdeev
parent 455cafa08a
commit 9b0ac8579b
8 changed files with 444 additions and 200 deletions

View file

@ -1,7 +1,5 @@
import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
logger = logging.getLogger("NeoLogger")
@dataclass @dataclass
@ -12,16 +10,16 @@ class ObjectRef:
@dataclass @dataclass
class LockObjectInfo(ObjectRef): class LockObjectInfo(ObjectRef):
lifetime: int = None lifetime: Optional[int] = None
expire_at: int = None expire_at: Optional[int] = None
@dataclass @dataclass
class StorageObjectInfo(ObjectRef): class StorageObjectInfo(ObjectRef):
size: str = None size: Optional[int] = None
wallet_file_path: str = None wallet_file_path: Optional[str] = None
file_path: str = None file_path: Optional[str] = None
file_hash: str = None file_hash: Optional[str] = None
attributes: list[dict[str, str]] = None attributes: Optional[list[dict[str, str]]] = None
tombstone: str = None tombstone: Optional[str] = None
locks: list[LockObjectInfo] = None locks: Optional[list[LockObjectInfo]] = None

View file

@ -4,14 +4,14 @@ import logging
import os import os
import uuid import uuid
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from enum import Enum
from typing import Any, Optional
import allure import allure
import json_transformers import json_transformers
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 json_transformers import encode_for_json from json_transformers import encode_for_json
from neo3 import wallet
from neofs_testlib.cli import NeofsCli from neofs_testlib.cli import NeofsCli
from neofs_testlib.shell import Shell from neofs_testlib.shell import Shell
from storage_object_info import StorageObjectInfo from storage_object_info import StorageObjectInfo
@ -19,16 +19,6 @@ from wallet import WalletFile
logger = logging.getLogger("NeoLogger") logger = logging.getLogger("NeoLogger")
PUT_VERB = "PUT"
DELETE_VERB = "DELETE"
LOCK_VERB = "LOCK"
GET_VERB = "GET"
RANGEHASH_VERB = "RANGEHASH"
RANGE_VERB = "RANGE"
HEAD_VERB = "HEAD"
SEARCH_VERB = "SEARCH"
UNRELATED_KEY = "unrelated key in the session" UNRELATED_KEY = "unrelated key in the session"
UNRELATED_OBJECT = "unrelated object in the session" UNRELATED_OBJECT = "unrelated object in the session"
UNRELATED_CONTAINER = "unrelated container in the session" UNRELATED_CONTAINER = "unrelated container in the session"
@ -36,6 +26,22 @@ WRONG_VERB = "wrong verb of the session"
INVALID_SIGNATURE = "invalid signature of the session data" INVALID_SIGNATURE = "invalid signature of the session data"
class ObjectVerb(Enum):
PUT = "PUT"
DELETE = "DELETE"
GET = "GET"
RANGEHASH = "RANGEHASH"
RANGE = "RANGE"
HEAD = "HEAD"
SEARCH = "SEARCH"
class ContainerVerb(Enum):
CREATE = "PUT"
DELETE = "DELETE"
SETEACL = "SETEACL"
@dataclass @dataclass
class Lifetime: class Lifetime:
exp: int = 100000000 exp: int = 100000000
@ -44,76 +50,20 @@ class Lifetime:
@allure.step("Generate Session Token") @allure.step("Generate Session Token")
def generate_session_token(owner: str, session_wallet: str, cid: str = "") -> str: def generate_session_token(
"""
This function generates session token for ContainerSessionContext
and writes it to the file. It is able to prepare session token file
for a specific container (<cid>) or for every container (adds
"wildcard" field).
Args:
owner(str): wallet address of container owner
session_wallet(str): the path to wallet to which we grant the
access via session token
cid(optional, str): container ID of the container; if absent,
we assume the session token is generated for any
container
Returns:
(str): the path to the generated session token file
"""
file_path = os.path.join(os.getcwd(), ASSETS_DIR, str(uuid.uuid4()))
session_wlt_content = ""
with open(session_wallet) as fout:
session_wlt_content = json.load(fout)
session_wlt = wallet.Wallet.from_json(session_wlt_content, password="")
pub_key_64 = base64.b64encode(bytes.fromhex(str(session_wlt.accounts[0].public_key))).decode(
"utf-8"
)
session_token = {
"body": {
"id": f"{base64.b64encode(uuid.uuid4().bytes).decode('utf-8')}",
"ownerID": {"value": f"{json_transformers.encode_for_json(owner)}"},
"lifetime": {"exp": "100000000", "nbf": "0", "iat": "0"},
"sessionKey": f"{pub_key_64}",
"container": {
"verb": "PUT",
"wildcard": cid != "",
**({"containerID": {"value": f"{encode_for_json(cid)}"}} if cid != "" else {}),
},
}
}
logger.info(f"Got this Session Token: {session_token}")
with open(file_path, "w", encoding="utf-8") as session_token_file:
json.dump(session_token, session_token_file, ensure_ascii=False, indent=4)
return file_path
@allure.step("Generate Session Token For Object")
def generate_object_session_token(
owner_wallet: WalletFile, owner_wallet: WalletFile,
session_wallet: WalletFile, session_wallet: WalletFile,
oids: list[str], session: dict[str, dict[str, Any]],
cid: str,
verb: str,
tokens_dir: str, tokens_dir: str,
lifetime: Optional[Lifetime] = None, lifetime: Optional[Lifetime] = None,
) -> str: ) -> str:
""" """
This function generates session token for ObjectSessionContext This function generates session token and writes it to the file.
and writes it to the file. It is able to prepare session token file
for a specific container (<cid>) or for every container (adds
"wildcard" field).
Args: Args:
owner_wallet: wallet of container owner owner_wallet: wallet of container owner
session_wallet: wallet to which we grant the session_wallet: wallet to which we grant the access via session token
access via session token session: Contains allowed operation with parameters
cid: container ID of the container tokens_dir: Dir for token
oids: list of objectIDs to put into session
verb: verb to grant access to;
Valid verbs are: GET, RANGE, RANGEHASH, HEAD, SEARCH.
lifetime: lifetime options for session lifetime: lifetime options for session
Returns: Returns:
The path to the generated session token file The path to the generated session token file
@ -123,7 +73,7 @@ def generate_object_session_token(
pub_key_64 = get_wallet_public_key(session_wallet.path, session_wallet.password, "base64") pub_key_64 = get_wallet_public_key(session_wallet.path, session_wallet.password, "base64")
lifetime = lifetime if lifetime else Lifetime() lifetime = lifetime or Lifetime()
session_token = { session_token = {
"body": { "body": {
@ -137,15 +87,9 @@ def generate_object_session_token(
"iat": f"{lifetime.iat}", "iat": f"{lifetime.iat}",
}, },
"sessionKey": pub_key_64, "sessionKey": pub_key_64,
"object": {
"verb": verb,
"target": {
"container": {"value": encode_for_json(cid)},
"objects": [{"value": encode_for_json(oid)} for oid in oids],
},
},
} }
} }
session_token["body"].update(session)
logger.info(f"Got this Session Token: {session_token}") logger.info(f"Got this Session Token: {session_token}")
with open(file_path, "w", encoding="utf-8") as session_token_file: with open(file_path, "w", encoding="utf-8") as session_token_file:
@ -154,12 +98,117 @@ def generate_object_session_token(
return file_path return file_path
@allure.step("Generate Session Token For Container")
def generate_container_session_token(
owner_wallet: WalletFile,
session_wallet: WalletFile,
verb: ContainerVerb,
tokens_dir: str,
lifetime: Optional[Lifetime] = None,
cid: Optional[str] = None,
) -> str:
"""
This function generates session token for ContainerSessionContext
and writes it to the file. It is able to prepare session token file
for a specific container (<cid>) or for every container (adds
"wildcard" field).
Args:
owner_wallet: wallet of container owner.
session_wallet: wallet to which we grant the access via session token.
verb: verb to grant access to.
lifetime: lifetime options for session.
cid: container ID of the container
Returns:
The path to the generated session token file
"""
session = {
"container": {
"verb": verb.value,
"wildcard": cid is not None,
**({"containerID": {"value": f"{encode_for_json(cid)}"}} if cid is not None else {}),
},
}
return generate_session_token(
owner_wallet=owner_wallet,
session_wallet=session_wallet,
session=session,
tokens_dir=tokens_dir,
lifetime=lifetime,
)
@allure.step("Generate Session Token For Object")
def generate_object_session_token(
owner_wallet: WalletFile,
session_wallet: WalletFile,
oids: list[str],
cid: str,
verb: ObjectVerb,
tokens_dir: str,
lifetime: Optional[Lifetime] = None,
) -> str:
"""
This function generates session token for ObjectSessionContext
and writes it to the file.
Args:
owner_wallet: wallet of container owner
session_wallet: wallet to which we grant the access via session token
cid: container ID of the container
oids: list of objectIDs to put into session
verb: verb to grant access to; Valid verbs are: ObjectVerb.
lifetime: lifetime options for session
Returns:
The path to the generated session token file
"""
session = {
"object": {
"verb": verb.value,
"target": {
"container": {"value": encode_for_json(cid)},
"objects": [{"value": encode_for_json(oid)} for oid in oids],
},
},
}
return generate_session_token(
owner_wallet=owner_wallet,
session_wallet=session_wallet,
session=session,
tokens_dir=tokens_dir,
lifetime=lifetime,
)
@allure.step("Get signed token for object session")
def get_container_signed_token(
owner_wallet: WalletFile,
user_wallet: WalletFile,
verb: ContainerVerb,
shell: Shell,
tokens_dir: str,
lifetime: Optional[Lifetime] = None,
) -> str:
"""
Returns signed token file path for static object session
"""
session_token_file = generate_container_session_token(
owner_wallet=owner_wallet,
session_wallet=user_wallet,
verb=verb,
tokens_dir=tokens_dir,
lifetime=lifetime,
)
return sign_session_token(shell, session_token_file, owner_wallet)
@allure.step("Get signed token for object session") @allure.step("Get signed token for object session")
def get_object_signed_token( def get_object_signed_token(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
cid: str,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
verb: str, verb: ObjectVerb,
shell: Shell, shell: Shell,
tokens_dir: str, tokens_dir: str,
lifetime: Optional[Lifetime] = None, lifetime: Optional[Lifetime] = None,
@ -169,15 +218,15 @@ def get_object_signed_token(
""" """
storage_object_ids = [storage_object.oid for storage_object in storage_objects] storage_object_ids = [storage_object.oid for storage_object in storage_objects]
session_token_file = generate_object_session_token( session_token_file = generate_object_session_token(
owner_wallet, owner_wallet=owner_wallet,
user_wallet, session_wallet=user_wallet,
storage_object_ids, oids=storage_object_ids,
owner_wallet.containers[0], cid=cid,
verb, verb=verb,
tokens_dir, tokens_dir=tokens_dir,
lifetime=lifetime, lifetime=lifetime,
) )
return sign_session_token(shell, session_token_file, owner_wallet.path) return sign_session_token(shell, session_token_file, owner_wallet)
@allure.step("Create Session Token") @allure.step("Create Session Token")
@ -212,7 +261,7 @@ def create_session_token(
@allure.step("Sign Session Token") @allure.step("Sign Session Token")
def sign_session_token(shell: Shell, session_token_file: str, wlt: str) -> str: def sign_session_token(shell: Shell, session_token_file: str, wlt: WalletFile) -> str:
""" """
This function signs the session token by the given wallet. This function signs the session token by the given wallet.
@ -227,6 +276,6 @@ def sign_session_token(shell: Shell, session_token_file: str, wlt: str) -> str:
signed_token_file = os.path.join(os.getcwd(), ASSETS_DIR, str(uuid.uuid4())) signed_token_file = os.path.join(os.getcwd(), ASSETS_DIR, str(uuid.uuid4()))
neofscli = NeofsCli(shell=shell, neofs_cli_exec_path=NEOFS_CLI_EXEC, config_file=WALLET_CONFIG) neofscli = NeofsCli(shell=shell, neofs_cli_exec_path=NEOFS_CLI_EXEC, config_file=WALLET_CONFIG)
neofscli.util.sign_session_token( neofscli.util.sign_session_token(
wallet=wlt, from_file=session_token_file, to_file=signed_token_file wallet=wlt.path, from_file=session_token_file, to_file=signed_token_file
) )
return signed_token_file return signed_token_file

View file

@ -0,0 +1,26 @@
import pytest
from wallet import WalletFactory, WalletFile
@pytest.fixture(scope="module")
def owner_wallet(wallet_factory: WalletFactory) -> WalletFile:
"""
Returns wallet which owns containers and objects
"""
return wallet_factory.create_wallet()
@pytest.fixture(scope="module")
def user_wallet(wallet_factory: WalletFactory) -> WalletFile:
"""
Returns wallet which will use objects from owner via static session
"""
return wallet_factory.create_wallet()
@pytest.fixture(scope="module")
def stranger_wallet(wallet_factory: WalletFactory) -> WalletFile:
"""
Returns stranger wallet which should fail to obtain data
"""
return wallet_factory.create_wallet()

View file

@ -6,7 +6,6 @@ from common import COMPLEX_OBJ_SIZE, SIMPLE_OBJ_SIZE
from epoch import ensure_fresh_epoch, tick_epoch from epoch import ensure_fresh_epoch, tick_epoch
from file_helper import generate_file from file_helper import generate_file
from grpc_responses import MALFORMED_REQUEST, OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND from grpc_responses import MALFORMED_REQUEST, OBJECT_ACCESS_DENIED, OBJECT_NOT_FOUND
from neofs_testlib.hosting import Hosting
from neofs_testlib.shell import Shell from neofs_testlib.shell import Shell
from pytest import FixtureRequest from pytest import FixtureRequest
from python_keywords.container import create_container from python_keywords.container import create_container
@ -20,23 +19,17 @@ from python_keywords.neofs_verbs import (
put_object, put_object,
search_object, search_object,
) )
from wallet import WalletFactory, WalletFile from wallet import WalletFile
from helpers.storage_object_info import StorageObjectInfo from helpers.storage_object_info import StorageObjectInfo
from steps.session_token import ( from steps.session_token import (
DELETE_VERB,
GET_VERB,
HEAD_VERB,
INVALID_SIGNATURE, INVALID_SIGNATURE,
PUT_VERB,
RANGE_VERB,
RANGEHASH_VERB,
SEARCH_VERB,
UNRELATED_CONTAINER, UNRELATED_CONTAINER,
UNRELATED_KEY, UNRELATED_KEY,
UNRELATED_OBJECT, UNRELATED_OBJECT,
WRONG_VERB, WRONG_VERB,
Lifetime, Lifetime,
ObjectVerb,
generate_object_session_token, generate_object_session_token,
get_object_signed_token, get_object_signed_token,
sign_session_token, sign_session_token,
@ -48,6 +41,14 @@ logger = logging.getLogger("NeoLogger")
RANGE_OFFSET_FOR_COMPLEX_OBJECT = 200 RANGE_OFFSET_FOR_COMPLEX_OBJECT = 200
@pytest.fixture(scope="module")
def storage_containers(owner_wallet: WalletFile, client_shell: Shell) -> list[str]:
# Separate containers for complex/simple objects to avoid side-effects
cid = create_container(owner_wallet.path, shell=client_shell)
other_cid = create_container(owner_wallet.path, shell=client_shell)
yield [cid, other_cid]
@pytest.fixture( @pytest.fixture(
params=[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE], params=[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE],
ids=["simple object", "complex object"], ids=["simple object", "complex object"],
@ -55,27 +56,26 @@ RANGE_OFFSET_FOR_COMPLEX_OBJECT = 200
scope="module", scope="module",
) )
def storage_objects( def storage_objects(
owner_wallet: WalletFile, client_shell: Shell, request: FixtureRequest owner_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
request: FixtureRequest,
) -> list[StorageObjectInfo]: ) -> list[StorageObjectInfo]:
file_path = generate_file(request.param) file_path = generate_file(request.param)
storage_objects = [] storage_objects = []
# Separate containers for complex/simple objects to avoid side-effects
cid = create_container(owner_wallet.path, shell=client_shell)
other_cid = create_container(owner_wallet.path, shell=client_shell)
owner_wallet.containers = [cid, other_cid]
with allure.step("Put objects"): with allure.step("Put objects"):
# upload couple objects # upload couple objects
for _ in range(3): for _ in range(3):
storage_object_id = put_object( storage_object_id = put_object(
wallet=owner_wallet.path, wallet=owner_wallet.path,
path=file_path, path=file_path,
cid=cid, cid=storage_containers[0],
shell=client_shell, shell=client_shell,
) )
storage_object = StorageObjectInfo(cid, storage_object_id) storage_object = StorageObjectInfo(storage_containers[0], storage_object_id)
storage_object.size = request.param storage_object.size = request.param
storage_object.wallet_file_path = owner_wallet.path storage_object.wallet_file_path = owner_wallet.path
storage_object.file_path = file_path storage_object.file_path = file_path
@ -102,56 +102,38 @@ def get_ranges(storage_object: StorageObjectInfo, shell: Shell) -> list[str]:
return [ return [
"0:10", "0:10",
f"{object_size-10}:10", f"{object_size-10}:10",
f"{max_object_size - RANGE_OFFSET_FOR_COMPLEX_OBJECT}:{RANGE_OFFSET_FOR_COMPLEX_OBJECT * 2}", f"{max_object_size - RANGE_OFFSET_FOR_COMPLEX_OBJECT}:"
f"{RANGE_OFFSET_FOR_COMPLEX_OBJECT * 2}",
] ]
else: else:
return ["0:10", f"{object_size-10}:10"] return ["0:10", f"{object_size-10}:10"]
@pytest.fixture(scope="module")
def owner_wallet(wallet_factory: WalletFactory) -> WalletFile:
"""
Returns wallet which owns containers and objects
"""
return wallet_factory.create_wallet()
@pytest.fixture(scope="module")
def user_wallet(wallet_factory: WalletFactory) -> WalletFile:
"""
Returns wallet which will use objects from owner via static session
"""
return wallet_factory.create_wallet()
@pytest.fixture(scope="module")
def stranger_wallet(wallet_factory: WalletFactory) -> WalletFile:
"""
Returns stranger wallet which should fail to obtain data
"""
return wallet_factory.create_wallet()
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def static_sessions( def static_sessions(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
client_shell: Shell, client_shell: Shell,
prepare_tmp_dir: str, prepare_tmp_dir: str,
) -> dict[str, str]: ) -> dict[ObjectVerb, str]:
""" """
Returns dict with static session token file paths for all verbs with default lifetime with valid container and first two objects Returns dict with static session token file paths for all verbs with default lifetime with
valid container and first two objects
""" """
verbs = [GET_VERB, RANGEHASH_VERB, RANGE_VERB, HEAD_VERB, SEARCH_VERB, DELETE_VERB, PUT_VERB] return {
sessions = {} verb: get_object_signed_token(
owner_wallet,
for verb in verbs: user_wallet,
sessions[verb] = get_object_signed_token( storage_containers[0],
owner_wallet, user_wallet, storage_objects[0:2], verb, client_shell, prepare_tmp_dir storage_objects[0:2],
verb,
client_shell,
prepare_tmp_dir,
) )
for verb in ObjectVerb
return sessions }
@allure.title("Validate static session with read operations") @allure.title("Validate static session with read operations")
@ -159,17 +141,17 @@ def static_sessions(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"method_under_test,verb", "method_under_test,verb",
[ [
(head_object, HEAD_VERB), (head_object, ObjectVerb.HEAD),
(get_object, GET_VERB), (get_object, ObjectVerb.GET),
], ],
) )
def test_static_session_read( def test_static_session_read(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
method_under_test, method_under_test,
verb: str, verb: ObjectVerb,
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -193,15 +175,15 @@ def test_static_session_read(
@pytest.mark.static_session @pytest.mark.static_session
@pytest.mark.parametrize( @pytest.mark.parametrize(
"method_under_test,verb", "method_under_test,verb",
[(get_range, RANGE_VERB), (get_range_hash, RANGEHASH_VERB)], [(get_range, ObjectVerb.RANGE), (get_range_hash, ObjectVerb.RANGEHASH)],
) )
def test_static_session_range( def test_static_session_range(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
method_under_test, method_under_test,
verb: str, verb: ObjectVerb,
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -233,7 +215,7 @@ def test_static_session_search(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -244,7 +226,7 @@ def test_static_session_search(
cid = storage_objects[0].cid cid = storage_objects[0].cid
expected_object_ids = [storage_object.oid for storage_object in storage_objects[0:2]] expected_object_ids = [storage_object.oid for storage_object in storage_objects[0:2]]
actual_object_ids = search_object( actual_object_ids = search_object(
user_wallet.path, cid, client_shell, session=static_sessions[SEARCH_VERB], root=True user_wallet.path, cid, client_shell, session=static_sessions[ObjectVerb.SEARCH], root=True
) )
assert expected_object_ids == actual_object_ids assert expected_object_ids == actual_object_ids
@ -255,7 +237,7 @@ def test_static_session_unrelated_object(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -270,7 +252,7 @@ def test_static_session_unrelated_object(
storage_objects[2].cid, storage_objects[2].cid,
storage_objects[2].oid, storage_objects[2].oid,
client_shell, client_shell,
session=static_sessions[HEAD_VERB], session=static_sessions[ObjectVerb.HEAD],
) )
@ -280,7 +262,7 @@ def test_static_session_head_unrelated_user(
stranger_wallet: WalletFile, stranger_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -297,7 +279,7 @@ def test_static_session_head_unrelated_user(
storage_object.cid, storage_object.cid,
storage_object.oid, storage_object.oid,
client_shell, client_shell,
session=static_sessions[HEAD_VERB], session=static_sessions[ObjectVerb.HEAD],
) )
@ -307,7 +289,7 @@ def test_static_session_head_wrong_verb(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -324,7 +306,7 @@ def test_static_session_head_wrong_verb(
storage_object.cid, storage_object.cid,
storage_object.oid, storage_object.oid,
client_shell, client_shell,
session=static_sessions[HEAD_VERB], session=static_sessions[ObjectVerb.HEAD],
) )
@ -334,8 +316,9 @@ def test_static_session_unrelated_container(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -349,10 +332,10 @@ def test_static_session_unrelated_container(
with pytest.raises(Exception, match=UNRELATED_CONTAINER): with pytest.raises(Exception, match=UNRELATED_CONTAINER):
get_object( get_object(
user_wallet.path, user_wallet.path,
owner_wallet.containers[1], storage_containers[1],
storage_object.oid, storage_object.oid,
client_shell, client_shell,
session=static_sessions[GET_VERB], session=static_sessions[ObjectVerb.GET],
) )
@ -363,6 +346,7 @@ def test_static_session_signed_by_other(
user_wallet: WalletFile, user_wallet: WalletFile,
stranger_wallet: WalletFile, stranger_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -379,11 +363,11 @@ def test_static_session_signed_by_other(
owner_wallet, owner_wallet,
user_wallet, user_wallet,
[storage_object.oid], [storage_object.oid],
owner_wallet.containers[0], storage_containers[0],
HEAD_VERB, ObjectVerb.HEAD,
prepare_tmp_dir, prepare_tmp_dir,
) )
signed_token_file = sign_session_token(client_shell, session_token_file, stranger_wallet.path) signed_token_file = sign_session_token(client_shell, session_token_file, stranger_wallet)
with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED): with pytest.raises(Exception, match=OBJECT_ACCESS_DENIED):
head_object( head_object(
user_wallet.path, user_wallet.path,
@ -400,6 +384,7 @@ def test_static_session_signed_for_other_container(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -411,12 +396,12 @@ def test_static_session_signed_for_other_container(
f"Validate static session which signed for another container for {request.node.callspec.id}" f"Validate static session which signed for another container for {request.node.callspec.id}"
) )
storage_object = storage_objects[0] storage_object = storage_objects[0]
container = owner_wallet.containers[1] container = storage_containers[1]
session_token_file = generate_object_session_token( session_token_file = generate_object_session_token(
owner_wallet, user_wallet, [storage_object.oid], container, HEAD_VERB, prepare_tmp_dir owner_wallet, user_wallet, [storage_object.oid], container, ObjectVerb.HEAD, prepare_tmp_dir
) )
signed_token_file = sign_session_token(client_shell, session_token_file, owner_wallet.path) signed_token_file = sign_session_token(client_shell, session_token_file, owner_wallet)
with pytest.raises(Exception, match=OBJECT_NOT_FOUND): with pytest.raises(Exception, match=OBJECT_NOT_FOUND):
head_object( head_object(
user_wallet.path, container, storage_object.oid, client_shell, session=signed_token_file user_wallet.path, container, storage_object.oid, client_shell, session=signed_token_file
@ -429,6 +414,7 @@ def test_static_session_without_sign(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -445,8 +431,8 @@ def test_static_session_without_sign(
owner_wallet, owner_wallet,
user_wallet, user_wallet,
[storage_object.oid], [storage_object.oid],
owner_wallet.containers[0], storage_containers[0],
HEAD_VERB, ObjectVerb.HEAD,
prepare_tmp_dir, prepare_tmp_dir,
) )
with pytest.raises(Exception, match=INVALID_SIGNATURE): with pytest.raises(Exception, match=INVALID_SIGNATURE):
@ -465,6 +451,7 @@ def test_static_session_expiration_at_next(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -477,15 +464,16 @@ def test_static_session_expiration_at_next(
) )
epoch = ensure_fresh_epoch(client_shell) epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0] container = storage_containers[0]
object_id = storage_objects[0].oid object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 1, epoch, epoch) expiration = Lifetime(epoch + 1, epoch, epoch)
token_expire_at_next_epoch = get_object_signed_token( token_expire_at_next_epoch = get_object_signed_token(
owner_wallet, owner_wallet,
user_wallet, user_wallet,
container,
storage_objects, storage_objects,
HEAD_VERB, ObjectVerb.HEAD,
client_shell, client_shell,
prepare_tmp_dir, prepare_tmp_dir,
expiration, expiration,
@ -509,6 +497,7 @@ def test_static_session_start_at_next(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -517,19 +506,21 @@ def test_static_session_start_at_next(
Validate static session which is valid starting from next epoch Validate static session which is valid starting from next epoch
""" """
allure.dynamic.title( allure.dynamic.title(
f"Validate static session which is valid starting from next epoch for {request.node.callspec.id}" "Validate static session which is valid starting from next epoch "
f"for {request.node.callspec.id}"
) )
epoch = ensure_fresh_epoch(client_shell) epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0] container = storage_containers[0]
object_id = storage_objects[0].oid object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 2, epoch + 1, epoch) expiration = Lifetime(epoch + 2, epoch + 1, epoch)
token_start_at_next_epoch = get_object_signed_token( token_start_at_next_epoch = get_object_signed_token(
owner_wallet, owner_wallet,
user_wallet, user_wallet,
container,
storage_objects, storage_objects,
HEAD_VERB, ObjectVerb.HEAD,
client_shell, client_shell,
prepare_tmp_dir, prepare_tmp_dir,
expiration, expiration,
@ -558,6 +549,7 @@ def test_static_session_already_expired(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -570,15 +562,16 @@ def test_static_session_already_expired(
) )
epoch = ensure_fresh_epoch(client_shell) epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0] container = storage_containers[0]
object_id = storage_objects[0].oid object_id = storage_objects[0].oid
expiration = Lifetime(epoch - 1, epoch - 2, epoch - 2) expiration = Lifetime(epoch - 1, epoch - 2, epoch - 2)
token_already_expired = get_object_signed_token( token_already_expired = get_object_signed_token(
owner_wallet, owner_wallet,
user_wallet, user_wallet,
container,
storage_objects, storage_objects,
HEAD_VERB, ObjectVerb.HEAD,
client_shell, client_shell,
prepare_tmp_dir, prepare_tmp_dir,
expiration, expiration,
@ -594,8 +587,9 @@ def test_static_session_already_expired(
def test_static_session_delete_verb( def test_static_session_delete_verb(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -611,7 +605,7 @@ def test_static_session_delete_verb(
storage_object.cid, storage_object.cid,
storage_object.oid, storage_object.oid,
client_shell, client_shell,
session=static_sessions[DELETE_VERB], session=static_sessions[ObjectVerb.DELETE],
) )
@ -620,7 +614,7 @@ def test_static_session_put_verb(
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
static_sessions: list[str], static_sessions: dict[ObjectVerb, str],
request: FixtureRequest, request: FixtureRequest,
): ):
""" """
@ -636,7 +630,7 @@ def test_static_session_put_verb(
storage_object.file_path, storage_object.file_path,
storage_object.cid, storage_object.cid,
client_shell, client_shell,
session=static_sessions[PUT_VERB], session=static_sessions[ObjectVerb.PUT],
) )
@ -646,6 +640,7 @@ def test_static_session_invalid_issued_epoch(
owner_wallet: WalletFile, owner_wallet: WalletFile,
user_wallet: WalletFile, user_wallet: WalletFile,
client_shell: Shell, client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo], storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str, prepare_tmp_dir: str,
request: FixtureRequest, request: FixtureRequest,
@ -658,15 +653,16 @@ def test_static_session_invalid_issued_epoch(
) )
epoch = ensure_fresh_epoch(client_shell) epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0] container = storage_containers[0]
object_id = storage_objects[0].oid object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 10, 0, epoch + 1) expiration = Lifetime(epoch + 10, 0, epoch + 1)
token_invalid_issue_time = get_object_signed_token( token_invalid_issue_time = get_object_signed_token(
owner_wallet, owner_wallet,
user_wallet, user_wallet,
container,
storage_objects, storage_objects,
HEAD_VERB, ObjectVerb.HEAD,
client_shell, client_shell,
prepare_tmp_dir, prepare_tmp_dir,
expiration, expiration,

View file

@ -0,0 +1,163 @@
import allure
import pytest
from file_helper import generate_file
from neofs_testlib.shell import Shell
from python_keywords.acl import (
EACLAccess,
EACLOperation,
EACLRole,
EACLRule,
create_eacl,
set_eacl,
wait_for_cache_expired,
)
from python_keywords.container import (
create_container,
delete_container,
get_container,
list_containers,
)
from python_keywords.object_access import can_put_object
from wallet import WalletFile
from wellknown_acl import PUBLIC_ACL
from steps.session_token import ContainerVerb, get_container_signed_token
class TestSessionTokenContainer:
@pytest.fixture(scope="module")
def static_sessions(
self,
owner_wallet: WalletFile,
user_wallet: WalletFile,
client_shell: Shell,
prepare_tmp_dir: str,
) -> dict[ContainerVerb, str]:
"""
Returns dict with static session token file paths for all verbs with default lifetime
"""
return {
verb: get_container_signed_token(
owner_wallet, user_wallet, verb, client_shell, prepare_tmp_dir
)
for verb in ContainerVerb
}
def test_static_session_token_container_create(
self,
client_shell: Shell,
owner_wallet: WalletFile,
user_wallet: WalletFile,
static_sessions: dict[ContainerVerb, str],
):
"""
Validate static session with create operation
"""
with allure.step("Create container with static session token"):
cid = create_container(
user_wallet.path,
session_token=static_sessions[ContainerVerb.CREATE],
shell=client_shell,
wait_for_creation=False,
)
container_info: dict[str, str] = get_container(owner_wallet.path, cid, shell=client_shell)
assert container_info["ownerID"] == owner_wallet.get_address()
assert cid not in list_containers(user_wallet.path, shell=client_shell)
assert cid in list_containers(owner_wallet.path, shell=client_shell)
@pytest.mark.skip("Failed with timeout")
def test_static_session_token_container_create_with_other_verb(
self,
client_shell: Shell,
owner_wallet: WalletFile,
user_wallet: WalletFile,
static_sessions: dict[ContainerVerb, str],
):
"""
Validate static session without create operation
"""
with allure.step("Try create container with static session token without PUT rule"):
for verb in [verb for verb in ContainerVerb if verb != ContainerVerb.CREATE]:
with pytest.raises(Exception):
create_container(
user_wallet.path,
session_token=static_sessions[verb],
shell=client_shell,
wait_for_creation=False,
)
@pytest.mark.skip("Failed with timeout")
def test_static_session_token_container_create_with_other_wallet(
self,
client_shell: Shell,
owner_wallet: WalletFile,
stranger_wallet: WalletFile,
static_sessions: dict[ContainerVerb, str],
):
"""
Validate static session with create operation for other wallet
"""
with allure.step("Try create container with static session token without PUT rule"):
with pytest.raises(Exception):
create_container(
stranger_wallet.path,
session_token=static_sessions[ContainerVerb.CREATE],
shell=client_shell,
wait_for_creation=False,
)
def test_static_session_token_container_delete(
self,
client_shell: Shell,
owner_wallet: WalletFile,
user_wallet: WalletFile,
static_sessions: dict[ContainerVerb, str],
):
"""
Validate static session with delete operation
"""
with allure.step("Create container"):
cid = create_container(owner_wallet.path, shell=client_shell, wait_for_creation=False)
with allure.step("Delete container with static session token"):
delete_container(
wallet=user_wallet.path,
cid=cid,
session_token=static_sessions[ContainerVerb.DELETE],
shell=client_shell,
)
assert cid not in list_containers(owner_wallet.path, shell=client_shell)
def test_static_session_token_container_set_eacl(
self,
client_shell: Shell,
owner_wallet: WalletFile,
user_wallet: WalletFile,
stranger_wallet: WalletFile,
static_sessions: dict[ContainerVerb, str],
):
"""
Validate static session with set eacl operation
"""
with allure.step("Create container"):
cid = create_container(owner_wallet.path, basic_acl=PUBLIC_ACL, shell=client_shell)
file_path = generate_file()
assert can_put_object(stranger_wallet.path, cid, file_path, client_shell)
with allure.step(f"Deny all operations for other via eACL"):
eacl_deny = [
EACLRule(access=EACLAccess.DENY, role=EACLRole.OTHERS, operation=op)
for op in EACLOperation
]
set_eacl(
user_wallet.path,
cid,
create_eacl(cid, eacl_deny, shell=client_shell),
shell=client_shell,
session_token=static_sessions[ContainerVerb.SETEACL],
)
wait_for_cache_expired()
assert not can_put_object(stranger_wallet.path, cid, file_path, client_shell)

View file

@ -130,7 +130,13 @@ def get_eacl(wallet_path: str, cid: str, shell: Shell) -> Optional[str]:
@allure.title("Set extended ACL") @allure.title("Set extended ACL")
def set_eacl(wallet_path: str, cid: str, eacl_table_path: str, shell: Shell) -> None: def set_eacl(
wallet_path: str,
cid: str,
eacl_table_path: str,
shell: Shell,
session_token: Optional[str] = None,
) -> None:
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG) cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
cli.container.set_eacl( cli.container.set_eacl(
wallet=wallet_path, wallet=wallet_path,
@ -138,6 +144,7 @@ def set_eacl(wallet_path: str, cid: str, eacl_table_path: str, shell: Shell) ->
cid=cid, cid=cid,
table=eacl_table_path, table=eacl_table_path,
await_mode=True, await_mode=True,
session=session_token,
) )

View file

@ -165,7 +165,9 @@ def get_container(
@allure.step("Delete Container") @allure.step("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, shell: Shell, force: bool = False) -> None: def delete_container(
wallet: str, cid: str, shell: Shell, force: bool = False, session_token: Optional[str] = None
) -> None:
""" """
A wrapper for `neofs-cli container delete` call. A wrapper for `neofs-cli container delete` call.
Args: Args:
@ -173,11 +175,14 @@ def delete_container(wallet: str, cid: str, shell: Shell, force: bool = False) -
cid (str): ID of the container to delete cid (str): ID of the container to delete
shell: executor for cli command shell: executor for cli command
force (bool): do not check whether container contains locks and remove immediately force (bool): do not check whether container contains locks and remove immediately
session_token: a path to session token file
This function doesn't return anything. This function doesn't return anything.
""" """
cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG) cli = NeofsCli(shell, NEOFS_CLI_EXEC, WALLET_CONFIG)
cli.container.delete(wallet=wallet, cid=cid, rpc_endpoint=NEOFS_ENDPOINT, force=force) cli.container.delete(
wallet=wallet, cid=cid, rpc_endpoint=NEOFS_ENDPOINT, force=force, session=session_token
)
def _parse_cid(output: str) -> str: def _parse_cid(output: str) -> str:

View file

@ -4,7 +4,7 @@ import os
import random import random
import re import re
import uuid import uuid
from typing import Optional from typing import Any, Optional
import allure import allure
import json_transformers import json_transformers
@ -406,7 +406,7 @@ def get_netmap_netinfo(
address: Optional[str] = None, address: Optional[str] = None,
ttl: Optional[int] = None, ttl: Optional[int] = None,
xhdr: Optional[dict] = None, xhdr: Optional[dict] = None,
) -> dict[str, object]: ) -> dict[str, Any]:
""" """
Get netmap netinfo output from node Get netmap netinfo output from node