async def handle(self, context: RequestContext, responder: BaseResponder): # Verify connection exists session = await context.session() manager = MediationManager(session) try: connection = await ConnRecord.retrieve_by_id( session, context.message.connection_id) except StorageNotFoundError: report = ProblemReport(explain_ltxt='Connection not found.', who_retries='none') report.assign_thread_from(context.message) await responder.send_reply(report) return _record, request = await manager.prepare_request( connection.connection_id) # Send mediation request await responder.send( request, connection_id=connection.connection_id, ) # Send notification of mediation request sent sent = MediationRequestSent(connection_id=connection.connection_id) sent.assign_thread_from(context.message) await responder.send_reply(sent)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle RotuesGet.""" session = await context.session() manager = MediationManager(session) routes = Routes( routes=await manager.get_my_keylist(context.message.connection_id)) routes.assign_thread_from(context.message) await responder.send_reply(routes)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle mediation deny request.""" session = await context.session() manager = MediationManager(session) record = await MediationRecord.retrieve_by_id( session, context.message.mediation_id ) deny = await manager.deny_request(record) await responder.send(deny, connection_id=record.connection_id) denied = MediationDenied(mediation_id=record.mediation_id) denied.assign_thread_from(context.message) await responder.send_reply(denied)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle KeylistUpdateSend messages.""" session = await context.session() manager = MediationManager(session) if context.message.action == KeylistUpdateRule.RULE_ADD: update = await manager.add_key(context.message.verkey, context.message.connection_id) elif context.message.action == KeylistUpdateRule.RULE_REMOVE: update = await manager.remove_key(context.message.verkey, context.message.connection_id) await responder.send( update, connection_id=context.message.connection_id, ) sent = KeylistUpdateSent(connection_id=context.message.connection_id, verkey=context.message.verkey, action=context.message.action) sent.assign_thread_from(context.message) await responder.send_reply(sent)
async def accept_response(self, response: ConnectionResponse, receipt: MessageReceipt) -> ConnRecord: """ Accept a connection response. Process a ConnectionResponse message by looking up the connection request and setting up the pairwise connection. Args: response: The `ConnectionResponse` to accept receipt: The message receipt Returns: The updated `ConnRecord` representing the connection Raises: ConnectionManagerError: If there is no DID associated with the connection response ConnectionManagerError: If the corresponding connection is not at the request or response stage """ connection = None if response._thread: # identify the request by the thread ID try: connection = await ConnRecord.retrieve_by_request_id( self._session, response._thread_id) except StorageNotFoundError: pass if not connection and receipt.sender_did: # identify connection by the DID they used for us try: connection = await ConnRecord.retrieve_by_did( self._session, receipt.sender_did, receipt.recipient_did) except StorageNotFoundError: pass if not connection: raise ConnectionManagerError( "No corresponding connection request found", error_code=ProblemReportReason.RESPONSE_NOT_ACCEPTED, ) if ConnRecord.State.get(connection.state) not in ( ConnRecord.State.REQUEST, ConnRecord.State.RESPONSE, ): raise ConnectionManagerError( f"Cannot accept connection response for connection" f" in state: {connection.state}") their_did = response.connection.did conn_did_doc = response.connection.did_doc if not conn_did_doc: raise ConnectionManagerError( "No DIDDoc provided; cannot connect to public DID") if their_did != conn_did_doc.did: raise ConnectionManagerError( "Connection DID does not match DIDDoc id") await self.store_did_document(conn_did_doc) connection.their_did = their_did connection.state = ConnRecord.State.RESPONSE.rfc160 await connection.save(self._session, reason="Accepted connection response") send_mediation_request = await connection.metadata_get( self._session, MediationManager.SEND_REQ_AFTER_CONNECTION) if send_mediation_request: mgr = MediationManager(self._session.profile) _record, request = await mgr.prepare_request( connection.connection_id) responder = self._session.inject(BaseResponder) await responder.send(request, connection_id=connection.connection_id) return connection
async def create_invitation( self, my_label: str = None, my_endpoint: str = None, auto_accept: bool = None, public: bool = False, multi_use: bool = False, alias: str = None, routing_keys: Sequence[str] = None, recipient_keys: Sequence[str] = None, metadata: dict = None, mediation_id: str = None, ) -> Tuple[ConnRecord, ConnectionInvitation]: """ Generate new connection invitation. This interaction represents an out-of-band communication channel. In the future and in practice, these sort of invitations will be received over any number of channels such as SMS, Email, QR Code, NFC, etc. Structure of an invite message: :: { "@type": "https://didcomm.org/connections/1.0/invitation", "label": "Alice", "did": "did:sov:QmWbsNYhMrjHiqZDTUTEJs" } Or, in the case of a peer DID: :: { "@type": "https://didcomm.org/connections/1.0/invitation", "label": "Alice", "did": "did:peer:oiSqsNYhMrjHiqZDTUthsw", "recipient_keys": ["8HH5gYEeNc3z7PYXmd54d4x6qAfCNrqQqEB3nS7Zfu7K"], "service_endpoint": "https://example.com/endpoint" "routing_keys": ["9EH5gYEeNc3z7PYXmd53d5x6qAfCNrqQqEB4nS7Zfu6K"], } Args: my_label: label for this connection my_endpoint: endpoint where other party can reach me auto_accept: auto-accept a corresponding connection request (None to use config) public: set to create an invitation from the public DID multi_use: set to True to create an invitation for multiple use alias: optional alias to apply to connection for later use Returns: A tuple of the new `ConnRecord` and `ConnectionInvitation` instances """ # Mediation Record can still be None after this operation if no # mediation id passed and no default mediation_record = await mediation_record_if_id( self._session, mediation_id, or_default=True, ) keylist_updates = None image_url = self._session.context.settings.get("image_url") # Multitenancy setup multitenant_mgr = self._session.inject(MultitenantManager, required=False) wallet_id = self._session.settings.get("wallet.id") if not my_label: my_label = self._session.settings.get("default_label") wallet = self._session.inject(BaseWallet) if public: if not self._session.settings.get("public_invites"): raise ConnectionManagerError( "Public invitations are not enabled") public_did = await wallet.get_public_did() if not public_did: raise ConnectionManagerError( "Cannot create public invitation with no public DID") if multi_use: raise ConnectionManagerError( "Cannot use public and multi_use at the same time") if metadata: raise ConnectionManagerError( "Cannot use public and set metadata at the same time") # FIXME - allow ledger instance to format public DID with prefix? invitation = ConnectionInvitation(label=my_label, did=f"did:sov:{public_did.did}", image_url=image_url) # Add mapping for multitenant relaying. # Mediation of public keys is not supported yet if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, public_did.verkey, skip_if_exists=True) return None, invitation invitation_mode = ConnRecord.INVITATION_MODE_ONCE if multi_use: invitation_mode = ConnRecord.INVITATION_MODE_MULTI if recipient_keys: # TODO: register recipient keys for relay # TODO: check that recipient keys are in wallet invitation_key = recipient_keys[0] # TODO first key appropriate? else: # Create and store new invitation key invitation_signing_key = await wallet.create_signing_key() invitation_key = invitation_signing_key.verkey recipient_keys = [invitation_key] mediation_mgr = MediationManager(self._session.profile) keylist_updates = await mediation_mgr.add_key( invitation_key, keylist_updates) if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, invitation_key) accept = (ConnRecord.ACCEPT_AUTO if (auto_accept or (auto_accept is None and self._session.settings.get("debug.auto_accept_requests"))) else ConnRecord.ACCEPT_MANUAL) # Create connection record connection = ConnRecord( invitation_key=invitation_key, # TODO: determine correct key to use their_role=ConnRecord.Role.REQUESTER.rfc160, state=ConnRecord.State.INVITATION.rfc160, accept=accept, invitation_mode=invitation_mode, alias=alias, ) await connection.save(self._session, reason="Created new invitation") routing_keys = [] my_endpoint = my_endpoint or self._session.settings.get( "default_endpoint") # The base wallet can act as a mediator for all tenants if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator( ) if base_mediation_record: routing_keys = base_mediation_record.routing_keys my_endpoint = base_mediation_record.endpoint # If we use a mediator for the base wallet we don't # need to register the key at the subwallet mediator # because it only needs to know the key of the base mediator # sub wallet mediator -> base wallet mediator -> agent keylist_updates = None if mediation_record: routing_keys = [*routing_keys, *mediation_record.routing_keys] my_endpoint = mediation_record.endpoint # Save that this invitation was created with mediation await connection.metadata_set(self._session, "mediation", {"id": mediation_id}) if keylist_updates: responder = self._session.inject(BaseResponder, required=False) await responder.send( keylist_updates, connection_id=mediation_record.connection_id) # Create connection invitation message # Note: Need to split this into two stages to support inbound routing of invites # Would want to reuse create_did_document and convert the result invitation = ConnectionInvitation( label=my_label, recipient_keys=recipient_keys, routing_keys=routing_keys, endpoint=my_endpoint, image_url=image_url, ) await connection.attach_invitation(self._session, invitation) if metadata: for key, value in metadata.items(): await connection.metadata_set(self._session, key, value) return connection, invitation
async def create_response( self, connection: ConnRecord, my_endpoint: str = None, mediation_id: str = None, ) -> ConnectionResponse: """ Create a connection response for a received connection request. Args: connection: The `ConnRecord` with a pending connection request my_endpoint: The endpoint I can be reached at mediation_id: The record id for mediation that contains routing_keys and service endpoint Returns: A tuple of the updated `ConnRecord` new `ConnectionResponse` message """ ConnRecord.log_state( "Creating connection response", {"connection_id": connection.connection_id}, settings=self._session.settings, ) keylist_updates = None mediation_record = await mediation_record_if_id( self._session, mediation_id) # Multitenancy setup multitenant_mgr = self._session.inject(MultitenantManager, required=False) wallet_id = self._session.settings.get("wallet.id") base_mediation_record = None if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator( ) if ConnRecord.State.get(connection.state) not in ( ConnRecord.State.REQUEST, ConnRecord.State.RESPONSE, ): raise ConnectionManagerError( "Connection is not in the request or response state") request = await connection.retrieve_request(self._session) wallet = self._session.inject(BaseWallet) if connection.my_did: my_info = await wallet.get_local_did(connection.my_did) else: my_info = await wallet.create_local_did() connection.my_did = my_info.did mediation_mgr = MediationManager(self._session.profile) keylist_updates = await mediation_mgr.add_key( my_info.verkey, keylist_updates) # Add mapping for multitenant relay if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, my_info.verkey) # Create connection response message if my_endpoint: my_endpoints = [my_endpoint] else: my_endpoints = [] default_endpoint = self._session.settings.get("default_endpoint") if default_endpoint: my_endpoints.append(default_endpoint) my_endpoints.extend( self._session.settings.get("additional_endpoints", [])) did_doc = await self.create_did_document( my_info, connection.inbound_connection_id, my_endpoints, mediation_records=list( filter(None, [base_mediation_record, mediation_record])), ) response = ConnectionResponse( connection=ConnectionDetail(did=my_info.did, did_doc=did_doc)) # Assign thread information response.assign_thread_from(request) response.assign_trace_from(request) # Sign connection field using the invitation key wallet = self._session.inject(BaseWallet) await response.sign_field("connection", connection.invitation_key, wallet) # Update connection state connection.state = ConnRecord.State.RESPONSE.rfc160 await connection.save( self._session, reason="Created connection response", log_params={"response": response}, ) # Update mediator if necessary if keylist_updates and mediation_record: responder = self._session.inject(BaseResponder, required=False) await responder.send(keylist_updates, connection_id=mediation_record.connection_id) # TODO It's possible the mediation request sent here might arrive # before the connection response. This would result in an error condition # difficult to accomodate for without modifying handlers for trust ping # to ensure the connection is active. send_mediation_request = await connection.metadata_get( self._session, MediationManager.SEND_REQ_AFTER_CONNECTION) if send_mediation_request: mgr = MediationManager(self._session.profile) _record, request = await mgr.prepare_request( connection.connection_id) responder = self._session.inject(BaseResponder) await responder.send(request, connection_id=connection.connection_id) return response
async def receive_request( self, request: ConnectionRequest, receipt: MessageReceipt, mediation_id: str = None, ) -> ConnRecord: """ Receive and store a connection request. Args: request: The `ConnectionRequest` to accept receipt: The message receipt Returns: The new or updated `ConnRecord` instance """ ConnRecord.log_state( "Receiving connection request", {"request": request}, settings=self._session.settings, ) mediation_mgr = MediationManager(self._session.profile) keylist_updates = None connection = None connection_key = None my_info = None # Multitenancy setup multitenant_mgr = self._session.inject(MultitenantManager, required=False) wallet_id = self._session.settings.get("wallet.id") wallet = self._session.inject(BaseWallet) # Determine what key will need to sign the response if receipt.recipient_did_public: my_info = await wallet.get_local_did(receipt.recipient_did) connection_key = my_info.verkey else: connection_key = receipt.recipient_verkey try: connection = await ConnRecord.retrieve_by_invitation_key( session=self._session, invitation_key=connection_key, their_role=ConnRecord.Role.REQUESTER.rfc160, ) except StorageNotFoundError: raise ConnectionManagerError( "No invitation found for pairwise connection") invitation = None if connection: invitation = await connection.retrieve_invitation(self._session) connection_key = connection.invitation_key ConnRecord.log_state( "Found invitation", {"invitation": invitation}, settings=self._session.settings, ) if connection.is_multiuse_invitation: wallet = self._session.inject(BaseWallet) my_info = await wallet.create_local_did() keylist_updates = await mediation_mgr.add_key( my_info.verkey, keylist_updates) new_connection = ConnRecord( invitation_key=connection_key, my_did=my_info.did, state=ConnRecord.State.INVITATION.rfc160, accept=connection.accept, their_role=connection.their_role, ) await new_connection.save( self._session, reason= "Received connection request from multi-use invitation DID", ) # Transfer metadata from multi-use to new connection # Must come after save so there's an ID to associate with metadata for key, value in (await connection.metadata_get_all(self._session )).items(): await new_connection.metadata_set(self._session, key, value) connection = new_connection # Add mapping for multitenant relay if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, my_info.verkey) else: # remove key from mediator keylist keylist_updates = await mediation_mgr.remove_key( connection_key, keylist_updates) conn_did_doc = request.connection.did_doc if not conn_did_doc: raise ConnectionManagerError( "No DIDDoc provided; cannot connect to public DID") if request.connection.did != conn_did_doc.did: raise ConnectionManagerError( "Connection DID does not match DIDDoc id", error_code=ProblemReportReason.REQUEST_NOT_ACCEPTED, ) await self.store_did_document(conn_did_doc) if connection: connection.their_label = request.label connection.their_did = request.connection.did connection.state = ConnRecord.State.REQUEST.rfc160 await connection.save( self._session, reason="Received connection request from invitation") elif not self._session.settings.get("public_invites"): raise ConnectionManagerError("Public invitations are not enabled") else: # request from public did my_info = await wallet.create_local_did() # send update-keylist message with new recipient keys keylist_updates = await mediation_mgr.add_key( my_info.verkey, keylist_updates) # Add mapping for multitenant relay if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, my_info.verkey) connection = ConnRecord( invitation_key=connection_key, my_did=my_info.did, their_role=ConnRecord.Role.RESPONDER.rfc160, their_did=request.connection.did, their_label=request.label, accept=( ConnRecord.ACCEPT_AUTO if self._session.settings.get("debug.auto_accept_requests") else ConnRecord.ACCEPT_MANUAL), state=ConnRecord.State.REQUEST.rfc160, ) await connection.save( self._session, reason="Received connection request from public DID") # Attach the connection request so it can be found and responded to await connection.attach_request(self._session, request) # Send keylist updates to mediator mediation_record = await mediation_record_if_id( self._session, mediation_id) if keylist_updates and mediation_record: responder = self._session.inject(BaseResponder, required=False) await responder.send(keylist_updates, connection_id=mediation_record.connection_id) if connection.accept == ConnRecord.ACCEPT_AUTO: response = await self.create_response(connection, mediation_id=mediation_id) responder = self._session.inject(BaseResponder, required=False) if responder: await responder.send_reply( response, connection_id=connection.connection_id) # refetch connection for accurate state connection = await ConnRecord.retrieve_by_id( self._session, connection.connection_id) else: self._logger.debug("Connection request will await acceptance") return connection
async def create_request( self, connection: ConnRecord, my_label: str = None, my_endpoint: str = None, mediation_id: str = None, ) -> ConnectionRequest: """ Create a new connection request for a previously-received invitation. Args: connection: The `ConnRecord` representing the invitation to accept my_label: My label my_endpoint: My endpoint Returns: A new `ConnectionRequest` message to send to the other agent """ keylist_updates = None # Mediation Record can still be None after this operation if no # mediation id passed and no default mediation_record = await mediation_record_if_id( self._session, mediation_id, or_default=True, ) multitenant_mgr = self._session.inject(MultitenantManager, required=False) wallet_id = self._session.settings.get("wallet.id") base_mediation_record = None if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator( ) my_info = None wallet = self._session.inject(BaseWallet) if connection.my_did: my_info = await wallet.get_local_did(connection.my_did) else: # Create new DID for connection my_info = await wallet.create_local_did() connection.my_did = my_info.did mediation_mgr = MediationManager(self._session.profile) keylist_updates = await mediation_mgr.add_key( my_info.verkey, keylist_updates) # Add mapping for multitenant relay if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, my_info.verkey) # Create connection request message if my_endpoint: my_endpoints = [my_endpoint] else: my_endpoints = [] default_endpoint = self._session.settings.get("default_endpoint") if default_endpoint: my_endpoints.append(default_endpoint) my_endpoints.extend( self._session.settings.get("additional_endpoints", [])) did_doc = await self.create_did_document( my_info, connection.inbound_connection_id, my_endpoints, mediation_records=list( filter(None, [base_mediation_record, mediation_record])), ) if not my_label: my_label = self._session.settings.get("default_label") request = ConnectionRequest( label=my_label, connection=ConnectionDetail(did=connection.my_did, did_doc=did_doc), image_url=self._session.settings.get("image_url"), ) # Update connection state connection.request_id = request._id connection.state = ConnRecord.State.REQUEST.rfc160 await connection.save(self._session, reason="Created connection request") # Notify mediator of keylist changes if keylist_updates and mediation_record: # send a update keylist message with new recipient keys. responder = self._session.inject(BaseResponder, required=False) await responder.send(keylist_updates, connection_id=mediation_record.connection_id) return request
async def create_invitation( self, my_label: str = None, my_endpoint: str = None, auto_accept: bool = None, public: bool = False, hs_protos: Sequence[HSProto] = None, multi_use: bool = False, alias: str = None, attachments: Sequence[Mapping] = None, metadata: dict = None, mediation_id: str = None, ) -> InvitationRecord: """ Generate new connection invitation. This interaction represents an out-of-band communication channel. In the future and in practice, these sort of invitations will be received over any number of channels such as SMS, Email, QR Code, NFC, etc. Args: my_label: label for this connection my_endpoint: endpoint where other party can reach me auto_accept: auto-accept a corresponding connection request (None to use config) public: set to create an invitation from the public DID hs_protos: list of handshake protocols to include multi_use: set to True to create an invitation for multiple-use connection alias: optional alias to apply to connection for later use attachments: list of dicts in form of {"id": ..., "type": ...} Returns: Invitation record """ mediation_mgr = MediationManager(self._session.profile) mediation_record = await mediation_record_if_id( self._session, mediation_id, or_default=True, ) keylist_updates = None if not (hs_protos or attachments): raise OutOfBandManagerError( "Invitation must include handshake protocols, " "request attachments, or both") wallet = self._session.inject(BaseWallet) # Multitenancy setup multitenant_mgr = self._session.inject(MultitenantManager, required=False) wallet_id = self._session.settings.get("wallet.id") accept = bool( auto_accept or (auto_accept is None and self._session.settings.get("debug.auto_accept_requests"))) if public: if multi_use: raise OutOfBandManagerError( "Cannot create public invitation with multi_use") if metadata: raise OutOfBandManagerError( "Cannot store metadata on public invitations") message_attachments = [] for atch in attachments or []: a_type = atch.get("type") a_id = atch.get("id") if a_type == "credential-offer": try: cred_ex_rec = await V10CredentialExchange.retrieve_by_id( self._session, a_id, ) message_attachments.append( InvitationMessage.wrap_message( cred_ex_rec.credential_offer_dict)) except StorageNotFoundError: cred_ex_rec = await V20CredExRecord.retrieve_by_id( self._session, a_id, ) message_attachments.append( InvitationMessage.wrap_message( V20CredOffer.deserialize( cred_ex_rec.cred_offer).offer())) elif a_type == "present-proof": try: pres_ex_rec = await V10PresentationExchange.retrieve_by_id( self._session, a_id, ) message_attachments.append( InvitationMessage.wrap_message( pres_ex_rec.presentation_request_dict)) except StorageNotFoundError: pres_ex_rec = await V20PresExRecord.retrieve_by_id( self._session, a_id, ) message_attachments.append( InvitationMessage.wrap_message( V20PresRequest.deserialize( pres_ex_rec.pres_request).attachment())) else: raise OutOfBandManagerError( f"Unknown attachment type: {a_type}") handshake_protocols = [ DIDCommPrefix.qualify_current(hsp.name) for hsp in hs_protos or [] ] or None if public: if not self._session.settings.get("public_invites"): raise OutOfBandManagerError( "Public invitations are not enabled") public_did = await wallet.get_public_did() if not public_did: raise OutOfBandManagerError( "Cannot create public invitation with no public DID") invi_msg = InvitationMessage( # create invitation message label=my_label or self._session.settings.get("default_label"), handshake_protocols=handshake_protocols, request_attach=message_attachments, service=[f"did:sov:{public_did.did}"], ) keylist_updates = await mediation_mgr.add_key( public_did.verkey, keylist_updates) ledger = self._session.inject(BaseLedger) try: async with ledger: base_url = await ledger.get_endpoint_for_did(public_did.did ) invi_url = invi_msg.to_url(base_url) except LedgerError as ledger_x: raise OutOfBandManagerError( "Error getting endpoint for public DID " f"{public_did.did}: {ledger_x}") conn_rec = ConnRecord( # create connection record invitation_key=public_did.verkey, invitation_msg_id=invi_msg._id, their_role=ConnRecord.Role.REQUESTER.rfc23, state=ConnRecord.State.INVITATION.rfc23, accept=ConnRecord.ACCEPT_AUTO if accept else ConnRecord.ACCEPT_MANUAL, alias=alias, ) await conn_rec.save(self._session, reason="Created new invitation") await conn_rec.attach_invitation(self._session, invi_msg) if multitenant_mgr and wallet_id: # add mapping for multitenant relay await multitenant_mgr.add_key(wallet_id, public_did.verkey, skip_if_exists=True) else: invitation_mode = (ConnRecord.INVITATION_MODE_MULTI if multi_use else ConnRecord.INVITATION_MODE_ONCE) if not my_endpoint: my_endpoint = self._session.settings.get("default_endpoint") # Create and store new invitation key connection_key = await wallet.create_signing_key() keylist_updates = await mediation_mgr.add_key( connection_key.verkey, keylist_updates) # Add mapping for multitenant relay if multitenant_mgr and wallet_id: await multitenant_mgr.add_key(wallet_id, connection_key.verkey) # Create connection record conn_rec = ConnRecord( invitation_key=connection_key.verkey, their_role=ConnRecord.Role.REQUESTER.rfc23, state=ConnRecord.State.INVITATION.rfc23, accept=ConnRecord.ACCEPT_AUTO if accept else ConnRecord.ACCEPT_MANUAL, invitation_mode=invitation_mode, alias=alias, ) await conn_rec.save(self._session, reason="Created new connection") routing_keys = [] # The base wallet can act as a mediator for all tenants if multitenant_mgr and wallet_id: base_mediation_record = await multitenant_mgr.get_default_mediator( ) if base_mediation_record: routing_keys = base_mediation_record.routing_keys my_endpoint = base_mediation_record.endpoint # If we use a mediator for the base wallet we don't # need to register the key at the subwallet mediator # because it only needs to know the key of the base mediator # sub wallet mediator -> base wallet mediator -> agent keylist_updates = None if mediation_record: routing_keys = [*routing_keys, *mediation_record.routing_keys] my_endpoint = mediation_record.endpoint # Save that this invitation was created with mediation await conn_rec.metadata_set(self._session, "mediation", {"id": mediation_id}) if keylist_updates: responder = self._session.inject(BaseResponder, required=False) await responder.send( keylist_updates, connection_id=mediation_record.connection_id) routing_keys = [ key if len(key.split(":")) == 3 else naked_to_did_key(key) for key in routing_keys ] # Create connection invitation message # Note: Need to split this into two stages to support inbound routing # of invitations # Would want to reuse create_did_document and convert the result invi_msg = InvitationMessage( label=my_label or self._session.settings.get("default_label"), handshake_protocols=handshake_protocols, request_attach=message_attachments, service=[ ServiceMessage( _id="#inline", _type="did-communication", recipient_keys=[ naked_to_did_key(connection_key.verkey) ], service_endpoint=my_endpoint, routing_keys=routing_keys, ) ], ) invi_url = invi_msg.to_url() # Update connection record conn_rec.invitation_msg_id = invi_msg._id await conn_rec.save(self._session, reason="Added Invitation") await conn_rec.attach_invitation(self._session, invi_msg) if metadata: for key, value in metadata.items(): await conn_rec.metadata_set(self._session, key, value) return InvitationRecord( # for return via admin API, not storage state=InvitationRecord.STATE_INITIAL, invi_msg_id=invi_msg._id, invitation=invi_msg.serialize(), invitation_url=invi_url, )