Skip to content

Commit 75e9b06

Browse files
bkioshnstevenj
andauthored
test(cat-gateway): Integration test for RBAC endpoint (#2453)
* test(cat-gateway): add integration test for rbac endpoint Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): refactor Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): fix spelling Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): fix spelling Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): fix spelling Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): update test data and fix rbac_chain logic Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): update test data Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): typo Signed-off-by: bkioshn <[email protected]> * test(cat-gateway): test all role for rbac auth token Signed-off-by: bkioshn <[email protected]> --------- Signed-off-by: bkioshn <[email protected]> Co-authored-by: Steven Johnson <[email protected]>
1 parent beb935d commit 75e9b06

File tree

7 files changed

+181
-68
lines changed

7 files changed

+181
-68
lines changed

catalyst-gateway/bin/src/service/common/types/cardano/catalyst_id.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! A Catalyst short identifier.
1+
//! A Catalyst identifier.
22
33
// cSpell:ignoreRegExp cardano/Fftx
44

@@ -16,22 +16,22 @@ use serde_json::Value;
1616
/// Catalyst Id String Format
1717
pub(crate) const FORMAT: &str = "catalyst_id";
1818

19-
/// Catalyst Id Pattern
20-
pub(crate) const PATTERN: &str = r".+\..+\/[A-Za-z0-9_-]{43}";
19+
/// Minimum format for Catalyst Id. (<`text`/`public_key_base64_url`>)
20+
pub(crate) const PATTERN: &str = r".+\/[A-Za-z0-9_-]{43}";
2121

2222
/// A schema.
2323
static SCHEMA: LazyLock<MetaSchema> = LazyLock::new(|| {
2424
let example = Some(CatalystId::example().0.to_string().into());
2525
MetaSchema {
26-
title: Some("Catalyst short ID".into()),
27-
description: Some("Catalyst short identifier in string format"),
26+
title: Some("Catalyst ID".into()),
27+
description: Some("Catalyst identifier in string format"),
2828
example,
2929
pattern: Some(PATTERN.into()),
3030
..MetaSchema::ANY
3131
}
3232
});
3333

34-
/// A Catalyst short identifier.
34+
/// A Catalyst identifier.
3535
#[derive(Debug, Clone, PartialEq, Hash)]
3636
pub(crate) struct CatalystId(CatalystIdInner);
3737

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pytest
2+
from utils.rbac_chain import ONLY_ROLE_0_REG_JSON, rbac_chain_factory, RoleID
3+
from api.v1.rbac import get
4+
5+
@pytest.mark.preprod_indexing
6+
def test_rbac_endpoints(rbac_chain_factory):
7+
8+
auth_token = rbac_chain_factory(RoleID.ROLE_0).auth_token()
9+
10+
# Registered stake address lookup
11+
stake_address = ONLY_ROLE_0_REG_JSON["0"][0]["stake_address"]
12+
resp = get(lookup=stake_address, token=auth_token)
13+
assert(resp.status_code == 200), f"Expected registered stake address: {resp.status_code} - {resp.text}"
14+
15+
# Not registered stake address lookup
16+
# Cardano test data CIP0019
17+
# <https://github.com/cardano-foundation/CIPs/blob/master/CIP-0019/README.md>
18+
# cspell:disable-next-line
19+
stake_address = "stake_test17rphkx6acpnf78fuvxn0mkew3l0fd058hzquvz7w36x4gtcljw6kf"
20+
resp = get(lookup=stake_address, token=auth_token)
21+
assert(resp.status_code == 404), f"Expected not registered stake address: {resp.status_code} - {resp.text}"
22+
23+
# Wrong format stake address lookup
24+
stake_address = "stake_invalid_address"
25+
resp = get(lookup=stake_address, token=auth_token)
26+
assert(resp.status_code == 412), f"Expected wrong format stake address: {resp.status_code} - {resp.text}"
27+
28+
# Registered Catalyst ID lookup
29+
cat_id = rbac_chain_factory(RoleID.ROLE_0).cat_id_for_role(RoleID.ROLE_0)[0]
30+
resp = get(lookup=cat_id, token=auth_token)
31+
assert(resp.status_code == 200), f"Expected registered cat id: {resp.status_code} - {resp.text}"
32+
33+
cat_id = rbac_chain_factory(RoleID.ROLE_0).short_cat_id()
34+
resp = get(lookup=cat_id, token=auth_token)
35+
assert(resp.status_code == 200), f"Expected registered short cat id: {resp.status_code} - {resp.text}"
36+
37+
# Not registered Catalyst ID lookup
38+
# <https://input-output-hk.github.io/catalyst-libs/architecture/08_concepts/rbac_id_uri/catalyst-id-uri/#test-vectors>
39+
# cspell:disable-next-line
40+
cat_id = "preprod.cardano/FftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE"
41+
resp = get(lookup=cat_id, token=auth_token)
42+
assert(resp.status_code == 404), f"Expected not registered cat id: {resp.status_code} - {resp.text}"
43+
44+
# Wrong format Catalyst ID lookup
45+
# cspell:disable-next-line
46+
cat_id = "invalidFftxFnOrj2qmTuB2oZG2v0YEWJfKvQ9Gg8AgNAhDsKE"
47+
resp = get(lookup="cat_id", token=auth_token)
48+
assert(resp.status_code == 412), f"Expected wrong format cat id: {resp.status_code} - {resp.text}"

catalyst-gateway/tests/api_tests/integration/test_rbac_auth_token.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def test_invalid_rbac_auth_token(rbac_chain_factory):
4242
resp = get(lookup=None, token=token)
4343
assert(resp.status_code == 401), f"Expected must not contain username: {resp.status_code} - {resp.text}"
4444

45-
# Catalyst ID not in a correct format -> 401
45+
# Catalyst ID not in a correct format (Should be in short form with catid. -> 401
4646
token = rbac_chain_factory(RoleID.ROLE_0).auth_token(is_uri=True)
4747
resp = get(lookup=None, token=token)
4848
assert(resp.status_code == 401), f"Expected invalid Catalyst ID format: {resp.status_code} - {resp.text}"
@@ -92,18 +92,24 @@ def test_invalid_rbac_auth_token(rbac_chain_factory):
9292

9393
@pytest.mark.preprod_indexing
9494
def test_valid_rbac_auth_token(rbac_chain_factory):
95-
token = rbac_chain_factory(RoleID.ROLE_0).auth_token()
96-
resp = get(lookup=None, token=token)
97-
assert(resp.status_code == 200), f"Expected valid token that already registered: {resp.status_code} - {resp.text}"
98-
99-
# X-API-Key does not match as expected value, but still pass because the nonce is valid
100-
token = rbac_chain_factory(RoleID.ROLE_0).auth_token()
101-
resp = get(lookup=None, token=token, extra_headers={"X-API-Key": "test"})
102-
assert(resp.status_code == 200), f"Expected valid token that already registered when X-API-Key does not match {resp.status_code} - {resp.text}"
95+
def test_valid_rbac_auth_token_inner(role: RoleID):
96+
token = rbac_chain_factory(role).auth_token()
10397

104-
# X-API-Key does match the expected, nonce validation is skipped
105-
nonce = int((datetime.now(timezone.utc) - timedelta(days=30)).timestamp())
106-
token = rbac_chain_factory(RoleID.ROLE_0).auth_token(nonce=nonce)
107-
resp = get(lookup=None, token=token, extra_headers={"X-API-Key": "123"})
108-
assert(resp.status_code == 200), f"Expected valid token that already registered when X-API-Key does match, nonce validation is skipped: {resp.status_code} - {resp.text}"
98+
resp = get(lookup=None, token=token)
99+
assert(resp.status_code == 200), f"Expected valid token that already registered: {resp.status_code} - {resp.text}"
100+
101+
# X-API-Key does not match as expected value, but still pass because the nonce is valid
102+
token = rbac_chain_factory(role).auth_token()
103+
resp = get(lookup=None, token=token, extra_headers={"X-API-Key": "test"})
104+
assert(resp.status_code == 200), f"Expected valid token that already registered when X-API-Key does not match {resp.status_code} - {resp.text}"
105+
106+
# X-API-Key does match the expected, nonce validation is skipped
107+
nonce = int((datetime.now(timezone.utc) - timedelta(days=30)).timestamp())
108+
token = rbac_chain_factory(role).auth_token(nonce=nonce)
109+
resp = get(lookup=None, token=token, extra_headers={"X-API-Key": "123"})
110+
assert(resp.status_code == 200), f"Expected valid token that already registered when X-API-Key does match, nonce validation is skipped: {resp.status_code} - {resp.text}"
111+
112+
113+
for role in RoleID:
114+
test_valid_rbac_auth_token_inner(role)
109115

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
{
2-
"0": {
3-
"tx_id": "e4d3c4957cfb8a77bfb7be3e320af57b6d5b2f6f36a8564b6c7921cb9b6ffd39",
4-
"sk": "600f662a02d72db06c21201e2e7810042419fa769f30b5be46f92e29e7e59341f03aa7eb2c89083569968ec621f1b0c8cdc392f4d895651cc29b168522066fbe65dcadfa4e257915afe0972ef01a805adabb725daf4b7eaa997f4a7181ebd512",
5-
"pk": "d0a195a645d57b9fa1d517c86f4f0c9dcfb13194f3497e555673c3687be1aea465dcadfa4e257915afe0972ef01a805adabb725daf4b7eaa997f4a7181ebd512",
6-
"rotation": 0
7-
}
8-
}
2+
"0":[
3+
{
4+
"tx_id":"e4d3c4957cfb8a77bfb7be3e320af57b6d5b2f6f36a8564b6c7921cb9b6ffd39",
5+
"sk":"600f662a02d72db06c21201e2e7810042419fa769f30b5be46f92e29e7e59341f03aa7eb2c89083569968ec621f1b0c8cdc392f4d895651cc29b168522066fbe65dcadfa4e257915afe0972ef01a805adabb725daf4b7eaa997f4a7181ebd512",
6+
"pk":"d0a195a645d57b9fa1d517c86f4f0c9dcfb13194f3497e555673c3687be1aea465dcadfa4e257915afe0972ef01a805adabb725daf4b7eaa997f4a7181ebd512",
7+
"stake_address":"stake_test1upkg8vzueeczrg443wkjkqhj2xk3hcz9hfe02vz48ayeqtsqeyvpf",
8+
"rotation":0
9+
}
10+
]
11+
}
Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
{
2-
"0": {
3-
"tx_id": "5fd71fb559d3ebf16bb0b8b30028a1d0fbbb3a983dbbe2e92eb87f851c6d205c",
4-
"sk": "a8f84dd9576f9b5224da38146df1dd4c80c6aa767cb71540bd86294f62cced568ecdf07352e0b48e1ae66370352e56aba4113461ec08e13b2fed10ecc056c65fd2a76829a3b53e66af79bb0cb1efade075f0ae65eaaabb75f5106bbeef59b866",
5-
"pk": "42149f1a6f1da43fcf066a473e12515b5b6216fedfc52b87bee091456981d9c6d2a76829a3b53e66af79bb0cb1efade075f0ae65eaaabb75f5106bbeef59b866",
6-
"rotation": 0
7-
},
8-
"3": {
9-
"tx_id": "5fd71fb559d3ebf16bb0b8b30028a1d0fbbb3a983dbbe2e92eb87f851c6d205c",
10-
"sk": "284b1a3b46f00d99193aefe80df3797cfcd88d1058203da1b3c695315bcced5665a53e06fb76f5a5d3a5122cbaa4eba94b81d3f4c7db7ace8bf6c7340a34bfc7995bb20c59d086d671cfac83177857761a4f4badd65fee96f3b8e351ebe217b8",
11-
"pk": "ac36a7c87a77de72c3404cca36029e63cdd5cc6e7b2538a52908eee983011b51995bb20c59d086d671cfac83177857761a4f4badd65fee96f3b8e351ebe217b8",
12-
"rotation": 1
13-
}
14-
}
2+
"0":[
3+
{
4+
"tx_id":"5fd71fb559d3ebf16bb0b8b30028a1d0fbbb3a983dbbe2e92eb87f851c6d205c",
5+
"sk":"a8f84dd9576f9b5224da38146df1dd4c80c6aa767cb71540bd86294f62cced568ecdf07352e0b48e1ae66370352e56aba4113461ec08e13b2fed10ecc056c65fd2a76829a3b53e66af79bb0cb1efade075f0ae65eaaabb75f5106bbeef59b866",
6+
"pk":"42149f1a6f1da43fcf066a473e12515b5b6216fedfc52b87bee091456981d9c6d2a76829a3b53e66af79bb0cb1efade075f0ae65eaaabb75f5106bbeef59b866",
7+
"stake_address":"stake_test1urhsxq8996yy7varz0kgr0ev2e9wltvkcr0kuzd4wwzsdzqvt0e8t",
8+
"rotation":0
9+
}
10+
],
11+
"3":[
12+
{
13+
"tx_id":"5fd71fb559d3ebf16bb0b8b30028a1d0fbbb3a983dbbe2e92eb87f851c6d205c",
14+
"sk":"284b1a3b46f00d99193aefe80df3797cfcd88d1058203da1b3c695315bcced5665a53e06fb76f5a5d3a5122cbaa4eba94b81d3f4c7db7ace8bf6c7340a34bfc7995bb20c59d086d671cfac83177857761a4f4badd65fee96f3b8e351ebe217b8",
15+
"pk":"ac36a7c87a77de72c3404cca36029e63cdd5cc6e7b2538a52908eee983011b51995bb20c59d086d671cfac83177857761a4f4badd65fee96f3b8e351ebe217b8",
16+
"rotation":0
17+
},
18+
{
19+
"tx_id":"95f781a3db75af41d1dde5a997b9f9ab3e20035882d4a2ccafdc81cfda6f52a2",
20+
"sk":"284b1a3b46f00d99193aefe80df3797cfcd88d1058203da1b3c695315bcced5665a53e06fb76f5a5d3a5122cbaa4eba94b81d3f4c7db7ace8bf6c7340a34bfc7995bb20c59d086d671cfac83177857761a4f4badd65fee96f3b8e351ebe217b8",
21+
"pk":"ac36a7c87a77de72c3404cca36029e63cdd5cc6e7b2538a52908eee983011b51995bb20c59d086d671cfac83177857761a4f4badd65fee96f3b8e351ebe217b8",
22+
"rotation":1
23+
}
24+
]
25+
}

catalyst-gateway/tests/api_tests/utils/rbac_chain.py

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,43 @@ def __init__(self, keys_map: dict, network: str, subnet: str):
2727
self.subnet = subnet
2828

2929
def auth_token(self, cid: str = None, sig: str = None, username: str = None, is_uri: bool = False, nonce: str = None) -> str:
30-
role_0_keys = self.keys_map[f"{RoleID.ROLE_0}"]
30+
role_0_arr = self.keys_map[f"{RoleID.ROLE_0}"]
3131
return generate_rbac_auth_token(
32-
self.network, self.subnet, role_0_keys["pk"], role_0_keys["sk"], cid, sig, username, is_uri, nonce
32+
self.network,
33+
self.subnet,
34+
role_0_arr[0]["pk"],
35+
role_0_arr[-1]["sk"],
36+
cid,
37+
sig,
38+
username,
39+
is_uri,
40+
nonce
3341
)
3442

3543
# returns a role's catalyst id, with the provided role secret key
3644
def cat_id_for_role(self, role_id: RoleID) -> (str, str):
37-
role_data = self.keys_map[f"{role_id}"]
38-
role_0_pk = self.keys_map[f"{RoleID.ROLE_0}"]["pk"]
45+
role_data_arr = self.keys_map[f"{role_id}"]
46+
role_0_arr= self.keys_map[f"{RoleID.ROLE_0}"]
3947
return (
4048
generate_cat_id(
41-
self.network,
42-
self.subnet,
43-
role_id,
44-
role_0_pk,
45-
role_data["rotation"],
46-
True,
49+
network=self.network,
50+
subnet=self.subnet,
51+
role_id=role_id,
52+
pk_hex=role_0_arr[0]["pk"],
53+
rotation=role_data_arr[-1]["rotation"],
54+
is_uri=True,
4755
),
48-
role_data["sk"],
56+
role_data_arr[-1]["sk"],
57+
)
58+
59+
def short_cat_id(self) -> str:
60+
return generate_cat_id(
61+
network=self.network,
62+
subnet=self.subnet,
63+
pk_hex=self.keys_map[f"{RoleID.ROLE_0}"][0]["pk"],
64+
is_uri=False,
4965
)
5066

51-
5267
@pytest.fixture
5368
def rbac_chain_factory():
5469
def __rbac_chain_factory(role_id: RoleID, network: str = "cardano", subnet: str = "preprod") -> RBACChain:
@@ -62,26 +77,50 @@ def __rbac_chain_factory(role_id: RoleID, network: str = "cardano", subnet: str
6277

6378
return __rbac_chain_factory
6479

65-
80+
# Default is set to URI format
81+
# Optional field = subnet, role id, rotation, username, nonce
6682
def generate_cat_id(
67-
network: str, subnet: str, role_id: RoleID, pk_hex: str, rotation: int, is_uri: bool, nonce: str = None
68-
):
83+
network: str,
84+
pk_hex: str,
85+
is_uri: bool = True,
86+
subnet: str = None,
87+
role_id: str = None,
88+
rotation: str = None,
89+
username: str = None,
90+
nonce: str = None,
91+
) -> str:
6992
pk = bytes.fromhex(pk_hex)[:32]
93+
role0_pk_b64 = base64_url(pk)
94+
95+
# If nonce is set to none, use current timestamp
96+
# If set to empty string, use empty string (no nonce)
7097
if nonce is None:
7198
nonce = f"{int(datetime.now(timezone.utc).timestamp())}"
72-
subnet = f"{subnet}." if subnet else ""
73-
role0_pk_b64 = base64_url(pk)
7499

75-
if role_id == RoleID.ROLE_0 and rotation == 0:
76-
res = f":{nonce}@{subnet}{network}/{role0_pk_b64}"
77-
else:
78-
res = f":{nonce}@{subnet}{network}/{role0_pk_b64}/{role_id}/{rotation}"
100+
# Authority part
101+
authority = ""
102+
if username:
103+
authority += f"{username}"
104+
if nonce:
105+
authority += f":{nonce}"
106+
authority += "@"
79107

108+
if subnet:
109+
authority += f"{subnet}.{network}"
110+
else:
111+
authority += network
112+
113+
# Path
114+
path = f"{role0_pk_b64}"
115+
if role_id:
116+
path += f"/{role_id}"
117+
if rotation:
118+
path += f"/{rotation}"
119+
80120
if is_uri:
81-
res = f"id.catalyst://{res}"
82-
83-
return res
84-
121+
return f"id.catalyst://{authority}/{path}"
122+
else:
123+
return f"{authority}/{path}"
85124

86125
def generate_rbac_auth_token(
87126
network: str,
@@ -92,7 +131,7 @@ def generate_rbac_auth_token(
92131
sig: str = None,
93132
username: str = None,
94133
is_uri: bool = False,
95-
nonce: int = None,
134+
nonce: str = None,
96135
) -> str:
97136
pk = bytes.fromhex(pk_hex)[:32]
98137
sk = bytes.fromhex(sk_hex)[:64]
@@ -103,7 +142,12 @@ def generate_rbac_auth_token(
103142

104143
token_prefix = f"catid.{username}" if username else "catid."
105144
if cid is None:
106-
cat_id = f"{token_prefix}{generate_cat_id(network, subnet, RoleID.ROLE_0, pk_hex, 0, is_uri, nonce)}."
145+
cat_id = f"{token_prefix}{generate_cat_id(
146+
network=network,
147+
subnet=subnet,
148+
pk_hex=pk_hex,
149+
is_uri=is_uri,
150+
nonce=nonce)}."
107151
else:
108152
cat_id = f"{token_prefix}:{cid}."
109153

cspell.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,8 @@
182182
"catalyst_voices/utilities/poc_local_storage/**/**",
183183
"**/*.svg",
184184
"catalyst_voices/packages/libs/catalyst_key_derivation/lib/src/rust/**",
185-
"catalyst_voices/packages/internal/catalyst_voices_driver/lib/src/browser_extensions/**"
185+
"catalyst_voices/packages/internal/catalyst_voices_driver/lib/src/browser_extensions/**",
186+
"catalyst-gateway/tests/api_tests/test_data/**",
186187
],
187188
"enableFiletypes": [
188189
"earthfile",

0 commit comments

Comments
 (0)