def validate_task_admin_or_owner( header, confirm, txn_signer_rel_address, task_rel_address, state, is_remove ): """Validate a [ Confirm | Reject }_____Task[ Admin | Owner } transaction. Args: header (TransactionHeader): The transaction header protobuf class.: confirm: ConfirmAddTaskAdmin, RejectAddTaskAdmin, ... txn_signer_rel_address (str): The transaction signer address. task_rel_address (str): The task relationship address. state (Context): The class responsible for gets and sets of state. is_remove (boolean): Determines if task owner is being added or removed. Returns: (dict of addresses) Raises: InvalidTransaction - The transaction is invalid. """ proposal_address = addresser.make_proposal_address( object_id=confirm.task_id, related_id=confirm.user_id ) if not is_remove: state_entries = state_accessor.get_state( state, [txn_signer_rel_address, proposal_address] ) else: state_entries = state_accessor.get_state( state, [txn_signer_rel_address, task_rel_address, proposal_address] ) if not proposal_validator.proposal_exists_and_open( state_entries, proposal_address, confirm.proposal_id ): raise InvalidTransaction( "The proposal {} does not exist or " "is not open".format(confirm.proposal_id) ) try: entry = state_accessor.get_state_entry(state_entries, txn_signer_rel_address) task_rel_container = message_accessor.get_task_rel_container(entry) except KeyError: raise InvalidTransaction( "Signer {} does not have the Task permissions " "to close the proposal".format(header.signer_public_key) ) if not message_accessor.is_in_task_rel_container( task_rel_container, task_id=confirm.task_id, identifier=header.signer_public_key ): raise InvalidTransaction( "Signer {} does not have the Task " "permissions to close the " "proposal".format(header.signer_public_key) ) return state_entries
def validate_create_role_state(create_role, state): state_return = state_accessor.get_state( state, [addresser.make_role_attributes_address(create_role.role_id)]) if _role_already_exists(state_return, create_role.role_id): raise InvalidTransaction("Role id {} is already in state".format( create_role.role_id)) users = list(create_role.admins) + list(create_role.owners) user_state_return = state_accessor.get_state( state, [addresser.make_user_address(u) for u in users]) user_validator.validate_list_of_user_are_users(user_state_return, users)
def validate_task_rel_proposal(header, propose, rel_address, state): """Validates that the User exists, the Task exists, and the User is not in the Task's relationship specified by rel_address. Args: header (TransactionHeader): The transaction header. propose (ProposeAddTask_____): The Task relationship proposal. rel_address (str): The Task relationship address produced by the Task and the User. state (sawtooth_sdk.Context): The way to communicate to the validator the state gets and sets. Returns: (dict of addresses) """ task_id = propose.task_id user_id = propose.user_id user_address = addresser.make_user_address(user_id) task_address = addresser.make_task_attributes_address(task_id) proposal_address = addresser.make_proposal_address(object_id=task_id, related_id=user_id) state_entries = state_accessor.get_state( state, [user_address, task_address, proposal_address, rel_address]) user_validator.validate_identifier_is_user(state_entries, identifier=user_id, address=user_address) user_entry = state_accessor.get_state_entry(state_entries, user_address) user = message_accessor.get_user_from_container( message_accessor.get_user_container(user_entry), user_id) validate_identifier_is_task(state_entries, identifier=task_id, address=task_address) try: task_rel_entry = state_accessor.get_state_entry( state_entries, rel_address) task_rel_container = message_accessor.get_task_rel_container( task_rel_entry) if (header.signer_public_key not in [ user.user_id, user.manager_id ]) and (not message_accessor.is_in_task_rel_container( task_rel_container, task_id, user_id)): raise InvalidTransaction( "Txn signer {} is not the user or the user's " "manager {} nor the task owner / admin".format( header.signer_public_key, [user.user_id, user.manager_id])) if message_accessor.is_in_task_rel_container(task_rel_container, task_id, user_id): raise InvalidTransaction("User {} is already in the Task {} " "relationship".format(user_id, task_id)) except KeyError: # The task rel container doesn't exist so no task relationship exists pass return state_entries
def record_decision(state, header, confirm, isApproval): """ Record decisions made and who made it in the proposal object """ on_behalf_id = confirm.on_behalf_id proposal_address = addresser.make_proposal_address( object_id=confirm.role_id, related_id=confirm.user_id) state_entries = state_accessor.get_state(state, [proposal_address]) proposal_entry = state_accessor.get_state_entry(state_entries, proposal_address) proposal_container = message_accessor.get_prop_container(proposal_entry) proposal = message_accessor.get_prop_from_container( proposal_container, proposal_id=confirm.proposal_id) if isApproval: record = proposal.approvals.add() record.approver = header.signer_public_key record.on_behalf = on_behalf_id else: record = proposal.rejections.add() record.rejector = header.signer_public_key record.on_behalf = on_behalf_id LOGGER.info( "recording decision from {}, on behalf of {} for proposal {}".format( # pylint: disable=logging-format-interpolation header.signer_public_key, confirm.on_behalf_id, confirm.proposal_id)) state_accessor.set_state( state, {proposal_address: proposal_container.SerializeToString()})
def validate_role_task(header, confirm, txn_signer_rel_address, state): proposal_address = addresser.make_proposal_address( object_id=confirm.role_id, related_id=confirm.task_id ) state_entries = state_accessor.get_state( state, [txn_signer_rel_address, proposal_address] ) if not proposal_validator.proposal_exists_and_open( state_entries, proposal_address, confirm.proposal_id ): raise InvalidTransaction( "The proposal {} does not exist or " "is not open".format(confirm.proposal_id) ) try: entry = state_accessor.get_state_entry(state_entries, txn_signer_rel_address) task_owners_container = message_accessor.get_task_rel_container(entry) except KeyError: raise InvalidTransaction( "Signer {} is not a task owner for task {}".format( header.signer_public_key, confirm.task_id ) ) if not message_accessor.is_in_task_rel_container( task_owners_container, confirm.task_id, header.signer_public_key ): raise InvalidTransaction( "Signer {} is not a task owner for task {} no bytes in " "state".format(header.signer_public_key, confirm.task_id) ) return state_entries
def hierarchical_decide(header, confirm, state, txn_signer_rel_address, isApproval): proposal_address = addresser.make_proposal_address( object_id=confirm.role_id, related_id=confirm.user_id) state_entries = state_accessor.get_state( state, [txn_signer_rel_address, proposal_address]) if not proposal_validator.proposal_exists_and_open( state_entries, proposal_address, confirm.proposal_id): raise InvalidTransaction("The proposal {} does not exist or " "is not open".format(confirm.proposal_id)) # verify on_behalf user has the permission to perform the action role_validator.verify_user_with_role_permission_on_proposal( proposal_address, confirm.on_behalf_id, confirm.role_id, txn_signer_rel_address, state, ) # verify current user is in the manager hierachy of on_behalf user if not state_accessor.is_hierarchical_manager_of_user( state, header, confirm.on_behalf_id): raise InvalidTransaction( "Signer {} is not a higher manager of {}. Signer cannot " "make decision on behalf of {}".format(header.signer_public_key, confirm.on_behalf_id, confirm.on_behalf_id)) state_change.record_decision(state, header, confirm, isApproval)
def create_role(new_role, state): role_container = role_state_pb2.RoleAttributesContainer() role = role_container.role_attributes.add() role.role_id = new_role.role_id role.name = new_role.name role.metadata = new_role.metadata entries_to_set = { addresser.make_role_attributes_address( new_role.role_id ): role_container.SerializeToString() } pubkeys_by_address = {} for admin in list(new_role.admins): admin_address = addresser.make_role_admins_address( role_id=new_role.role_id, user_id=admin ) if admin_address in pubkeys_by_address: pubkeys_by_address[admin_address].append(admin) else: pubkeys_by_address[admin_address] = [admin] for owner in list(new_role.owners): owner_address = addresser.make_role_owners_address( role_id=new_role.role_id, user_id=owner ) if owner_address in pubkeys_by_address: pubkeys_by_address[owner_address].append(owner) else: pubkeys_by_address[owner_address] = [owner] state_returns = state_accessor.get_state( state, [ addresser.make_role_admins_address(role_id=new_role.role_id, user_id=a) for a in new_role.admins ] + [ addresser.make_role_owners_address(role_id=new_role.role_id, user_id=o) for o in new_role.owners ], ) for addr, pubkeys in pubkeys_by_address.items(): try: state_entry = state_accessor.get_state_entry(state_returns, addr) container = role_state_pb2.RoleRelationshipContainer() container.ParseFromString(state_entry.data) except KeyError: container = role_state_pb2.RoleRelationshipContainer() message_accessor.add_role_rel_to_container(container, new_role.role_id, pubkeys) entries_to_set[addr] = container.SerializeToString() state_accessor.set_state(state, entries_to_set)
def get_state_entries(header, confirm, txn_signer_rel_address, state): """Fetch a collection of state entries veri Args: header (TransactionHeader): The transaction header protobuf class.: confirm: ConfirmAddRoleAdmin, RejectAddRoleAdmin, ... txn_signer_rel_address: Transaction signer role relationship address state (Context): The class responsible for gets and sets of state. Returns: (dict of addresses) """ proposal_address = addresser.proposal.address(object_id=confirm.role_id, related_id=confirm.user_id) state_entries = state_accessor.get_state( state, [txn_signer_rel_address, proposal_address]) if not proposal_validator.proposal_exists_and_open( state_entries, proposal_address, confirm.proposal_id): raise InvalidTransaction("The proposal {} does not exist or " "is not open".format(confirm.proposal_id)) verify_user_with_role_permission_on_proposal( proposal_address, header.signer_public_key, confirm.role_id, txn_signer_rel_address, state, ) return state_entries
def _validate_state_and_return_user(header, user_proposal, state): """Validate that 1. There is no other open proposal for the manager change 2. The user is a User 3. the manager is a User 4. The manager is the signer of the transaction. Args: header (TransactionHeader): The transaction header. user_proposal (ProposeUpdateUserManager): The transaction that makes the proposal to update the user's manager. state (Context): The way to set and get values from state. """ prop_state_entries = _validate_unique_proposal(user_proposal, state) user_address = addresser.make_user_address(user_id=user_proposal.user_id) user_state_entries = state_accessor.get_state(state, [user_address]) user_validator.validate_identifier_is_user( state_entries=user_state_entries, identifier=user_proposal.user_id, address=user_address, ) manager_address = addresser.make_user_address( user_id=user_proposal.new_manager_id) manager_state_entries = state_accessor.get_state(state, [manager_address]) user_validator.validate_identifier_is_user( state_entries=manager_state_entries, identifier=user_proposal.new_manager_id, address=manager_address, ) user_state_entry = state_accessor.get_state_entry(user_state_entries, user_address) user_container = message_accessor.get_user_container(user_state_entry) _validate_manager_is_signer(header, user_container, user_proposal.user_id) return prop_state_entries
def validate_manager_state(create_user, state): manager_entries = state_accessor.get_state( state, [addresser.user.address(create_user.manager_id)]) if not manager_entries: raise InvalidTransaction("User id {} listed as manager is not " "in state.".format(create_user.manager_id)) state_entry = state_accessor.get_state_entry( manager_entries, addresser.user.address(create_user.manager_id)) manager_container = message_accessor.get_user_container(state_entry) if not message_accessor.is_in_user_container(manager_container, create_user.manager_id): raise InvalidTransaction( "user id {} listed as manager is not within the User container " "in state".format(create_user.manager_id))
def validate_user_state(create_user, state): user_entries = state_accessor.get_state( state, [addresser.user.address(create_user.user_id)]) if user_entries: # this is necessary for state collisions. try: user_container = message_accessor.get_user_container( user_entries[0]) _get_index_in_container(user_container, create_user.user_id) raise InvalidTransaction( "User with user_id {} already exists.".format( create_user.user_id)) except KeyError: # The user does not exist yet in state and so the transaction # is valid. pass
def _validate_unique_proposal(user_proposal, state): proposal_address = addresser.make_proposal_address( object_id=user_proposal.user_id, related_id=user_proposal.new_manager_id) state_return = state_accessor.get_state(state, [proposal_address]) if not proposal_validator.has_no_open_proposal( state_return, proposal_address, user_proposal.user_id, user_proposal.new_manager_id, proposal_type=proposal_state_pb2.Proposal.UPDATE_USER_MANAGER, ): raise InvalidTransaction("There is already a ProposeUpdateUserManager " "proposal for this user and manager.") return state_return
def apply_user_confirm(header, payload, state): confirm_payload = user_transaction_pb2.ConfirmUpdateUserManager() confirm_payload.ParseFromString(payload.content) proposal_address = addresser.proposal.address( object_id=confirm_payload.user_id, target_id=confirm_payload.manager_id ) proposal_entries = state_accessor.get_state(state, [proposal_address]) if not proposal_validator.proposal_exists_and_open( state_entries=proposal_entries, proposal_address=proposal_address, proposal_id=confirm_payload.proposal_id, ): raise InvalidTransaction( "Proposal id {} for user {} to update manager to {} does not exist or is not open.".format( confirm_payload.proposal_id, confirm_payload.user_id, confirm_payload.manager_id, ) ) entry = state_accessor.get_state_entry(proposal_entries, proposal_address) proposal_container = message_accessor.get_prop_container(entry) proposal = message_accessor.get_prop_from_container( container=proposal_container, proposal_id=confirm_payload.proposal_id ) if not proposal.target_id == header.signer_public_key: raise InvalidTransaction( "Confirm update manager txn signed by {} while " "proposal expecting {}".format(header.signer_public_key, proposal.target_id) ) state_change.confirm_manager_change( container=proposal_container, proposal=proposal, closer=header.signer_public_key, reason=confirm_payload.reason, address=proposal_address, user_id=confirm_payload.user_id, new_manager_id=confirm_payload.manager_id, state=state, )
def confirm_manager_change(container, proposal, closer, reason, address, user_id, new_manager_id, state): proposal.status = proposal_state_pb2.Proposal.CONFIRMED proposal.closer = closer proposal.close_reason = reason state_accessor.set_state(state, {address: container.SerializeToString()}) user_address = addresser.make_user_address(user_id) state_entries = state_accessor.get_state(state, [user_address]) state_entry = state_accessor.get_state_entry(state_entries=state_entries, address=user_address) user_container = message_accessor.get_user_container(state_entry) user = message_accessor.get_user_from_container(user_container, user_id) user.manager_id = new_manager_id state_accessor.set_state( state, {user_address: user_container.SerializeToString()})
def apply_user_reject(header, payload, state): reject_payload = user_transaction_pb2.RejectUpdateUserManager() reject_payload.ParseFromString(payload.content) proposal_address = addresser.proposal.address( object_id=reject_payload.user_id, target_id=reject_payload.manager_id ) state_entries = state_accessor.get_state(state, [proposal_address]) if not proposal_validator.proposal_exists_and_open( state_entries=state_entries, proposal_address=proposal_address, proposal_id=reject_payload.proposal_id, ): raise InvalidTransaction( "Proposal {} is not open or does not " "exist".format(reject_payload.proposal_id) ) entry = state_accessor.get_state_entry(state_entries, proposal_address) proposal_container = message_accessor.get_prop_container(entry) if not reject_payload.manager_id == header.signer_public_key: raise InvalidTransaction( "Proposal expected closer to be {} while txn " "signer was {}".format(reject_payload.manager_id, header.signer_public_key) ) proposal = message_accessor.get_prop_from_container( proposal_container, reject_payload.proposal_id ) state_change.reject_state_change( container=proposal_container, proposal=proposal, closer=header.signer_public_key, reason=reject_payload.reason, address=proposal_address, state=state, )
def verify_user_with_role_permission_on_proposal(proposal_address, user_id, role_id, txn_signer_rel_address, state): state_entries = state_accessor.get_state( state, [txn_signer_rel_address, proposal_address]) try: entry = state_accessor.get_state_entry(state_entries, txn_signer_rel_address) role_rel_container = message_accessor.get_role_rel_container(entry) except KeyError: raise InvalidTransaction( "Signer {} does not have the Role permissions " "to close the proposal".format(user_id)) if not message_accessor.is_in_role_rel_container( role_rel_container, role_id=role_id, identifier=user_id): raise InvalidTransaction("Signer {} does not have the Role " "permissions to close the " "proposal".format(user_id))
def new_task(payload, state): create_payload = task_transaction_pb2.CreateTask() create_payload.ParseFromString(payload.content) addresses = [ addresser.make_task_attributes_address(create_payload.task_id) ] if not create_payload.admins: raise InvalidTransaction("New tasks must have administrators.") if not create_payload.owners: raise InvalidTransaction("New tasks must have owners.") if create_payload.admins: addresses.extend( [addresser.make_user_address(u) for u in create_payload.admins]) addresses.extend([ addresser.make_task_admins_address(task_id=create_payload.task_id, user_id=u) for u in create_payload.admins ]) if create_payload.owners: addresses.extend( [addresser.make_user_address(u) for u in create_payload.owners]) addresses.extend([ addresser.make_task_owners_address(create_payload.task_id, user_id=u) for u in create_payload.owners ]) state_entries = state_accessor.get_state(state, addresses) task_validator.validate_create_task_state(state_entries=state_entries, payload=create_payload) create_task(state_entries, create_payload, state)
def validate_role_rel_proposal(header, propose, rel_address, state, is_remove=False): """Validates that the User exists, the Role exists, and the User is not in the Role's relationship specified by rel_address. Args: header (TransactionHeader): The transaction header. propose (ProposeAddRole_____): The role relationship proposal. rel_address (str): The Role relationship address produced by the Role and the User. state (sawtooth_sdk.Context): The way to communicate to the validator the state gets and sets. Returns: (dict of addresses) """ user_address = addresser.make_user_address(propose.user_id) role_address = addresser.make_role_attributes_address(propose.role_id) proposal_address = addresser.make_proposal_address( object_id=propose.role_id, related_id=propose.user_id ) state_entries = state_accessor.get_state( state, [user_address, role_address, proposal_address, rel_address] ) user_validator.validate_identifier_is_user( state_entries, identifier=propose.user_id, address=user_address ) user_entry = state_accessor.get_state_entry(state_entries, user_address) user = message_accessor.get_user_from_container( message_accessor.get_user_container(user_entry), propose.user_id ) if header.signer_public_key not in [user.user_id, user.manager_id]: raise InvalidTransaction( "Txn signer {} is not the user or the user's " "manager {}".format( header.signer_public_key, [user.user_id, user.manager_id] ) ) validate_identifier_is_role( state_entries, identifier=propose.role_id, address=role_address ) try: role_admins_entry = state_accessor.get_state_entry(state_entries, rel_address) role_rel_container = message_accessor.get_role_rel_container(role_admins_entry) if (header.signer_public_key not in [user.user_id, user.manager_id]) and ( not message_accessor.is_in_role_rel_container( role_rel_container, propose.role_id, propose.user_id ) ): raise InvalidTransaction( "Txn signer {} is not the user or the user's " "manager {} nor the group owner / admin".format( header.signer_public_key, [user.user_id, user.manager_id] ) ) if (not is_remove) and message_accessor.is_in_role_rel_container( role_rel_container, propose.role_id, propose.user_id ): raise InvalidTransaction( "User {} is already in the Role {} " "relationship".format(propose.user_id, propose.role_id) ) except KeyError: # The role rel container doesn't exist so no role relationship exists pass return state_entries
def validate_role_task_proposal(header, propose, state): """Applies state validation rules for ADDRoleTaskProposal. - The Role exists. - The Task exists. - The Transaction was signed by a Role Owner. - There is no open Proposal for the same change. - The task is not already part of the Role. Args: header (TransactionHeader): The propobuf transaction header. propose (ProposeAddRoleTask): The protobuf transaction. state (Context): A connection to the validator to ask about state. Returns: (list of StateEntry) """ role_address = addresser.role.address(propose.role_id) task_address = addresser.task.address(propose.task_id) proposal_address = addresser.proposal.address(propose.role_id, propose.task_id) txn_signer_role_owner_address = addresser.role.owner.address( propose.role_id, header.signer_public_key) role_tasks_address = addresser.role.task.address(propose.role_id, propose.task_id) state_entries = state_accessor.get_state( state=state, addresses=[ role_address, task_address, proposal_address, role_tasks_address, txn_signer_role_owner_address, ], ) role_validator.validate_identifier_is_role(state_entries=state_entries, address=role_address, identifier=propose.role_id) task_validator.validate_identifier_is_task(state_entries=state_entries, identifier=propose.task_id, address=task_address) try: role_task_entry = state_accessor.get_state_entry( state_entries, role_tasks_address) role_task_container = message_accessor.get_role_rel_container( role_task_entry) if message_accessor.is_in_role_rel_container( role_task_container, role_id=propose.role_id, identifier=propose.task_id): raise InvalidTransaction("Role {} already contains task {}".format( propose.role_id, propose.task_id)) except KeyError: # The Task is not in the RoleTask state pass try: role_owner_entry = state_accessor.get_state_entry( state_entries, txn_signer_role_owner_address) role_owner_container = message_accessor.get_role_rel_container( role_owner_entry) if not message_accessor.is_in_role_rel_container( role_owner_container, role_id=propose.role_id, identifier=header.signer_public_key, ): raise InvalidTransaction( "Txn signer {} is not a role owner".format( header.signer_public_key)) except KeyError: raise InvalidTransaction("Txn signer {} is not a role owner.".format( header.signer_public_key)) if not has_no_open_proposal( state_entries=state_entries, object_id=propose.role_id, related_id=propose.task_id, proposal_address=proposal_address, proposal_type=proposal_state_pb2.Proposal.ADD_ROLE_TASK, ): raise InvalidTransaction( "There is already an open proposal to add task {} to " "role {}".format(propose.task_id, propose.role_id)) return state_entries
def validate_task_rel_del_proposal(header, propose, rel_address, state): """Validates that the User exists, the Task exists, and the User is in the Tasks's relationship specified by the rel_address. Args: header (TransactionHeader): The transaction header. propose (ProposeRemoveTask____): The Task Remove relationship proposal rel_address (str): The task relationship address. state (Context:: The way to communicate to the validator State gets and sets. Returns: (dict of addresses) """ user_address = addresser.make_user_address(propose.user_id) task_address = addresser.make_task_attributes_address(propose.task_id) proposal_address = addresser.make_proposal_address( object_id=propose.task_id, related_id=propose.user_id) state_entries = state_accessor.get_state( state, [user_address, task_address, proposal_address, rel_address]) user_validator.validate_identifier_is_user(state_entries, identifier=propose.user_id, address=user_address) user_entry = state_accessor.get_state_entry(state_entries, user_address) user = message_accessor.get_user_from_container( message_accessor.get_user_container(user_entry), propose.user_id) validate_identifier_is_task(state_entries, identifier=propose.task_id, address=task_address) try: task_rel_entry = state_accessor.get_state_entry( state_entries, rel_address) task_rel_container = message_accessor.get_task_rel_container( task_rel_entry) if (header.signer_public_key not in [ user.user_id, user.manager_id ]) and (not message_accessor.is_in_task_rel_container( task_rel_container, propose.task_id, propose.user_id)): raise InvalidTransaction( "Txn signer {} is not the user or the user's " "manager {} nor the task owner / admin".format( header.signer_public_key, [user.user_id, user.manager_id])) if not message_accessor.is_in_task_rel_container( task_rel_container, propose.task_id, propose.user_id): raise InvalidTransaction("User {} isn't in the Task {} " "relationship".format( propose.user_id, propose.task_id)) except KeyError: raise InvalidTransaction( "User {} isn't in the Task {} relationship, " "since there isn't a container at the address".format( propose.user_id, propose.task_id)) return state_entries