def create_task(txn_key, batch_key, task_id, task_name, admins, owners, metadata): create_payload = task_transaction_pb2.CreateTask(task_id=task_id, name=task_name) create_payload.admins.extend(admins) inputs = [ addresser.make_task_attributes_address(task_id=task_id), addresser.make_sysadmin_members_address(txn_key.public_key), ] inputs.extend([addresser.make_user_address(user_id=u) for u in admins]) inputs.extend([addresser.make_task_admins_address(task_id, u) for u in admins]) outputs = [addresser.make_task_attributes_address(task_id=task_id)] outputs.extend([addresser.make_task_admins_address(task_id, u) for u in admins]) if owners: create_payload.owners.extend(owners) inputs.extend([addresser.make_user_address(user_id=u) for u in owners]) inputs.extend([addresser.make_task_owners_address(task_id, u) for u in owners]) outputs.extend([addresser.make_task_owners_address(task_id, u) for u in owners]) rbac_payload = rbac_payload_pb2.RBACPayload( content=create_payload.SerializeToString(), message_type=rbac_payload_pb2.RBACPayload.CREATE_TASK, ) return make_header_and_batch(rbac_payload, inputs, outputs, txn_key, batch_key)
def create_task(state_entries, payload, state): try: entry = state_accessor.get_state_entry( state_entries, addresser.make_task_attributes_address(payload.task_id)) container = message_accessor.get_task_container(entry) except KeyError: container = task_state_pb2.TaskAttributesContainer() task = container.task_attributes.add() task.task_id = payload.task_id task.name = payload.name task.metadata = payload.metadata address_values = {} pubkeys_by_address = {} for pubkey in payload.admins: address = addresser.make_task_admins_address(task_id=payload.task_id, user_id=pubkey) if address in pubkeys_by_address: pubkeys_by_address[address].append(pubkey) else: pubkeys_by_address[address] = [pubkey] address_values.update( _handle_task_rel_container( state_entries=state_entries, task_id=payload.task_id, pubkeys_by_address=pubkeys_by_address, )) pubkeys_by_address = {} for pubkey in payload.owners: address = addresser.make_task_owners_address(task_id=payload.task_id, user_id=pubkey) if address in pubkeys_by_address: pubkeys_by_address[address].append(pubkey) else: pubkeys_by_address[address] = [pubkey] address_values.update( _handle_task_rel_container( state_entries=state_entries, task_id=payload.task_id, pubkeys_by_address=pubkeys_by_address, )) address_values[addresser.make_task_attributes_address( payload.task_id)] = container.SerializeToString() state_accessor.set_state(state, address_values)
def propose_add_task_admins( txn_key, batch_key, proposal_id, task_id, user_id, reason, metadata ): propose_payload = task_transaction_pb2.ProposeAddTaskAdmin( proposal_id=proposal_id, task_id=task_id, user_id=user_id, reason=reason, metadata=metadata, ) inputs = [ addresser.make_user_address(user_id), addresser.make_task_admins_address(task_id=task_id, user_id=user_id), addresser.make_proposal_address(task_id, user_id), addresser.make_task_attributes_address(task_id), ] outputs = [addresser.make_proposal_address(task_id, user_id)] rbac_payload = rbac_payload_pb2.RBACPayload( content=propose_payload.SerializeToString(), message_type=rbac_payload_pb2.RBACPayload.PROPOSE_ADD_TASK_ADMINS, ) return make_header_and_batch(rbac_payload, inputs, outputs, txn_key, batch_key)
def propose_remove_task_owners( txn_key, batch_key, proposal_id, task_id, user_id, reason, metadata ): propose = task_transaction_pb2.ProposeRemoveTaskOwner( proposal_id=proposal_id, task_id=task_id, user_id=user_id, reason=reason, metadata=metadata, ) inputs = [ addresser.make_user_address(user_id), addresser.make_task_owners_address(task_id=task_id, user_id=user_id), addresser.make_proposal_address(task_id, user_id), addresser.make_task_attributes_address(task_id), ] outputs = [addresser.make_proposal_address(task_id, user_id)] rbac_payload = rbac_payload_pb2.RBACPayload( content=propose.SerializeToString(), message_type=rbac_payload_pb2.RBACPayload.PROPOSE_REMOVE_TASK_OWNERS, ) return make_header_and_batch(rbac_payload, inputs, outputs, txn_key, batch_key)
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 propose_add_role_tasks( txn_key, batch_key, proposal_id, role_id, task_id, reason, metadata ): propose_payload = role_transaction_pb2.ProposeAddRoleTask( proposal_id=proposal_id, role_id=role_id, task_id=task_id, reason=reason, metadata=metadata, ) inputs = [ addresser.make_proposal_address(role_id, task_id), addresser.make_role_tasks_address(role_id, task_id), addresser.make_role_owners_address(role_id, txn_key.public_key), addresser.make_role_attributes_address(role_id), addresser.make_task_attributes_address(task_id), ] outputs = [addresser.make_proposal_address(role_id, task_id)] rbac_payload = rbac_payload_pb2.RBACPayload( content=propose_payload.SerializeToString(), message_type=rbac_payload_pb2.RBACPayload.PROPOSE_ADD_ROLE_TASKS, ) return make_header_and_batch(rbac_payload, inputs, outputs, txn_key, batch_key)
def test_generated_task_address(self): """Tests the task address creation function as well as the address_is function. """ ident = uuid4().hex address = addresser.make_task_attributes_address(ident) self.assertEqual(len(address), addresser.ADDRESS_LENGTH, "The address is 70 characters") self.assertTrue(addresser.is_address(address), "The address is 70 character hexidecimal") self.assertTrue(addresser.namespace_ok(address), "The address has correct namespace prefix") self.assertTrue( addresser.is_family_address(address), "The address is 70 character hexidecimal with family prefix", ) self.assertEqual( addresser.address_is(address), AddressSpace.TASKS_ATTRIBUTES, "The address created must be a Task Attributes address.", )
def test_deterministic_task_address(self): """Tests that a specific task_id generates the expected task address, and thus is probably deterministic. """ ident = "99968acb8f1a48b3a4bc21e2cd252e67" expected_address = "9f44481e326a1713a905b26359fc8d\ a2817c1a5f67de6f464701f0c10042da345d2800" address = addresser.make_task_attributes_address(ident) self.assertEqual(len(address), addresser.ADDRESS_LENGTH, "The address is 70 characters") self.assertTrue(addresser.is_address(address), "The address is 70 character hexidecimal") self.assertTrue(addresser.namespace_ok(address), "The address has correct namespace prefix") self.assertTrue( addresser.is_family_address(address), "The address is 70 character hexidecimal with family prefix", ) self.assertEqual(address, expected_address, "The address is the one we expected it to be") self.assertEqual( addresser.address_is(address), AddressSpace.TASKS_ATTRIBUTES, "The address created must be a Task Attributes address.", )
def test_task_addresses(self): """Tests the Task address creation functions as well as the address_is function. Notes: 1. Create an address of a particular type: - Task Attributes - Task Owners - Task Admins 2. Assert that address_is returns the correct address type. """ task_address = addresser.make_task_attributes_address(uuid4().hex) self.assertEqual(len(task_address), 70, "The address is a well-formed address.") self.assertEqual( addresser.address_is(task_address), AddressSpace.TASKS_ATTRIBUTES, "The Task Attributes address created must be " "found to be a Task Attributes address.", ) task_owners_address = addresser.make_task_owners_address( task_id=uuid4().hex, user_id=uuid4().hex ) self.assertEqual( len(task_owners_address), 70, "The address is a well-formed address." ) self.assertEqual( addresser.address_is(task_owners_address), AddressSpace.TASKS_OWNERS, "The Task Owners address created must be " "found to be a Task Owners address.", ) task_admins_address = addresser.make_task_admins_address( task_id=uuid4().hex, user_id=uuid4().hex ) self.assertEqual( len(task_admins_address), 70, "The address is a well-formed address." ) self.assertEqual( addresser.address_is(task_admins_address), AddressSpace.TASKS_ADMINS, "The Task Admins address created must be " "found to be a Task Admins address.", )
def validate_create_task_state(state_entries, payload): user_validator.validate_list_of_user_are_users(state_entries, payload.admins) user_validator.validate_list_of_user_are_users(state_entries, payload.owners) try: entry = state_accessor.get_state_entry( state_entries, addresser.make_task_attributes_address(payload.task_id) ) container = message_accessor.get_task_container(entry) if message_accessor.is_in_task_container(container, payload.task_id): raise InvalidTransaction( "Task with id {} already in " "state".format(payload.task_id) ) except KeyError: # The task container is not in state, so no at this address. pass
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_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
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.make_role_attributes_address(propose.role_id) task_address = addresser.make_task_attributes_address(propose.task_id) proposal_address = addresser.make_proposal_address(propose.role_id, propose.task_id) txn_signer_role_owner_address = addresser.make_role_owners_address( role_id=propose.role_id, user_id=header.signer_public_key) role_tasks_address = addresser.make_role_tasks_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_TASKS, ): raise InvalidTransaction( "There is already an open proposal to add task {} to " "role {}".format(propose.task_id, propose.role_id)) return state_entries