Add static session token container tests

Signed-off-by: Vladimir Avdeev <v.avdeev@yadro.com>
support/v0.36
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
logger = logging.getLogger("NeoLogger")
from typing import Optional
@dataclass
@ -12,16 +10,16 @@ class ObjectRef:
@dataclass
class LockObjectInfo(ObjectRef):
lifetime: int = None
expire_at: int = None
lifetime: Optional[int] = None
expire_at: Optional[int] = None
@dataclass
class StorageObjectInfo(ObjectRef):
size: str = None
wallet_file_path: str = None
file_path: str = None
file_hash: str = None
attributes: list[dict[str, str]] = None
tombstone: str = None
locks: list[LockObjectInfo] = None
size: Optional[int] = None
wallet_file_path: Optional[str] = None
file_path: Optional[str] = None
file_hash: Optional[str] = None
attributes: Optional[list[dict[str, str]]] = None
tombstone: Optional[str] = None
locks: Optional[list[LockObjectInfo]] = None

View File

@ -4,14 +4,14 @@ import logging
import os
import uuid
from dataclasses import dataclass
from typing import Optional
from enum import Enum
from typing import Any, Optional
import allure
import json_transformers
from common import ASSETS_DIR, NEOFS_CLI_EXEC, NEOFS_ENDPOINT, WALLET_CONFIG
from data_formatters import get_wallet_public_key
from json_transformers import encode_for_json
from neo3 import wallet
from neofs_testlib.cli import NeofsCli
from neofs_testlib.shell import Shell
from storage_object_info import StorageObjectInfo
@ -19,16 +19,6 @@ from wallet import WalletFile
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_OBJECT = "unrelated object 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"
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
class Lifetime:
exp: int = 100000000
@ -44,76 +50,20 @@ class Lifetime:
@allure.step("Generate Session Token")
def generate_session_token(owner: str, session_wallet: str, cid: str = "") -> 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(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(
def generate_session_token(
owner_wallet: WalletFile,
session_wallet: WalletFile,
oids: list[str],
cid: str,
verb: str,
session: dict[str, dict[str, Any]],
tokens_dir: str,
lifetime: Optional[Lifetime] = None,
) -> str:
"""
This function generates session token for ObjectSessionContext
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).
This function generates session token 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: GET, RANGE, RANGEHASH, HEAD, SEARCH.
session_wallet: wallet to which we grant the access via session token
session: Contains allowed operation with parameters
tokens_dir: Dir for token
lifetime: lifetime options for session
Returns:
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")
lifetime = lifetime if lifetime else Lifetime()
lifetime = lifetime or Lifetime()
session_token = {
"body": {
@ -137,15 +87,9 @@ def generate_object_session_token(
"iat": f"{lifetime.iat}",
},
"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}")
with open(file_path, "w", encoding="utf-8") as session_token_file:
@ -154,12 +98,117 @@ def generate_object_session_token(
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")
def get_object_signed_token(
owner_wallet: WalletFile,
user_wallet: WalletFile,
cid: str,
storage_objects: list[StorageObjectInfo],
verb: str,
verb: ObjectVerb,
shell: Shell,
tokens_dir: str,
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]
session_token_file = generate_object_session_token(
owner_wallet,
user_wallet,
storage_object_ids,
owner_wallet.containers[0],
verb,
tokens_dir,
owner_wallet=owner_wallet,
session_wallet=user_wallet,
oids=storage_object_ids,
cid=cid,
verb=verb,
tokens_dir=tokens_dir,
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")
@ -212,7 +261,7 @@ def create_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.
@ -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()))
neofscli = NeofsCli(shell=shell, neofs_cli_exec_path=NEOFS_CLI_EXEC, config_file=WALLET_CONFIG)
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

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 file_helper import generate_file
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 pytest import FixtureRequest
from python_keywords.container import create_container
@ -20,23 +19,17 @@ from python_keywords.neofs_verbs import (
put_object,
search_object,
)
from wallet import WalletFactory, WalletFile
from wallet import WalletFile
from helpers.storage_object_info import StorageObjectInfo
from steps.session_token import (
DELETE_VERB,
GET_VERB,
HEAD_VERB,
INVALID_SIGNATURE,
PUT_VERB,
RANGE_VERB,
RANGEHASH_VERB,
SEARCH_VERB,
UNRELATED_CONTAINER,
UNRELATED_KEY,
UNRELATED_OBJECT,
WRONG_VERB,
Lifetime,
ObjectVerb,
generate_object_session_token,
get_object_signed_token,
sign_session_token,
@ -48,6 +41,14 @@ logger = logging.getLogger("NeoLogger")
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(
params=[SIMPLE_OBJ_SIZE, COMPLEX_OBJ_SIZE],
ids=["simple object", "complex object"],
@ -55,27 +56,26 @@ RANGE_OFFSET_FOR_COMPLEX_OBJECT = 200
scope="module",
)
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]:
file_path = generate_file(request.param)
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"):
# upload couple objects
for _ in range(3):
storage_object_id = put_object(
wallet=owner_wallet.path,
path=file_path,
cid=cid,
cid=storage_containers[0],
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.wallet_file_path = owner_wallet.path
storage_object.file_path = file_path
@ -102,56 +102,38 @@ def get_ranges(storage_object: StorageObjectInfo, shell: Shell) -> list[str]:
return [
"0: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:
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")
def static_sessions(
owner_wallet: WalletFile,
user_wallet: WalletFile,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
client_shell: Shell,
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]
sessions = {}
for verb in verbs:
sessions[verb] = get_object_signed_token(
owner_wallet, user_wallet, storage_objects[0:2], verb, client_shell, prepare_tmp_dir
return {
verb: get_object_signed_token(
owner_wallet,
user_wallet,
storage_containers[0],
storage_objects[0:2],
verb,
client_shell,
prepare_tmp_dir,
)
return sessions
for verb in ObjectVerb
}
@allure.title("Validate static session with read operations")
@ -159,17 +141,17 @@ def static_sessions(
@pytest.mark.parametrize(
"method_under_test,verb",
[
(head_object, HEAD_VERB),
(get_object, GET_VERB),
(head_object, ObjectVerb.HEAD),
(get_object, ObjectVerb.GET),
],
)
def test_static_session_read(
user_wallet: WalletFile,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
method_under_test,
verb: str,
verb: ObjectVerb,
request: FixtureRequest,
):
"""
@ -193,15 +175,15 @@ def test_static_session_read(
@pytest.mark.static_session
@pytest.mark.parametrize(
"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(
user_wallet: WalletFile,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
method_under_test,
verb: str,
verb: ObjectVerb,
request: FixtureRequest,
):
"""
@ -233,7 +215,7 @@ def test_static_session_search(
user_wallet: WalletFile,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -244,7 +226,7 @@ def test_static_session_search(
cid = storage_objects[0].cid
expected_object_ids = [storage_object.oid for storage_object in storage_objects[0:2]]
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
@ -255,7 +237,7 @@ def test_static_session_unrelated_object(
user_wallet: WalletFile,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -270,7 +252,7 @@ def test_static_session_unrelated_object(
storage_objects[2].cid,
storage_objects[2].oid,
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,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -297,7 +279,7 @@ def test_static_session_head_unrelated_user(
storage_object.cid,
storage_object.oid,
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,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -324,7 +306,7 @@ def test_static_session_head_wrong_verb(
storage_object.cid,
storage_object.oid,
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,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -349,10 +332,10 @@ def test_static_session_unrelated_container(
with pytest.raises(Exception, match=UNRELATED_CONTAINER):
get_object(
user_wallet.path,
owner_wallet.containers[1],
storage_containers[1],
storage_object.oid,
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,
stranger_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
request: FixtureRequest,
@ -379,11 +363,11 @@ def test_static_session_signed_by_other(
owner_wallet,
user_wallet,
[storage_object.oid],
owner_wallet.containers[0],
HEAD_VERB,
storage_containers[0],
ObjectVerb.HEAD,
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):
head_object(
user_wallet.path,
@ -400,6 +384,7 @@ def test_static_session_signed_for_other_container(
owner_wallet: WalletFile,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
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}"
)
storage_object = storage_objects[0]
container = owner_wallet.containers[1]
container = storage_containers[1]
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):
head_object(
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,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
request: FixtureRequest,
@ -445,8 +431,8 @@ def test_static_session_without_sign(
owner_wallet,
user_wallet,
[storage_object.oid],
owner_wallet.containers[0],
HEAD_VERB,
storage_containers[0],
ObjectVerb.HEAD,
prepare_tmp_dir,
)
with pytest.raises(Exception, match=INVALID_SIGNATURE):
@ -465,6 +451,7 @@ def test_static_session_expiration_at_next(
owner_wallet: WalletFile,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
request: FixtureRequest,
@ -477,15 +464,16 @@ def test_static_session_expiration_at_next(
)
epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0]
container = storage_containers[0]
object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 1, epoch, epoch)
token_expire_at_next_epoch = get_object_signed_token(
owner_wallet,
user_wallet,
container,
storage_objects,
HEAD_VERB,
ObjectVerb.HEAD,
client_shell,
prepare_tmp_dir,
expiration,
@ -509,6 +497,7 @@ def test_static_session_start_at_next(
owner_wallet: WalletFile,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
request: FixtureRequest,
@ -517,19 +506,21 @@ def test_static_session_start_at_next(
Validate static session which is valid starting from next epoch
"""
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)
container = owner_wallet.containers[0]
container = storage_containers[0]
object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 2, epoch + 1, epoch)
token_start_at_next_epoch = get_object_signed_token(
owner_wallet,
user_wallet,
container,
storage_objects,
HEAD_VERB,
ObjectVerb.HEAD,
client_shell,
prepare_tmp_dir,
expiration,
@ -558,6 +549,7 @@ def test_static_session_already_expired(
owner_wallet: WalletFile,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
request: FixtureRequest,
@ -570,15 +562,16 @@ def test_static_session_already_expired(
)
epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0]
container = storage_containers[0]
object_id = storage_objects[0].oid
expiration = Lifetime(epoch - 1, epoch - 2, epoch - 2)
token_already_expired = get_object_signed_token(
owner_wallet,
user_wallet,
container,
storage_objects,
HEAD_VERB,
ObjectVerb.HEAD,
client_shell,
prepare_tmp_dir,
expiration,
@ -594,8 +587,9 @@ def test_static_session_already_expired(
def test_static_session_delete_verb(
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -611,7 +605,7 @@ def test_static_session_delete_verb(
storage_object.cid,
storage_object.oid,
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,
client_shell: Shell,
storage_objects: list[StorageObjectInfo],
static_sessions: list[str],
static_sessions: dict[ObjectVerb, str],
request: FixtureRequest,
):
"""
@ -636,7 +630,7 @@ def test_static_session_put_verb(
storage_object.file_path,
storage_object.cid,
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,
user_wallet: WalletFile,
client_shell: Shell,
storage_containers: list[str],
storage_objects: list[StorageObjectInfo],
prepare_tmp_dir: str,
request: FixtureRequest,
@ -658,15 +653,16 @@ def test_static_session_invalid_issued_epoch(
)
epoch = ensure_fresh_epoch(client_shell)
container = owner_wallet.containers[0]
container = storage_containers[0]
object_id = storage_objects[0].oid
expiration = Lifetime(epoch + 10, 0, epoch + 1)
token_invalid_issue_time = get_object_signed_token(
owner_wallet,
user_wallet,
container,
storage_objects,
HEAD_VERB,
ObjectVerb.HEAD,
client_shell,
prepare_tmp_dir,
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")
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.container.set_eacl(
wallet=wallet_path,
@ -138,6 +144,7 @@ def set_eacl(wallet_path: str, cid: str, eacl_table_path: str, shell: Shell) ->
cid=cid,
table=eacl_table_path,
await_mode=True,
session=session_token,
)

View File

@ -165,7 +165,9 @@ def get_container(
@allure.step("Delete Container")
# TODO: make the error message about a non-found container more user-friendly
# 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.
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
shell: executor for cli command
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.
"""
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:

View File

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