Skip to content

Commit

Permalink
Merge pull request hyperledger#1410 from ashcherbakov/transaction-end…
Browse files Browse the repository at this point in the history
…orser

INDY-2199: do not require endorser if multi-signed by oner/monitor roles only.
  • Loading branch information
ashcherbakov authored Aug 12, 2019
2 parents 6115e90 + acc17bc commit 5499deb
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 46 deletions.
12 changes: 11 additions & 1 deletion indy_common/authorize/authorizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,17 @@ def _check_endorser_field_presence(self, request):
if author_role in self.NO_NEED_FOR_ENDORSER_ROLES:
return True, ""

# 4. Check that Endorser field is present
# 4. Endorser field is not required when multi-signed by non-privileged roles only
has_privileged_sig = False
for idr, _ in request.signatures.items():
role = self._get_role(idr)
if role in self.NO_NEED_FOR_ENDORSER_ROLES:
has_privileged_sig = True
break
if not has_privileged_sig:
return True, ""

# 5. Check that Endorser field is present
if request.endorser is None:
return False, "'Endorser' field must be explicitly set for the endorsed transaction"

Expand Down
4 changes: 2 additions & 2 deletions indy_common/test/auth/metadata/helper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import NamedTuple, List
from typing import NamedTuple, List, Optional

from indy_common.authorize.auth_actions import AbstractAuthAction, AuthActionAdd
from indy_common.authorize.auth_constraints import AuthConstraint
Expand All @@ -11,7 +11,7 @@
PLUGIN_FIELD = "new_field"

Action = NamedTuple('Action',
[("author", str), ("endorser", str), ("sigs", dict),
[("author", str), ("endorser", Optional[str]), ("sigs", dict),
("is_owner", bool), ("amount", int), ("extra_sigs", bool)])


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,9 @@ def test_plugin_or_rule_one_amount_diff_roles_owner_no_endorser(write_auth_req_v
AuthConstraint(role=IDENTITY_OWNER, sig_count=1, need_to_be_owner=True,
metadata={PLUGIN_FIELD: 1}),
]),
valid_actions=[Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: 1},
is_owner=True, amount=1, extra_sigs=False)],
valid_actions=[Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: s},
is_owner=True, amount=1, extra_sigs=False)
for s in range(1, MAX_SIG_COUNT + 1)],
author=IDENTITY_OWNER, endorser=None,
all_signatures=signatures, is_owner=is_owner, amount=amount,
write_auth_req_validator=write_auth_req_validator,
Expand Down Expand Up @@ -210,8 +211,9 @@ def test_plugin_or_rule_one_amount_all_roles_owner_no_endorser(write_auth_req_va
off_ledger_signature=off_ledger_signature,
metadata={PLUGIN_FIELD: 3}),
]),
valid_actions=[Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: 1},
is_owner=True, amount=3, extra_sigs=False)],
valid_actions=[Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: s},
is_owner=True, amount=3, extra_sigs=False)
for s in range(1, MAX_SIG_COUNT + 1)],
author=IDENTITY_OWNER, endorser=None,
all_signatures=signatures, is_owner=is_owner, amount=amount,
write_auth_req_validator=write_auth_req_validator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ def test_plugin_simple_rule_1_sig_owner_no_endorser(write_auth_req_validator, wr
auth_constraint=AuthConstraint(role=IDENTITY_OWNER, sig_count=1, need_to_be_owner=True,
metadata={PLUGIN_FIELD: 2}),
valid_actions=[
Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: 1},
Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: s},
is_owner=True, amount=2, extra_sigs=False)
for s in range(1, MAX_SIG_COUNT + 1)
],
author=IDENTITY_OWNER, endorser=None,
all_signatures=signatures, is_owner=is_owner, amount=amount,
Expand Down Expand Up @@ -118,8 +119,9 @@ def test_plugin_simple_rule_1_sig_all_roles_owner_no_endorser(write_auth_req_val
off_ledger_signature=off_ledger_signature,
metadata={PLUGIN_FIELD: 2}),
valid_actions=[
Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: 1},
Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: s},
is_owner=True, amount=2, extra_sigs=False)
for s in range(1, MAX_SIG_COUNT + 1)
],
author=IDENTITY_OWNER, endorser=None,
all_signatures=signatures, is_owner=is_owner, amount=amount,
Expand Down Expand Up @@ -257,13 +259,12 @@ def test_plugin_simple_rule_0_sig_owner_no_endorser(write_auth_req_validator, wr
off_ledger_signature=off_ledger_signature,
metadata={PLUGIN_FIELD: 2}),
valid_actions=[Action(author=IDENTITY_OWNER, endorser=None, sigs={},
is_owner=True, amount=2, extra_sigs=False),
Action(author=IDENTITY_OWNER, endorser=None, sigs={},
is_owner=False, amount=2, extra_sigs=False),
Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: 1},
is_owner=True, amount=2, extra_sigs=False),
Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: 1},
is_owner=False, amount=2, extra_sigs=False)],
is_owner=owner, amount=2, extra_sigs=False)
for owner in [True, False]] +
[Action(author=IDENTITY_OWNER, endorser=None, sigs={IDENTITY_OWNER: s},
is_owner=owner, amount=2, extra_sigs=False)
for owner in [True, False]
for s in range(1, MAX_SIG_COUNT + 1)],
author=IDENTITY_OWNER, endorser=None,
all_signatures=signatures, is_owner=is_owner, amount=amount,
write_auth_req_validator=write_auth_req_validator,
Expand Down
87 changes: 57 additions & 30 deletions indy_common/test/auth/test_endorser_authorizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,58 @@
AUTH_CONSTR = AuthConstraint(role=ENDORSER, sig_count=1)


def get_idr_by_role(role):
def get_idr_by_role_author(role):
if role == TRUSTEE:
return "trustee_did"
return "author_did_trustee"
if role == STEWARD:
return "steward_did"
return "author_did_steward"
if role == ENDORSER:
return "endorser_did"
return "author_did_endorser"
if role == NETWORK_MONITOR:
return "network_monitor_did"
return "no_role_did"
return "author_did_network_monitor"
return "author_did_no_role"


def get_idr_by_role_endorser(role):
if role == TRUSTEE:
return "endorser_did_trustee"
if role == STEWARD:
return "endorser_did_steward"
if role == ENDORSER:
return "endorser_did_endorser"
if role == NETWORK_MONITOR:
return "endorser_did_network_monitor"
return "endorser_did_no_role"


@pytest.fixture(scope='module')
def idr_cache():
cache = IdrCache("Cache",
KeyValueStorageInMemory())
cache.set("author_no_role", 1, int(time.time()), role=IDENTITY_OWNER,
# authors
cache.set("author_did_no_role", 1, int(time.time()), role=IDENTITY_OWNER,
verkey="SomeVerkey", isCommitted=False)
cache.set("trustee_did", 1, int(time.time()), role=TRUSTEE,
cache.set("author_did_trustee", 1, int(time.time()), role=TRUSTEE,
verkey="SomeVerkey1", isCommitted=False)
cache.set("steward_did", 1, int(time.time()), role=STEWARD,
cache.set("author_did_steward", 1, int(time.time()), role=STEWARD,
verkey="SomeVerkey2", isCommitted=False)
cache.set("endorser_did", 1, int(time.time()), role=ENDORSER,
cache.set("author_did_endorser", 1, int(time.time()), role=ENDORSER,
verkey="SomeVerkey3", isCommitted=False)
cache.set("no_role_did", 1, int(time.time()), role=IDENTITY_OWNER,
cache.set("author_did_network_monitor", 1, int(time.time()), role=NETWORK_MONITOR,
verkey="SomeVerkey5", isCommitted=False)

# endorsers
cache.set("endorser_did_no_role", 1, int(time.time()), role=IDENTITY_OWNER,
verkey="SomeVerkey4", isCommitted=False)
cache.set("network_monitor_did", 1, int(time.time()), role=NETWORK_MONITOR,
cache.set("endorser_did_trustee", 1, int(time.time()), role=TRUSTEE,
verkey="SomeVerkey1", isCommitted=False)
cache.set("endorser_did_steward", 1, int(time.time()), role=STEWARD,
verkey="SomeVerkey2", isCommitted=False)
cache.set("endorser_did_endorser", 1, int(time.time()), role=ENDORSER,
verkey="SomeVerkey3", isCommitted=False)
cache.set("endorser_did_network_monitor", 1, int(time.time()), role=NETWORK_MONITOR,
verkey="SomeVerkey5", isCommitted=False)
cache.set("endorser_did2", 1, int(time.time()), role=ENDORSER,
verkey="SomeVerkey6", isCommitted=False)

return cache


Expand All @@ -52,8 +74,8 @@ def authorizer(idr_cache):


def build_req_multi_signed_by_endorser(author_role, endorser_role=ENDORSER, append_endorser=False):
author_idr = get_idr_by_role(author_role) if author_role != IDENTITY_OWNER else "author_no_role"
endorser_idr = get_idr_by_role(endorser_role)
author_idr = get_idr_by_role_author(author_role) if author_role != IDENTITY_OWNER else "author_no_role"
endorser_idr = get_idr_by_role_endorser(endorser_role)
req = Request(identifier=author_idr,
reqId=1,
operation=randomOperation(),
Expand All @@ -65,7 +87,7 @@ def build_req_multi_signed_by_endorser(author_role, endorser_role=ENDORSER, appe


def build_req_multi_signed_by_author_only(author_role):
idr = get_idr_by_role(author_role)
idr = get_idr_by_role_author(author_role)
return Request(identifier=idr,
reqId=1,
operation=randomOperation(),
Expand All @@ -74,15 +96,15 @@ def build_req_multi_signed_by_author_only(author_role):


def build_req_multi_signed_by_non_author_only(author_role, non_author_role):
return Request(identifier=get_idr_by_role(author_role),
return Request(identifier=get_idr_by_role_author(author_role),
reqId=1,
operation=randomOperation(),
signatures={get_idr_by_role(non_author_role): "sig"},
signatures={get_idr_by_role_endorser(non_author_role): "sig"},
protocolVersion=CURRENT_PROTOCOL_VERSION)


def build_req_signed_by_author_only(author_role):
idr = get_idr_by_role(author_role)
idr = get_idr_by_role_author(author_role)
return Request(identifier=idr,
reqId=1,
operation=randomOperation(),
Expand All @@ -105,10 +127,8 @@ def test_dont_require_endorser_if_one_multisig(authorizer, author_role):


@pytest.mark.parametrize('author_role', [IDENTITY_OWNER, NETWORK_MONITOR])
@pytest.mark.parametrize('non_author_role', [IDENTITY_OWNER, NETWORK_MONITOR, TRUSTEE, STEWARD, ENDORSER])
@pytest.mark.parametrize('non_author_role', [TRUSTEE, STEWARD, ENDORSER])
def test_require_endorser_if_signed_by_non_author(authorizer, author_role, non_author_role):
if author_role == non_author_role:
return
req = build_req_multi_signed_by_non_author_only(author_role, non_author_role)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert not authorized
Expand All @@ -118,17 +138,16 @@ def test_require_endorser_if_signed_by_non_author(authorizer, author_role, non_a
@pytest.mark.parametrize('author_role', [TRUSTEE, STEWARD, ENDORSER])
@pytest.mark.parametrize('non_author_role', [IDENTITY_OWNER, NETWORK_MONITOR, TRUSTEE, STEWARD, ENDORSER])
def test_dont_require_endorser_if_signed_by_non_author(authorizer, author_role, non_author_role):
if author_role == non_author_role:
return
req = build_req_multi_signed_by_non_author_only(author_role, non_author_role)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert authorized


@pytest.mark.parametrize('author_role', [IDENTITY_OWNER, NETWORK_MONITOR])
def test_require_endorser_when_multi_sig(authorizer, author_role):
@pytest.mark.parametrize('endorser_role', [TRUSTEE, STEWARD, ENDORSER])
def test_require_endorser_when_multi_sig(authorizer, author_role, endorser_role):
# isn't authorized without explicit Endorser field
req = build_req_multi_signed_by_endorser(author_role, append_endorser=False)
req = build_req_multi_signed_by_endorser(author_role, endorser_role=endorser_role, append_endorser=False)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert not authorized
assert "'Endorser' field must be explicitly set for the endorsed transaction" in reason
Expand All @@ -139,6 +158,14 @@ def test_require_endorser_when_multi_sig(authorizer, author_role):
assert authorized


@pytest.mark.parametrize('author_role', [IDENTITY_OWNER, NETWORK_MONITOR])
@pytest.mark.parametrize('non_author_role', [IDENTITY_OWNER, NETWORK_MONITOR])
def test_dont_require_endorser_when_multi_sig_by_owner(authorizer, author_role, non_author_role):
req = build_req_multi_signed_by_endorser(author_role, endorser_role=non_author_role, append_endorser=False)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert authorized


@pytest.mark.parametrize('author_role', [TRUSTEE, STEWARD, ENDORSER])
def test_dont_require_endorser_when_multi_sig(authorizer, author_role):
# authorized even without explicit Endorser field
Expand Down Expand Up @@ -181,7 +208,7 @@ def test_endorser_role_checked_when_author_is_endorser(authorizer, author_role):
@pytest.mark.parametrize('author_role', [IDENTITY_OWNER, NETWORK_MONITOR, TRUSTEE, STEWARD])
def test_endorser_must_sign(authorizer, author_role):
req = build_req_multi_signed_by_author_only(author_role=author_role)
req.endorser = get_idr_by_role(ENDORSER)
req.endorser = get_idr_by_role_endorser(ENDORSER)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert not authorized
assert "Endorser must sign the transaction" in reason
Expand All @@ -190,15 +217,15 @@ def test_endorser_must_sign(authorizer, author_role):
@pytest.mark.parametrize('author_role', [IDENTITY_OWNER, NETWORK_MONITOR, TRUSTEE, STEWARD])
def test_endorser_is_author_and_1_sig(authorizer, author_role):
req = build_req_signed_by_author_only(author_role=ENDORSER)
req.endorser = get_idr_by_role(ENDORSER)
req.endorser = get_idr_by_role_author(ENDORSER)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert authorized


@pytest.mark.parametrize('author_role', [IDENTITY_OWNER, NETWORK_MONITOR, TRUSTEE, STEWARD])
def test_endorser_is_not_author_and_1_sig(authorizer, author_role):
req = build_req_signed_by_author_only(author_role=ENDORSER)
req.endorser = "endorser_did2"
req.endorser = get_idr_by_role_endorser(ENDORSER)
authorized, reason = authorizer.authorize(req, AUTH_CONSTR)
assert not authorized
assert "Endorser must sign the transaction" in reason

0 comments on commit 5499deb

Please sign in to comment.