class BaseManager: def __init__(self): """Objects and methods shared across *_manager libraries""" self.batch = Batcher() self.client = ClientSync() def send(self, signer_keypair, object_id, payload, do_send=True, do_get=False): """Sends a payload to the transaction processor""" if not isinstance(signer_keypair, Key): raise TypeError("Expected signer_keypair to be a Key") if not isinstance(payload, RBACPayload): raise TypeError("Expected payload to be an RBACPayload") transaction, batch, batch_list, batch_request = self.batch.make( payload=payload, signer_keypair=signer_keypair) if not do_send: return None, None, transaction, batch, batch_list, batch_request status = self.client.send_batches_get_status(batch_list=batch_list) if not do_get: return None, status, transaction, batch, batch_list, batch_request got = self.get(object_id=object_id) return got, status, transaction, batch, batch_list, batch_request
def exists(self, object_id, related_id): """Check the existence of a relationship record""" # pylint: disable=not-callable container = self._state_container() address = self.address(object_id=object_id, related_id=related_id) data = ClientSync().get_address(address=address) if not data: return False container.ParseFromString(data) stores = list(container.relationships) if not stores: LOGGER.warning( "%s %s relationship container for %s %s at address %s has no records", self.object_type.name.title(), object_id, self.related_type.name.lower(), related_id, address, ) return False if len(stores) > 1: LOGGER.warning( "%s %s relationship container for %s %s at address %s has more than one record", self.object_type.name.title(), object_id, self.related_type.name.lower(), related_id, address, ) store = stores[0] return bool(store.object_id == object_id and store.related_id == related_id)
def process(rec, database): """ Process inbound queue records """ try: if "batch" not in rec or not rec["batch"]: database.run_query( database.get_table("inbound_queue").get(rec["id"]).delete()) rec["sync_direction"] = "inbound" database.run_query(database.get_table("sync_errors").insert(rec)) return batch = batch_pb2.Batch() batch.ParseFromString(rec["batch"]) batch_list = batcher.batch_to_list(batch=batch) status = ClientSync().send_batches_get_status(batch_list=batch_list) if status[0]["status"] == "COMMITTED": if "metadata" in rec and rec["metadata"]: data = { "address": rec["address"], "object_type": rec["object_type"], "object_id": rec["object_id"], "provider_id": rec["provider_id"], "created_at": r.now(), "updated_at": r.now(), **rec["metadata"], } query = ( database.get_table("metadata").get( rec["address"]).replace(lambda doc: r.branch( # pylint: disable=singleton-comparison (doc == None), # noqa r.expr(data), doc.merge({ "metadata": rec["metadata"], "updated_at": r.now() }), ))) result = database.run_query(query) if (not result["inserted"] and not result["replaced"]) or result["errors"] > 0: LOGGER.warning("error updating metadata record:\n%s\n%s", result, query) rec["sync_direction"] = "inbound" database.run_query(database.get_table("changelog").insert(rec)) database.run_query( database.get_table("inbound_queue").get(rec["id"]).delete()) else: rec["error"] = get_status_error(status) rec["sync_direction"] = "inbound" database.run_query(database.get_table("sync_errors").insert(rec)) database.run_query( database.get_table("inbound_queue").get(rec["id"]).delete()) except Exception as err: # pylint: disable=broad-except LOGGER.exception("%s exception processing inbound record:\n%s", type(err).__name__, rec) LOGGER.exception(err)
def send(self, signer_keypair, payload): """Sends a payload to the validator API""" if not isinstance(signer_keypair, Key): raise TypeError("Expected signer_keypair to be a Key") if not isinstance(payload, protobuf.rbac_payload_pb2.RBACPayload): raise TypeError("Expected payload to be an RBACPayload") _, _, batch_list, _ = make(payload=payload, signer_keypair=signer_keypair) status = ClientSync().send_batches_get_status(batch_list=batch_list) return status
class TestRestClient(BatchAssertions): def __init__(self, *args, **kwargs): BatchAssertions.__init__(self, *args, **kwargs) self.client = ClientSync() @pytest.mark.state @pytest.mark.skip("too expensive if large chain, refactor elsewhere") def test_state(self): subtree = addresser.NAMESPACE for item in self.client.list_state(subtree=subtree)["data"]: address_type = item["address_type"] = addresser.address_is( item["address"]) if address_type == addresser.AddressSpace.USER: content = user_state_pb2.UserContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.PROPOSALS: content = proposal_state_pb2.ProposalsContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.SYSADMIN_ATTRIBUTES: content = "SYSADMIN_ATTRIBUTES" elif address_type == addresser.AddressSpace.SYSADMIN_MEMBERS: content = "SYSADMIN_MEMBERS" elif address_type == addresser.AddressSpace.SYSADMIN_OWNERS: content = "SYSADMIN_OWNERS" elif address_type == addresser.AddressSpace.SYSADMIN_ADMINS: content = "SYSADMIN_ADMINS" elif address_type == addresser.AddressSpace.ROLES_ATTRIBUTES: content = role_state_pb2.RoleAttributesContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_MEMBERS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_OWNERS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_ADMINS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_TASKS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.TASKS_ATTRIBUTES: content = task_state_pb2.TaskAttributesContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.TASKS_OWNERS: content = task_state_pb2.TaskRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.TASKS_ADMINS: content = task_state_pb2.TaskRelationshipContainer() content.ParseFromString(b64decode(item["data"])) else: content = "ERROR: unknown type: {}".format(address_type) LOGGER.debug("%-80s%-30s%s", item["address"], address_type, content)
def get(self, object_id, related_id=None): """Gets an address from the blockchain from the validator API""" address = self.address(object_id=object_id, related_id=related_id) # pylint: disable=not-callable container = self._state_container() container.ParseFromString(ClientSync().get_address(address=address)) return self._find_in_state_container( container=container, address=address, object_id=object_id, related_id=related_id, )
class BaseRelationship: def __init__(self): """Objects and methods shared across relationship libraries""" self.client = ClientSync() @property def name(self): raise NotImplementedError("Class must implement this property") @property def container_proto(self): raise NotImplementedError("Class must implement this property") def address(self, object_id, target_id): raise NotImplementedError("Class must implement this method") def exists(self, object_id, target_id): """Check the existence of a relationship record""" container = self.container_proto() address = self.address(object_id=object_id, target_id=target_id) container.ParseFromString(self.client.get_address(address=address)) items = list(container.relationships) if not items: return False if len(items) > 1: LOGGER.warning( "%s %s relationship container for user %s at address %s has more than one record", self.name, object_id, target_id, address, ) item = items[0] identifiers = list(item.identifiers) if not identifiers: LOGGER.warning( "%s %s relationship container for user %s at address %s has no identifiers", self.name, object_id, target_id, address, ) return False if len(identifiers) > 1: LOGGER.warning( "%s %s relationship container for user %s at address %s has more than one identifier", self.name, object_id, target_id, address, ) return bool(target_id in item.identifiers)
def test_state(self): """Grab the entire blockchain state and deserialize it""" subtree = addresser.family.namespace for item in ClientSync().list_state(subtree=subtree)["data"]: address_type = item["address_type"] = addresser.get_address_type( item["address"]) if address_type == addresser.AddressSpace.USER: content = user_state_pb2.UserContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.PROPOSALS: content = proposal_state_pb2.ProposalsContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.SYSADMIN_ATTRIBUTES: content = "SYSADMIN_ATTRIBUTES" elif address_type == addresser.AddressSpace.SYSADMIN_MEMBERS: content = "SYSADMIN_MEMBERS" elif address_type == addresser.AddressSpace.SYSADMIN_OWNERS: content = "SYSADMIN_OWNERS" elif address_type == addresser.AddressSpace.SYSADMIN_ADMINS: content = "SYSADMIN_ADMINS" elif address_type == addresser.AddressSpace.ROLES_ATTRIBUTES: content = role_state_pb2.RoleAttributesContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_MEMBERS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_OWNERS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_ADMINS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.ROLES_TASKS: content = role_state_pb2.RoleRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.TASKS_ATTRIBUTES: content = task_state_pb2.TaskAttributesContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.TASKS_OWNERS: content = task_state_pb2.TaskRelationshipContainer() content.ParseFromString(b64decode(item["data"])) elif address_type == addresser.AddressSpace.TASKS_ADMINS: content = task_state_pb2.TaskRelationshipContainer() content.ParseFromString(b64decode(item["data"])) else: content = "ERROR: unknown type: {}".format(address_type) LOGGER.debug("%-80s%-30s%s", item["address"], address_type, content)
def __init__(self): """Objects and methods shared across message libraries""" self.batch = Batcher() self.client = ClientSync() self.state = StateClient()
class BaseMessage: def __init__(self): """Objects and methods shared across message libraries""" self.batch = Batcher() self.client = ClientSync() self.state = StateClient() def getattr(self, item, attribute): """A version of getattr that will return None if attributes is not found on the item""" if hasattr(item, attribute): return getattr(item, attribute) return None @property def name(self): raise NotImplementedError("Class must implement this property") @property def names(self): return self.name + "s" @property def message_type(self): raise NotImplementedError("Class must implement this method") @property def message_proto(self): raise NotImplementedError("Class must implement this method") @property def container_proto(self): raise NotImplementedError("Class must implement this method") @property def state_proto(self): raise NotImplementedError("Class must implement this method") @property def message_fields_not_in_state(self): """Fields that are on the message but not stored on the state object""" return [] def address(self, object_id, target_id): raise NotImplementedError("Class must implement this method") def make(self, object_id): raise NotImplementedError("Class must implement this method") def make_addresses(self, message, signer_keypair): raise NotImplementedError("Class must implement this method") def base_validate(self, message, signer=None): if not isinstance(message, self.message_proto): raise TypeError("Expected message to be {}".format( self.message_proto)) if (signer is not None and not isinstance(signer, Key) and not (isinstance(signer, str) and PUBLIC_KEY_PATTERN.match(signer))): raise TypeError("Expected signer to be a keypair or a public key") if isinstance(signer, Key): signer = signer.public_key return signer def base_validate_state(self, state, message, signer): if signer is None: raise ValueError("Signer is required") if message is None: raise ValueError("Message is required") if not isinstance(signer, str) and PUBLIC_KEY_PATTERN.match(signer): raise TypeError("Expected signer to be a public key") if state is None: raise ValueError("State is required") def validate(self, message, signer=None): signer = self.base_validate(message=message, signer=signer) def make_payload(self, message, signer_keypair=None): """Make a payload for the given message type""" self.validate(message=message, signer=signer_keypair) message_type = self.message_type inputs, outputs = self.make_addresses(message=message, signer_keypair=signer_keypair) return self.batch.make_payload(message=message, message_type=message_type, inputs=inputs, outputs=outputs) def create(self, signer_keypair, message, object_id=None, target_id=None): """Send a message to the blockchain""" self.validate(message=message, signer=signer_keypair) return self.send( signer_keypair=signer_keypair, payload=self.make_payload(message=message, signer_keypair=signer_keypair), object_id=object_id, target_id=target_id, ) def send(self, signer_keypair, payload, object_id=None, target_id=None): """Sends a payload to the transaction processor""" if not isinstance(signer_keypair, Key): raise TypeError("Expected signer_keypair to be a Key") if not isinstance(payload, protobuf.rbac_payload_pb2.RBACPayload): raise TypeError("Expected payload to be an RBACPayload") _, _, batch_list, _ = self.batch.make(payload=payload, signer_keypair=signer_keypair) got = None status = self.client.send_batches_get_status(batch_list=batch_list) if object_id is not None: got = self.get(object_id=object_id, target_id=target_id) return got, status def _find_in_container(self, container, address, object_id, target_id=None): items = list(getattr(container, self.names)) if not items: return None if len(items) > 1: LOGGER.warning( "%s container for %s target %s has more than one record at address %s", self.name, object_id, target_id, address, ) for item in items: if (self.getattr(item, "object_id") == object_id and self.getattr(item, "target_id") == target_id): return item if self.getattr(item, self.name + "_id") == object_id and target_id is None: return item LOGGER.warning( "%s not found in container for %s target %s at address %s", self.name, object_id, target_id, address, ) return None def get(self, object_id, target_id=None): """Gets an address from the blockchain from the API""" address = self.address(object_id=object_id, target_id=target_id) container = self.container_proto() container.ParseFromString(self.client.get_address(address=address)) return self._find_in_container( container=container, address=address, object_id=object_id, target_id=target_id, ) def get_state(self, state, object_id, target_id=None): """Gets an address from the blockchain state from the state object""" address = self.address(object_id=object_id, target_id=target_id) container = self.container_proto() results = self.state.get_address(state=state, address=address) if not list(results): return None container.ParseFromString(results[0].data) return self._find_in_container( container=container, address=address, object_id=object_id, target_id=target_id, ) def exists_state(self, state, object_id, target_id=None): """Checks an object exists in the blockchain""" got = self.get_state(state=state, object_id=object_id, target_id=target_id) return bool(got is not None)
def __init__(self): """Objects and methods shared across *_manager libraries""" self.batch = Batcher() self.client = ClientSync()
def __init__(self): self.batch = Batcher() self.client = ClientSync()
class UserManager: def __init__(self): self.batch = Batcher() self.client = ClientSync() def make(self, user_id, name, user_name=None, email=None, metadata=None, manager_id=None): return user_state_pb2.User( user_id=user_id, name=name, # user_name=user_name, # email=email, metadata=metadata, manager_id=manager_id, ) def make_with_key( self, name, user_id=None, user_name=None, email=None, metadata=None, manager_id=None, ): keypair = Key() if user_id is None: user_id = keypair.public_key user = self.make( user_id=user_id, name=name, user_name=user_name, email=email, metadata=metadata, manager_id=manager_id, ) return user, keypair def create(self, signer_keypair, user, do_batch=True, do_send=True, do_get=False): if not isinstance(signer_keypair, Key): raise TypeError("Expected signer_keypair to be a Key") if not isinstance(user, user_state_pb2.User): raise TypeError( "Expected user to be a user_state_pb2.User, use make first") message = user_transaction_pb2.CreateUser( user_id=user.user_id, # user_name=user_name, name=user.name, metadata=user.metadata, ) inputs = [make_user_address(user_id=user.user_id)] if user.manager_id: message.manager_id = user.manager_id inputs.append(make_user_address(user_id=user.manager_id)) transaction = self.batch.make_transaction( message=message, message_type=RBACPayload.CREATE_USER, inputs=inputs, outputs=inputs, signer_keypair=signer_keypair, ) if not do_batch: return transaction batch = self.batch.make_batch(transaction=transaction) if not do_send: return batch batch_list = self.batch.batch_to_list(batch) status = self.client.send_batches_get_status(batch_list=batch_list) if not do_get: return status return self.get(user_id=user.user_id) def get(self, user_id): address = make_user_address(user_id=user_id) user_container = user_state_pb2.UserContainer() user_container.ParseFromString( self.client.get_address(address=address)) users = list(user_container.users) if len(users) == 0: return None elif len(users) > 1: LOGGER.warning( "user container at address %s has more than one record, looking for %s", address, user_id, ) for user in users: if user.user_id == user_id: return user LOGGER.warning("user %s not found in container address %s", user_id, address) return None
# Copyright 2018 Contributors to Hyperledger Sawtooth # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ----------------------------------------------------------------------------- from rbac.common.sawtooth.client_sync import ClientSync # pylint: disable=invalid-name client = ClientSync() __all__ = ["client"]
class RoleManager: def __init__(self): self.batch = Batcher() self.client = ClientSync() def make(self, role_id, name, metadata=None, admins=None, owners=None): return role_transaction_pb2.CreateRole( role_id=role_id, name=name, metadata=metadata, admins=admins, owners=owners ) def create(self, signer_keypair, role, do_batch=True, do_send=True, do_get=False): if not isinstance(signer_keypair, Key): raise TypeError("Expected signer_keypair to be a Key") if not isinstance(role, role_transaction_pb2.CreateRole): raise TypeError( "Expected role to be a role_transaction_pb2.CreateRole, use make first" ) inputs = [ addresser.make_sysadmin_members_address(signer_keypair.public_key), addresser.make_role_attributes_address(role.role_id), ] inputs.extend([addresser.make_user_address(u) for u in role.admins]) inputs.extend([addresser.make_user_address(u) for u in role.owners]) inputs.extend( [ addresser.make_role_admins_address(role_id=role.role_id, user_id=a) for a in role.admins ] ) inputs.extend( [ addresser.make_role_owners_address(role_id=role.role_id, user_id=o) for o in role.owners ] ) transaction = self.batch.make_transaction( message=role, message_type=RBACPayload.CREATE_ROLE, inputs=inputs, outputs=inputs, signer_keypair=signer_keypair, ) if not do_batch: return transaction batch = self.batch.make_batch(transaction=transaction) if not do_send: return batch batch_list = self.batch.batch_to_list(batch) status = self.client.send_batches_get_status(batch_list=batch_list) if not do_get: return status return self.get(role_id=role.role_id) def get(self, role_id): container = role_state_pb2.RoleAttributesContainer() address = addresser.make_role_attributes_address(role_id=role_id) container.ParseFromString(self.client.get_address(address=address)) items = list(container.role_attributes) if len(items) == 0: return None elif len(items) > 1: LOGGER.warning( "role container for %s at address %s has more than one role record", role_id, address, ) return items[0] def check_owner(self, role_id, user_id): container = role_state_pb2.RoleRelationshipContainer() address = addresser.make_role_owners_address(role_id=role_id, user_id=user_id) container.ParseFromString(self.client.get_address(address=address)) items = list(container.relationships) if len(items) == 0: return False elif len(items) > 1: LOGGER.warning( "role %s owners container for user %s at address %s has more than one record", role_id, user_id, address, ) item = items[0] identifiers = list(item.identifiers) if len(identifiers) == 0: LOGGER.warning( "role %s owners container for user %s at address %s has no identifiers", role_id, user_id, address, ) return False if len(identifiers) > 1: LOGGER.warning( "role %s owners container for user %s at address %s has more than one identifier", role_id, user_id, address, ) return bool(user_id in item.identifiers) def check_admin(self, role_id, user_id): container = role_state_pb2.RoleRelationshipContainer() address = addresser.make_role_admins_address(role_id=role_id, user_id=user_id) container.ParseFromString(self.client.get_address(address=address)) items = list(container.relationships) if len(items) == 0: return False elif len(items) > 1: LOGGER.warning( "role %s admins container for user %s at address %s has more than one record", role_id, user_id, address, ) item = items[0] identifiers = list(item.identifiers) if len(identifiers) == 0: LOGGER.warning( "role %s admins container for user %s at address %s has no identifiers", role_id, user_id, address, ) return False if len(identifiers) > 1: LOGGER.warning( "role %s admins container for user %s at address %s has more than one identifier", role_id, user_id, address, ) return bool(user_id in item.identifiers)
def __init__(self): """Objects and methods shared across relationship libraries""" self.client = ClientSync()
def __init__(self, *args, **kwargs): BatchAssertions.__init__(self, *args, **kwargs) self.client = ClientSync()
def process(rec, conn): """ Process inbound queue records """ try: # Changes members from distinguished name to next_id for roles if "members" in rec["data"]: rec = translate_field_to_next(rec, "members") if "owners" in rec["data"]: rec = translate_field_to_next(rec, "owners") add_transaction(rec) if "batch" not in rec or not rec["batch"]: r.table("inbound_queue").get(rec["id"]).delete().run(conn) rec["sync_direction"] = "inbound" r.table("sync_errors").insert(rec).run(conn) return batch = batch_pb2.Batch() batch.ParseFromString(rec["batch"]) batch_list = batch_to_list(batch=batch) client = ClientSync() status = client.send_batches_get_status(batch_list=batch_list) while status[0]["status"] == "PENDING": LOGGER.info("Batch status is %s", status) status = client.status_recheck(batch_list) if status[0]["status"] == "COMMITTED": if rec["data_type"] == "user": insert_to_user_mapping(rec) if "metadata" in rec and rec["metadata"]: data = { "address": rec["address"], "object_type": rec["object_type"], "object_id": rec["object_id"], "provider_id": rec["provider_id"], "created_at": r.now(), "updated_at": r.now(), **rec["metadata"], } query = ( r.table("metadata").get( rec["address"]).replace(lambda doc: r.branch( # pylint: disable=singleton-comparison (doc == None), # noqa r.expr(data), doc.merge({ "metadata": rec["metadata"], "updated_at": r.now() }), ))) result = query.run(conn) if (not result["inserted"] and not result["replaced"]) or result["errors"] > 0: LOGGER.warning("error updating metadata record:\n%s\n%s", result, query) rec["sync_direction"] = "inbound" r.table("changelog").insert(rec).run(conn) r.table("inbound_queue").get(rec["id"]).delete().run(conn) else: rec["error"] = get_status_error(status) rec["sync_direction"] = "inbound" r.table("sync_errors").insert(rec).run(conn) r.table("inbound_queue").get(rec["id"]).delete().run(conn) except Exception as err: # pylint: disable=broad-except LOGGER.exception("%s exception processing inbound record:\n%s", type(err).__name__, rec) LOGGER.exception(err)