Example #1
0
    async def send_schema(self, schema_data_json: str) -> str:
        """
        Send schema to ledger, then retrieve it as written to the ledger and return it.
        If schema already exists on ledger, log error and return schema.

        :param schema_data_json: schema data json with name, version, attribute names; e.g.,

        ::

            {
                'name': 'my-schema',
                'version': '1.234',
                'attr_names': ['favourite_drink', 'height', 'last_visit_date']
            }

        :return: schema json as written to ledger (or existed a priori)
        """

        LOGGER.debug('Origin.send_schema >>> schema_data_json: %s',
                     schema_data_json)

        schema_data = json.loads(schema_data_json)
        s_key = schema_key(
            schema_id(self.did, schema_data['name'], schema_data['version']))
        with SCHEMA_CACHE.lock:
            try:
                rv_json = await self.get_schema(s_key)
                LOGGER.error(
                    'Schema %s version %s already exists on ledger for origin-did %s: not sending',
                    schema_data['name'], schema_data['version'], self.did)
            except AbsentSchema:  # OK - about to create and send it
                (_, schema_json) = await anoncreds.issuer_create_schema(
                    self.did, schema_data['name'], schema_data['version'],
                    json.dumps(schema_data['attr_names']))
                req_json = await ledger.build_schema_request(
                    self.did, schema_json)
                resp_json = await self._sign_submit(req_json)
                resp = json.loads(resp_json)
                resp_result_txn = resp['result']['txn']
                rv_json = await self.get_schema(
                    schema_key(
                        schema_id(resp_result_txn['metadata']['from'],
                                  resp_result_txn['data']['data']['name'],
                                  resp_result_txn['data']['data']['version']))
                )  # add to cache en passant

        LOGGER.debug('Origin.send_schema <<< %s', rv_json)
        return rv_json
Example #2
0
    def __setitem__(self, index: Union[SchemaKey, int], schema: dict) -> dict:
        """
        Put schema into cache and return it.

        :param index: schema key or sequence number
        :param schema: schema to put into cache
        :return: input schema
        """

        logger = logging.getLogger(__name__)
        logger.debug(
            'SchemaCache.__setitem__: >>> index: {}, schema: {}'.format(
                index, schema))

        if isinstance(index, SchemaKey):
            self._schema_key2schema[index] = schema
            self._seq_no2schema_key[schema['seqNo']] = index
        elif isinstance(index, int):
            s_key = schema_key(schema['id'])
            self._schema_key2schema[s_key] = schema
            self._seq_no2schema_key[index] = s_key
        else:
            logger.debug(
                'SchemaCache.__setitem__: <!< Bad index {} must be a schema key or a sequence number'
                .format(index))
            raise CacheIndex(
                'Bad index {} must be a schema key or a sequence number'.
                format(index))

        logger.debug('SchemaCache.__setitem__: <<< {}'.format(schema))
        return schema
Example #3
0
    def __getitem__(self, index: Union[SchemaKey, int, str]) -> dict:
        """
        Get schema by schema key, sequence number, or schema identifier. Raise CacheIndex for no such schema.

        Raise CacheIndex for no such index in schema store.

        :param index: schema key, sequence number, or schema identifier
        :return: corresponding schema or None
        """

        LOGGER.debug('SchemaCache.__getitem__ >>> index: %s', index)

        rv = None
        if isinstance(index, SchemaKey):
            rv = self._schema_key2schema[index]
        elif isinstance(index, int) or (isinstance(index, str) and ':2:' not in index):
            try:
                rv = self._schema_key2schema[self._seq_no2schema_key[int(index)]]
            except KeyError:
                LOGGER.debug('SchemaCache.__getitem__: <!< index %s not present', index)
                raise CacheIndex('{}'.format(index))
        elif isinstance(index, str):
            rv = self._schema_key2schema[schema_key(index)]
        else:
            LOGGER.debug('SchemaCache.__getitem__: <!< index %s must be int SchemaKey, or schema id', index)
            raise CacheIndex('{} must be int, SchemaKey, or schema id'.format(index))

        LOGGER.debug('SchemaCache.__getitem__ <<< %s', rv)
        return rv
Example #4
0
    def contains(self, index: Union[SchemaKey, int, str]) -> bool:
        """
        Return whether the cache contains a schema for the input key, sequence number, or schema identifier.

        :param index: schema key, sequence number, or sequence identifier
        :return: whether the cache contains a schema for the input index
        """

        LOGGER.debug('SchemaCache.contains >>> index: %s', index)

        rv = None
        if isinstance(index, SchemaKey):
            rv = (index in self._schema_key2schema)
        elif isinstance(index, int) or (isinstance(index, str) and ':2:' not in index):
            rv = (int(index) in self._seq_no2schema_key)
        elif isinstance(index, str):
            rv = (schema_key(index) in self._schema_key2schema)
        else:
            rv = False

        LOGGER.debug('SchemaCache.contains <<< %s', rv)
        return rv
Example #5
0
    def __setitem__(self, index: Union[SchemaKey, int], schema: dict) -> dict:
        """
        Put schema into cache and return it.

        :param index: schema key or sequence number
        :param schema: schema to put into cache
        :return: input schema
        """

        LOGGER.debug('SchemaCache.__setitem__ >>> index: %s, schema: %s', index, schema)

        if isinstance(index, SchemaKey):
            self._schema_key2schema[index] = schema
            self._seq_no2schema_key[schema['seqNo']] = index
        elif isinstance(index, int):
            s_key = schema_key(schema['id'])
            self._schema_key2schema[s_key] = schema
            self._seq_no2schema_key[index] = s_key
        else:
            LOGGER.debug('SchemaCache.__setitem__: <!< Bad index %s must be a schema key or a sequence number', index)
            raise CacheIndex('Bad index {} must be a schema key or a sequence number'.format(index))

        LOGGER.debug('SchemaCache.__setitem__ <<< %s', schema)
        return schema
Example #6
0
    async def send_cred_def(self,
                            s_id: str,
                            revocation: bool = True,
                            rr_size: int = None) -> str:
        """
        Create a credential definition as Issuer, store it in its wallet, and send it to the ledger.

        Raise CorruptWallet for wallet not pertaining to current ledger, BadLedgerTxn on failure
        to send credential definition to ledger if need be, or IndyError for any other failure
        to create and store credential definition in wallet.

        :param s_id: schema identifier
        :param revocation: whether to support revocation for cred def
        :param rr_size: size of initial revocation registry (default as per _create_rev_reg()), if revocation supported
        :return: json credential definition as it appears on ledger
        """

        LOGGER.debug(
            'Issuer.send_cred_def >>> s_id: %s, revocation: %s, rr_size: %s',
            s_id, revocation, rr_size)

        rv_json = json.dumps({})
        schema_json = await self.get_schema(schema_key(s_id))
        schema = json.loads(schema_json)

        cd_id = cred_def_id(self.did, schema['seqNo'])
        private_key_ok = True
        with CRED_DEF_CACHE.lock:
            try:
                rv_json = await self.get_cred_def(cd_id)
                LOGGER.info(
                    'Cred def on schema %s version %s already exists on ledger; Issuer %s not sending another',
                    schema['name'], schema['version'], self.wallet.name)
            except AbsentCredDef:
                pass  # OK - about to create, store, and send it

            try:
                (
                    _, cred_def_json
                ) = await anoncreds.issuer_create_and_store_credential_def(
                    self.wallet.handle,
                    self.did,  # issuer DID
                    schema_json,
                    CD_ID_TAG,  # expect only one cred def per schema and issuer
                    'CL',
                    json.dumps({'support_revocation': revocation}))
                if json.loads(rv_json):
                    private_key_ok = False
                    LOGGER.warning(
                        'New cred def on %s in wallet shadows existing one on ledger: private key not usable',
                        cd_id)
                    # carry on though, this agent may have other roles so public key may be good enough
            except IndyError as x_indy:
                if x_indy.error_code == ErrorCode.AnoncredsCredDefAlreadyExistsError:
                    if json.loads(rv_json):
                        LOGGER.info(
                            'Issuer wallet %s reusing existing cred def on schema %s version %s',
                            self.wallet.name, schema['name'],
                            schema['version'])
                    else:
                        LOGGER.debug(
                            'Issuer.send_cred_def: <!< corrupt wallet %s',
                            self.wallet.name)
                        raise CorruptWallet(
                            'Corrupt Issuer wallet {} has cred def on schema {} version {} not on ledger'
                            .format(self.wallet.name, schema['name'],
                                    schema['version']))
                else:
                    LOGGER.debug(
                        'Issuer.send_cred_def: <!< cannot store cred def in wallet %s: indy error code %s',
                        self.wallet.name, x_indy.error_code)
                    raise

            if not json.loads(
                    rv_json
            ):  # checking the ledger returned no cred def: send it
                req_json = await ledger.build_cred_def_request(
                    self.did, cred_def_json)
                await self._sign_submit(req_json)
                rv_json = await self.get_cred_def(
                    cd_id)  # pick up from ledger and parse; add to cache

                if revocation:
                    await self._sync_revoc(
                        rev_reg_id(cd_id, 0),
                        rr_size)  # create new rev reg, tails file for tag 0

        if revocation and private_key_ok:
            for tag in [
                    str(t) for t in range(
                        int(Tails.next_tag(self._dir_tails, cd_id)[0]))
            ]:  # '0' to str(next-1)
                await self._sync_revoc(rev_reg_id(cd_id, tag),
                                       rr_size if tag == 0 else None)

        dir_cred_def = join(self._dir_tails, cd_id)
        if not isdir(
                dir_cred_def
        ):  # make sure a directory exists for box id collection when required, revo or not
            makedirs(dir_cred_def, exist_ok=True)

        LOGGER.debug('Issuer.send_cred_def <<< %s', rv_json)
        return rv_json
Example #7
0
    async def _publish_schema(self, issuer: VonIssuer, schema: dict) -> None:
        """
        Check the ledger for a specific schema and version, and publish it if not found.
        Also publish the related credential definition if not found

        Args:
            issuer: the initialized and opened issuer instance publishing the schema
            schema: a dict which will be updated with the published schema and credential def
        """

        if not schema or "definition" not in schema:
            raise ValueError("Missing schema definition")
        definition = schema["definition"]

        if not schema.get("ledger"):
            LOGGER.info(
                "Checking for schema: %s (%s)",
                definition.name,
                definition.version,
            )
            # Check if schema exists on ledger

            try:
                s_key = schema_key(
                    schema_id(issuer.did, definition.name, definition.version)
                )
                schema_json = await issuer.get_schema(s_key)
                ledger_schema = json.loads(schema_json)
                log_json("Schema found on ledger:", ledger_schema, LOGGER)
            except AbsentSchema:
                # If not found, send the schema to the ledger
                LOGGER.info(
                    "Publishing schema: %s (%s)",
                    definition.name,
                    definition.version,
                )
                schema_json = await issuer.send_schema(
                    json.dumps(
                        {
                            "name": definition.name,
                            "version": definition.version,
                            "attr_names": definition.attr_names,
                        }
                    )
                )
                ledger_schema = json.loads(schema_json)
                if not ledger_schema or not ledger_schema.get("seqNo"):
                    raise RuntimeError("Schema was not published to ledger")
                log_json("Published schema:", ledger_schema, LOGGER)
            schema["ledger"] = ledger_schema

        if not schema.get("credential_definition"):
            # Check if credential definition has been published
            LOGGER.info(
                "Checking for credential def: %s (%s)",
                definition.name,
                definition.version,
            )

            try:
                cred_def_json = await issuer.get_cred_def(
                    cred_def_id(issuer.did, schema["ledger"]["seqNo"])
                )
                cred_def = json.loads(cred_def_json)
                log_json("Credential def found on ledger:", cred_def, LOGGER)
            except AbsentCredDef:
                # If credential definition is not found then publish it
                LOGGER.info(
                    "Publishing credential def: %s (%s)",
                    definition.name,
                    definition.version,
                )
                cred_def_json = await issuer.send_cred_def(
                    schema_json, revocation=False
                )
                cred_def = json.loads(cred_def_json)
                log_json("Published credential def:", cred_def, LOGGER)
            schema["credential_definition"] = cred_def
Example #8
0
    async def get_schema(self, index: Union[SchemaKey, int, str]) -> str:
        """
        Get schema from ledger by SchemaKey namedtuple (origin DID, name, version),
        sequence number, or schema identifier.

        Raise AbsentSchema for no such schema, logging any error condition and raising
        BadLedgerTxn on bad request.

        Retrieve the schema from the agent's schema cache if it has it; cache it
        en passant if it does not (and there is a corresponding schema on the ledger).

        :param index: schema key (origin DID, name, version), sequence number, or schema identifier
        :return: schema json, parsed from ledger
        """

        LOGGER.debug('_BaseAgent.get_schema >>> index: %s', index)

        rv_json = json.dumps({})
        with SCHEMA_CACHE.lock:
            if SCHEMA_CACHE.contains(index):
                LOGGER.info(
                    '_BaseAgent.get_schema: got schema %s from schema cache',
                    index)
                rv_json = SCHEMA_CACHE[index]
                LOGGER.debug('_BaseAgent.get_schema <<< %s', rv_json)
                return json.dumps(rv_json)

            if isinstance(index, SchemaKey) or (isinstance(index, str)
                                                and ':2:' in index):
                s_id = schema_id(
                    *index) if isinstance(index, SchemaKey) else index
                s_key = schema_key(s_id)
                req_json = await ledger.build_get_schema_request(
                    self.did, s_id)
                resp_json = await self._submit(req_json)
                resp = json.loads(resp_json)

                if not ('result' in resp and resp['result'].get(
                        'data', {}).get('attr_names', None)):
                    LOGGER.debug(
                        '_BaseAgent.get_schema: <!< no schema exists on %s',
                        index)
                    raise AbsentSchema('No schema exists on {}'.format(index))
                try:
                    (_, rv_json
                     ) = await ledger.parse_get_schema_response(resp_json)
                except IndyError:  # ledger replied, but there is no such schema
                    LOGGER.debug(
                        '_BaseAgent.get_schema: <!< no schema exists on %s',
                        index)
                    raise AbsentSchema('No schema exists on {}'.format(index))
                SCHEMA_CACHE[s_key] = json.loads(
                    rv_json
                )  # cache indexes by both txn# and schema key en passant
                LOGGER.info('_BaseAgent.get_schema: got schema %s from ledger',
                            index)

            elif isinstance(
                    index, (int, str)
            ):  # ':2:' not in index - it's a stringified int txn num if it's a str
                txn_json = await self.get_txn(int(index))
                txn = json.loads(txn_json)
                if txn.get(
                        'type', None
                ) == '101':  # {} for no such txn; 101 marks indy-sdk schema txn type
                    rv_json = await self.get_schema(
                        SchemaKey(txn['metadata']['from'],
                                  txn['data']['data']['name'],
                                  txn['data']['data']['version']))
                else:
                    LOGGER.info(
                        '_BaseAgent.get_schema: no schema at seq #%s on ledger',
                        index)

            else:
                LOGGER.debug(
                    '_BaseAgent.get_schema: <!< bad schema index type')
                raise AbsentSchema(
                    'Attempt to get schema on ({}) {} , must use schema key or an int'
                    .format(type(index), index))

        LOGGER.debug('_BaseAgent.get_schema <<< %s', rv_json)
        return rv_json