def complete_access_service(did, service_endpoint, attributes, template_id, reward_contract_address=None, service_type=ServiceTypes.ASSET_ACCESS): """ Build the access service. :param did: DID, str :param service_endpoint: identifier of the service inside the asset DDO, str :param template_id: id of the template use to create the service, str :param reward_contract_address: hex str ethereum address of deployed reward condition smart contract :return: ServiceAgreement """ param_map = { '_documentId': did_to_id(did), '_amount': attributes['main']['price'] } if reward_contract_address is not None: param_map['_rewardAddress'] = reward_contract_address try: param_map['_did'] = did_to_id(did) param_map['_amounts'] = attributes['main']['_amounts'] param_map['_receivers'] = attributes['main']['_receivers'] param_map['_tokenAddress'] = attributes['main']['_tokenAddress'] param_map['_numberNfts'] = attributes['main']['_numberNfts'] except KeyError as e: logger.error(f'Error mapping field {e}') sla_template_dict = get_sla_template(service_type) sla_template = ServiceAgreementTemplate(template_id, service_type, attributes['main']['creator'], sla_template_dict) sla_template.template_id = template_id conditions = sla_template.conditions[:] for cond in conditions: for param in cond.parameters: param.value = param_map.get(param.name, '') if cond.timeout > 0: cond.timeout = attributes['main']['timeout'] sla_template.set_conditions(conditions) sa = ServiceAgreement(attributes, sla_template, service_endpoint, service_type) return sa
def is_access_granted(self, agreement_id, did, consumer_address): """ Check permission for the agreement. Verify on-chain that the `consumer_address` has permission to access the given asset `did` according to the `agreement_id`. :param agreement_id: id of the agreement, hex str :param did: DID, str :param consumer_address: ethereum account address of consumer, hex str :return: bool True if user has permission """ agreement_consumer = self._keeper.access_template.get_agreement_consumer( agreement_id) if agreement_consumer is None: return False if agreement_consumer != consumer_address: logger.warning(f'Invalid consumer address {consumer_address} and/or ' f'service agreement id {agreement_id} (did {did})' f', agreement consumer is {agreement_consumer}') return False document_id = did_to_id(did) return self._keeper.access_condition.check_permissions( document_id, consumer_address )
def owner(self, did): """ Return the owner of the asset. :param did: DID, str :return: the ethereum address of the owner/publisher of given asset did, hex-str """ # return self._get_metadata_provider(self._metadata_url).get_asset_ddo(did).proof['creator'] return self._keeper.did_registry.get_did_owner(did_to_id(did))
def get_permissions(self, did, address): """ Gets access permission of a grantee :param did: the id of an asset on-chain, hex str :param address: ethereum account address, hex str :return: true if the address has access permission to a DID """ asset_id = add_0x_prefix(did_to_id(did)) return self._keeper.did_registry.get_permission(asset_id, address)
def refund_reward(event, agreement_id, did, service_agreement, price, consumer_account, publisher_address, condition_ids, escrow_condition_id): """ Refund the reward to the publisher address. :param event: AttributeDict with the event data. :param agreement_id: id of the agreement, hex str :param did: DID, str :param service_agreement: ServiceAgreement instance :param price: Asset price, int :param consumer_account: Account instance of the consumer :param publisher_address: ethereum account address of publisher, hex str :param condition_ids: is a list of bytes32 content-addressed Condition IDs, bytes32 :param escrow_condition_id: hex str the id of escrow reward condition at this `agreement_id` """ logger.debug( f"trigger refund (agreement {agreement_id}) after event {event}.") if Keeper.get_instance().condition_manager.get_condition_state( escrow_condition_id) > 1: logger.debug( f'escrow payment condition already fulfilled/aborted: ' f'agreementId={agreement_id}, escrow reward conditionId={escrow_condition_id},' f' publisher={publisher_address}') return access_id, lock_id = condition_ids[:2] name_to_parameter = { param.name: param for param in service_agreement.condition_by_name['escrowPayment'].parameters } document_id = add_0x_prefix(name_to_parameter['_documentId'].value) asset_id = add_0x_prefix(did_to_id(did)) did_owner = Keeper.get_instance( ).agreement_manager.get_agreement_did_owner(agreement_id) assert document_id == asset_id, f'document_id {document_id} <=> asset_id {asset_id} mismatch.' assert price == service_agreement.get_price(), 'price mismatch.' try: escrow_condition = Keeper.get_instance().escrow_payment_condition tx_hash = escrow_condition.fulfill( agreement_id, price, Web3Provider.get_web3().toChecksumAddress(did_owner), consumer_account.address, lock_id, access_id, consumer_account) process_tx_receipt( tx_hash, getattr(escrow_condition.contract.events, escrow_condition.FULFILLED_EVENT)(), 'EscrowReward.Fulfilled') except Exception as e: logger.error( f'Error when doing escrow_payment_condition.fulfills (agreementId {agreement_id}): {e}', exc_info=1) raise e
def revoke_permissions(self, did, address_to_revoke, account): """ Revoke access permission to an address. :param did: the id of an asset on-chain, hex str :param address_to_revoke: ethereum account address, hex str :param account: Account executing the action :return: bool """ asset_id = add_0x_prefix(did_to_id(did)) return self._keeper.did_registry.revoke_permission(asset_id, address_to_revoke, account)
def delegate_persmission(self, did, address_to_grant, account): """ Grant access permission to an address. :param did: the id of an asset on-chain, hex str :param address_to_grant: ethereum account address, hex str :param account: Account executing the action :return: bool """ asset_id = add_0x_prefix(did_to_id(did)) return self._keeper.did_registry.grant_permission(asset_id, address_to_grant, account)
def transfer_nft(self, did, address, amount, account): """ Transfer nft to another address. Return true if successful :param did: the id of an asset on-chain, hex str :param address: ethereum account address, hex str :param amount: amount of nft to be transfer, int :param account: Account executing the action """ return self._keeper.did_registry.transfer_nft(did_to_id(did), address, amount, account)
def transfer_ownership(self, did, new_owner_address, account): """ Transfer did ownership to an address. :param did: the id of an asset on-chain, hex str :param new_owner_address: ethereum account address, hex str :param account: Account executing the action :return: bool """ asset_id = add_0x_prefix(did_to_id(did)) return self._keeper.did_registry.transfer_did_ownership( asset_id, new_owner_address, account)
def test_did_to_id(): did = DID.did("0x123") _id = did_to_id(did) assert _id is not None and len(_id) == 64, '' test_id = '%s' % secrets.token_hex(32) assert did_to_id(f'{NEVERMINED_PREFIX}{test_id}') == test_id assert did_to_id('did:nv1:011') == '011' assert did_to_id('did:nv:0') == '0' with pytest.raises(ValueError): did_to_id(NEVERMINED_PREFIX) assert did_to_id(f'{NEVERMINED_PREFIX}AB*&$#') == 'AB', ''
def was_compute_triggered(agreement_id, did, compute_consumer_address, keeper): agreement_consumer = keeper.escrow_compute_execution_template.get_agreement_consumer( agreement_id) if agreement_consumer is None: return False if agreement_consumer != compute_consumer_address: logger.warning( f'Invalid consumer address {compute_consumer_address} and/or ' f'service agreement id {agreement_id} (did {did})' f', agreement consumer is {agreement_consumer}') return False document_id = did_to_id(did) return keeper.compute_execution_condition.was_compute_triggered( document_id, compute_consumer_address)
def complete_compute_service(did, service_endpoint, attributes, template_id, reward_contract_address): """ Build the access service. :param did: DID, str :param service_endpoint: identifier of the service inside the asset DDO, str :param template_id: id of the template use to create the service, str :param reward_contract_address: hex str ethereum address of deployed reward condition smart contract :return: ServiceAgreement """ param_map = { '_documentId': did_to_id(did), '_amount': attributes['main']['price'], '_rewardAddress': reward_contract_address } try: param_map['_amounts'] = attributes['main']['_amounts'] param_map['_receivers'] = attributes['main']['_receivers'] except KeyError: pass sla_template_dict = get_sla_template(ServiceTypes.CLOUD_COMPUTE) sla_template = ServiceAgreementTemplate(template_id, ServiceTypes.CLOUD_COMPUTE, attributes['main']['creator'], sla_template_dict) sla_template.template_id = template_id conditions = sla_template.conditions[:] for cond in conditions: for param in cond.parameters: param.value = param_map.get(param.name, '') if cond.timeout > 0: cond.timeout = attributes['main']['timeout'] sla_template.set_conditions(conditions) sa = ServiceAgreement(attributes, sla_template, service_endpoint, ServiceTypes.CLOUD_COMPUTE) return sa
def fulfill_exec_compute_condition(event, agreement_id, did, service_agreement, consumer_address, publisher_account, exec_compute_condition_id): """ Fulfill the exec compute condition. :param event: AttributeDict with the event data. :param agreement_id: id of the agreement, hex str :param did: DID, str :param service_agreement: ServiceAgreement instance :param consumer_address: ethereum account address of consumer, hex str :param publisher_account: Account instance of the publisher :param exec_compute_condition_id: hex str the id of the exec compute condition for this `agreement_id` """ if not event: logger.debug(f'`fulfill_exec_compute_condition` got empty event: ' f'event listener timed out.') return keeper = Keeper.get_instance() if keeper.condition_manager.get_condition_state(exec_compute_condition_id) > 1: logger.debug( f'exec compute condition already fulfilled/aborted: ' f'agreementId={agreement_id}, exec compute conditionId={exec_compute_condition_id}' ) return logger.debug(f"grant access (agreement {agreement_id}) after event {event}.") name_to_parameter = {param.name: param for param in service_agreement.condition_by_name['execCompute'].parameters} document_id = add_0x_prefix(name_to_parameter['_documentId'].value) asset_id = add_0x_prefix(did_to_id(did)) assert document_id == asset_id, f'document_id {document_id} <=> asset_id {asset_id} mismatch.' args = ( agreement_id, document_id, consumer_address, publisher_account ) process_fulfill_condition(args, keeper.compute_execution_condition, exec_compute_condition_id, logger, keeper, 10)
def is_access_granted(agreement_id, did, consumer_address, keeper): agreement_consumer = keeper.access_template.get_agreement_consumer( agreement_id) logger.info(agreement_consumer) if agreement_consumer is None: return False if agreement_consumer != consumer_address: logger.warning(f'Invalid consumer address {consumer_address} and/or ' f'service agreement id {agreement_id} (did {did})' f', agreement consumer is {agreement_consumer}') return False document_id = did_to_id(did) is_granted = keeper.access_condition.check_permissions( document_id, consumer_address) logger.info(is_granted) return is_granted
def grant_access(event, instance, agr_id, did, cons_address, account): instance.agreements.conditions.grant_access( agr_id, add_0x_prefix(did_to_id(did)), cons_address, account)
def is_owner_granted(did, consumer_address, keeper): document_id = did_to_id(did) is_granted = keeper.access_condition.check_permissions( document_id, consumer_address) logger.info(is_granted) return is_granted
def create(self, metadata, publisher_account, service_descriptors=None, providers=None, authorization_type=ServiceAuthorizationTypes.PSK_RSA, use_secret_store=False, activity_id=None, attributes=None, asset_rewards={ "_amounts": [], "_receivers": [] }, cap=None, royalties=None): """ Register an asset in both the keeper's DIDRegistry (on-chain) and in the Metadata store. :param metadata: dict conforming to the Metadata accepted by Nevermined Protocol. :param publisher_account: Account of the publisher registering this asset :param service_descriptors: list of ServiceDescriptor tuples of length 2. The first item must be one of ServiceTypes and the second item is a dict of parameters and values required by the service :param providers: list of addresses of providers of this asset (a provider is an ethereum account that is authorized to provide asset services) :param authorization_type: str indicate the authorization type that is going to be used to encrypt the urls. SecretStore, PSK-RSA and PSK-ECDSA are supported. :param use_secret_store: bool indicate whether to use the secret store directly for encrypting urls (Uses Gateway provider service if set to False) :param activity_id: identifier of the activity creating the new entity :param attributes: attributes associated with the action :param asset_rewards: rewards distribution including the amounts and the receivers :param cap: max cap of nfts that can be minted for the asset :param royalties: royalties in the secondary market going to the original creator :return: DDO instance """ assert isinstance( metadata, dict), f'Expected metadata of type dict, got {type(metadata)}' # copy metadata so we don't change the original metadata_copy = copy.deepcopy(metadata) # Create a DDO object ddo = DDO() gateway = GatewayProvider.get_gateway() ddo_service_endpoint = self._get_metadata_provider( ).get_service_endpoint() metadata_service_desc = ServiceDescriptor.metadata_service_descriptor( metadata_copy, ddo_service_endpoint) if metadata_copy['main']['type'] == 'dataset' or metadata_copy['main'][ 'type'] == 'algorithm': access_service_attributes = self._build_access( metadata_copy, publisher_account, asset_rewards) if not service_descriptors: if authorization_type == ServiceAuthorizationTypes.PSK_RSA: service_descriptors = [ ServiceDescriptor.authorization_service_descriptor( self._build_authorization( authorization_type, public_key=gateway.get_rsa_public_key( self._config)), gateway.get_access_endpoint(self._config)) ] elif authorization_type == ServiceAuthorizationTypes.PSK_ECDSA: service_descriptors = [ ServiceDescriptor.authorization_service_descriptor( self._build_authorization( authorization_type, public_key=gateway.get_ecdsa_public_key( self._config)), gateway.get_access_endpoint(self._config)) ] else: service_descriptors = [ ServiceDescriptor.authorization_service_descriptor( self._build_authorization(authorization_type, threshold=0), self._config.secret_store_url) ] service_descriptors += [ ServiceDescriptor.access_service_descriptor( access_service_attributes, gateway.get_access_endpoint(self._config)) ] else: service_types = set(map(lambda x: x[0], service_descriptors)) if ServiceTypes.AUTHORIZATION not in service_types: if authorization_type == ServiceAuthorizationTypes.PSK_RSA: service_descriptors += [ ServiceDescriptor.authorization_service_descriptor( self._build_authorization( authorization_type, public_key=gateway.get_rsa_public_key( self._config)), gateway.get_access_endpoint(self._config)) ] elif authorization_type == ServiceAuthorizationTypes.PSK_ECDSA: service_descriptors += [ ServiceDescriptor.authorization_service_descriptor( self._build_authorization( authorization_type, public_key=gateway.get_ecdsa_public_key( self._config)), gateway.get_access_endpoint(self._config)) ] else: service_descriptors += [ ServiceDescriptor.authorization_service_descriptor( self._build_authorization(authorization_type, threshold=0), self._config.secret_store_url) ] else: service_descriptors += [ ServiceDescriptor.access_service_descriptor( access_service_attributes, gateway.get_access_endpoint(self._config)) ] else: if not service_descriptors: service_descriptors = [] else: service_descriptors += [] logger.info('registering a workflow.') # Add all services to ddo service_descriptors = [metadata_service_desc] + service_descriptors services = ServiceFactory.build_services(service_descriptors) checksums = dict() for service in services: checksums[str(service.index)] = checksum(service.main) # Adding proof to the ddo. ddo.add_proof(checksums, publisher_account) # Generating the did and adding to the ddo. did = ddo.assign_did(DID.did(ddo.proof['checksum'])) logger.debug(f'Generating new did: {did}') # Check if it's already registered first! if did in self._get_metadata_provider().list_assets(): raise DIDAlreadyExist( f'Asset id {did} is already registered to another asset.') for service in services: if service.type == ServiceTypes.ASSET_ACCESS: access_service = ServiceFactory.complete_access_service( did, gateway.get_access_endpoint(self._config), access_service_attributes, self._keeper.access_template.address, self._keeper.escrow_payment_condition.address) ddo.add_service(access_service) elif service.type == ServiceTypes.METADATA: ddo_service_endpoint = service.service_endpoint.replace( '{did}', did) service.set_service_endpoint(ddo_service_endpoint) ddo.add_service(service) elif service.type == ServiceTypes.CLOUD_COMPUTE: compute_service = ServiceFactory.complete_compute_service( did, service.service_endpoint, service.attributes, self._keeper.compute_execution_condition.address, self._keeper.escrow_payment_condition.address) ddo.add_service(compute_service) else: ddo.add_service(service) ddo.proof['signatureValue'] = self._keeper.sign_hash( add_ethereum_prefix_and_hash_msg(did_to_id_bytes(did)), publisher_account) # Add public key and authentication ddo.add_public_key(did, publisher_account.address) ddo.add_authentication(did, PUBLIC_KEY_TYPE_RSA) # Setup metadata service # First compute files_encrypted if metadata_copy['main']['type'] in ['dataset', 'algorithm']: assert metadata_copy['main'][ 'files'], 'files is required in the metadata main attributes.' logger.debug('Encrypting content urls in the metadata.') if not use_secret_store: encrypt_endpoint = gateway.get_encrypt_endpoint(self._config) files_encrypted = gateway.encrypt_files_dict( metadata_copy['main']['files'], encrypt_endpoint, ddo.asset_id, authorization_type) else: files_encrypted = self._get_secret_store(publisher_account) \ .encrypt_document( did_to_id(did), json.dumps(metadata_copy['main']['files']), ) # only assign if the encryption worked if files_encrypted: logger.debug( f'Content urls encrypted successfully {files_encrypted}') index = 0 for file in metadata_copy['main']['files']: file['index'] = index index = index + 1 del file['url'] metadata_copy['encryptedFiles'] = files_encrypted else: raise AssertionError('Encrypting the files failed.') # DDO url and `Metadata` service logger.debug(f'Generated ddo and services, DID is {ddo.did},' f' metadata service @{ddo_service_endpoint}.') response = None # register on-chain registered_on_chain = self._keeper.did_registry.register( ddo.asset_id, checksum=Web3Provider.get_web3().toBytes(hexstr=ddo.asset_id), url=ddo_service_endpoint, account=publisher_account, cap=cap, royalties=royalties, providers=providers, activity_id=activity_id, attributes=attributes) if registered_on_chain is False: logger.warning(f'Registering {did} on-chain failed.') return None logger.info(f'Successfully registered DDO (DID={did}) on chain.') try: # publish the new ddo response = self._get_metadata_provider().publish_asset_ddo(ddo) logger.info('Asset/ddo published successfully in Metadata.') except ValueError as ve: raise ValueError( f'Invalid value to publish in the metadata: {str(ve)}') except Exception as e: logger.error(f'Publish asset in Metadata failed: {str(e)}') if not response: return None return ddo
def asset_id(self): """The asset id part of the DID""" if not self._did: return None return add_0x_prefix(did_to_id(self._did))
def test_did(): assert DID.did("0x123").startswith(NEVERMINED_PREFIX) assert len(DID.did("0x123")) - len(NEVERMINED_PREFIX) == 64 _id = did_to_id(DID.did("0x123")) assert not _id.startswith( '0x'), 'id portion of did should not have a 0x prefix.'