async def handle(self, context: RequestContext, responder: BaseResponder): """Handle create invitation request.""" connection_mgr = ConnectionManager(context) connection, invitation = await connection_mgr.create_invitation( my_label=context.message.label, their_role=context.message.role, auto_accept=context.message.auto_accept, multi_use=bool(context.message.multi_use), public=False, alias=context.message.alias, ) invite_response = Invitation( id=connection.connection_id, label=invitation.label, alias=connection.alias, role=connection.their_role, auto_accept=connection.accept == ConnectionRecord.ACCEPT_AUTO, multi_use=( connection.invitation_mode == ConnectionRecord.INVITATION_MODE_MULTI ), invitation_url=invitation.to_url(), created_date=connection.created_at, raw_repr={ 'connection': connection.serialize(), 'invitation': invitation.serialize(), } ) invite_response.assign_thread_from(context.message) await responder.send_reply(invite_response)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle recieve invitation request.""" connection_mgr = ConnectionManager(context) invitation = ConnectionInvitation.from_url(context.message.invitation) connection = await connection_mgr.receive_invitation( invitation, auto_accept=context.message.auto_accept) connection_resp = Connection(**conn_record_to_message_repr(connection)) await responder.send_reply(connection_resp)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle static connection get list request.""" session = await context.session() connection_mgr = ConnectionManager(session) wallet: BaseWallet = session.inject(BaseWallet) try: tag_filter = dict( filter( lambda item: item[1] is not None, { 'my_did': context.message.my_did, 'their_did': context.message.their_did, }.items())) post_filter_positive = dict( filter( lambda item: item[1] is not None, { 'initiator': context.message.initiator, 'invitation_key': context.message.invitation_key, 'invitation_mode': ConnRecord.INVITATION_MODE_STATIC, 'their_role': context.message.their_role }.items())) records = await ConnRecord.query( session, tag_filter, post_filter_positive=post_filter_positive) except StorageNotFoundError: report = ProblemReport(explain_ltxt='Connection not found.', who_retries='none') report.assign_thread_from(context.message) await responder.send_reply(report) return def flatten_target(connection, target, my_info): """Map for flattening results.""" return { 'connection_id': connection.connection_id, 'their_info': { 'label': target.label, 'did': target.did, 'vk': target.recipient_keys[0], 'endpoint': target.endpoint }, 'my_info': { 'did': my_info.did, 'vk': my_info.verkey, 'endpoint': context.settings.get("default_endpoint") } } targets = [] my_info = [] for record in records: targets.extend( await connection_mgr.get_connection_targets(connection=record)) my_info.append(await wallet.get_local_did(record.my_did)) results = list(map(flatten_target, records, targets, my_info)) static_connections = StaticConnectionList(results=results) static_connections.assign_thread_from(context.message) await responder.send_reply(static_connections)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle received basic message.""" session = await context.session() msg = BasicMessageRecord( connection_id=context.connection_record.connection_id, message_id=context.message._id, sent_time=context.message.sent_time, content=context.message.content, state=BasicMessageRecord.STATE_RECV) await msg.save(session, reason='New message received.') await responder.send_webhook( "basicmessages", { "connection_id": context.connection_record.connection_id, "message_id": context.message._id, "content": context.message.content, "state": "received", }, ) session = await context.session() connection_mgr = ConnectionManager(session) storage = session.inject(BaseStorage) admin_ids = map( lambda record: record.tags['connection_id'], filter( lambda record: json.loads(record.value) == 'admin', await storage.find_all_records(ConnRecord.RECORD_TYPE_METADATA, {'key': 'group'}))) admins = [ await ConnRecord.retrieve_by_id(session, id) for id in admin_ids ] if not admins: return admins = filter(lambda admin: admin.state == 'active', admins) admin_verkeys = [ target.recipient_keys[0] for admin in admins for target in await connection_mgr.get_connection_targets( connection=admin) ] notification = New( connection_id=context.connection_record.connection_id, message=msg) for verkey in admin_verkeys: await responder.send(notification, reply_to_verkey=verkey, to_session_only=True)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle static connection creation request.""" session = await context.session() connection_mgr = ConnectionManager(session) wallet: BaseWallet = session.inject(BaseWallet) # Make our info for the connection my_info = await wallet.create_local_did() # Create connection record connection = ConnRecord( initiator=ConnRecord.INITIATOR_SELF, my_did=my_info.did, their_did=context.message.static_did, their_label=context.message.label, their_role=context.message.role if context.message.role else None, state=ConnRecord.STATE_ACTIVE, invitation_mode=ConnRecord.INVITATION_MODE_STATIC) # Construct their did doc from the basic components in message diddoc = DIDDoc(context.message.static_did) public_key = PublicKey(did=context.message.static_did, ident="1", value=context.message.static_key, pk_type=PublicKeyType.ED25519_SIG_2018, controller=context.message.static_did) service = Service(did=context.message.static_did, ident="indy", typ="IndyAgent", recip_keys=[public_key], routing_keys=[], endpoint=context.message.static_endpoint) diddoc.set(public_key) diddoc.set(service) # Save await connection_mgr.store_did_document(diddoc) await connection.save(session, reason='Created new static connection') # Prepare response info = StaticConnectionInfo( did=my_info.did, key=my_info.verkey, endpoint=context.settings.get("default_endpoint")) info.assign_thread_from(context.message) await responder.send_reply(info) return
async def handle(self, context: RequestContext, responder: BaseResponder): """Message handler implementation.""" self._logger.debug("InvitationRequestHandler called with context %s", context) assert isinstance(context.message, InvitationRequest) if not context.connection_ready: raise HandlerException( "No connection established for invitation request message") # Need a way to prompt the user for acceptance? if context.settings.get("accept_requests"): # Create a new connection invitation and send it back in an Invitation connection_mgr = ConnectionManager(context) _connection, invite = await connection_mgr.create_invitation() response = Invitation(invitation=invite) response.assign_thread_from(context.message) response.assign_trace_from(context.message) await responder.send_reply(response)
async def handle(self, context: RequestContext, responder: BaseResponder): """Message handler implementation.""" self._logger.debug("ForwardInvitationHandler called with context %s", context) assert isinstance(context.message, ForwardInvitation) if not context.connection_ready: raise HandlerException( "No connection established for forward invitation message") # Store invitation connection_mgr = ConnectionManager(context) connection = await connection_mgr.receive_invitation( context.message.invitation, their_role=None) # Auto-accept if context.settings.get("accept_invites"): request = await connection_mgr.create_request(connection) await responder.send(request, connection_id=connection.connection_id)
async def handle(self, context: RequestContext, responder: BaseResponder): """Handle received basic message.""" msg = BasicMessageRecord( connection_id=context.connection_record.connection_id, message_id=context.message._id, sent_time=context.message.sent_time, content=context.message.content, state=BasicMessageRecord.STATE_RECV) await msg.save(context, reason='New message received.') await responder.send_webhook( "basicmessages", { "connection_id": context.connection_record.connection_id, "message_id": context.message._id, "content": context.message.content, "state": "received", }, ) connection_mgr = ConnectionManager(context) admins = await ConnectionRecord.query( context, post_filter_positive={'their_role': 'admin'}) if not admins: return admins = filter(lambda admin: admin.state == 'active', admins) admin_verkeys = [ target.recipient_keys[0] for admin in admins for target in await connection_mgr.get_connection_targets( connection=admin) ] notification = New( connection_id=context.connection_record.connection_id, message=msg) for verkey in admin_verkeys: await responder.send(notification, reply_to_verkey=verkey, to_session_only=True)
async def handle(self, context: RequestContext, responder: BaseResponder): """Message handler implementation.""" self._logger.debug( "%s called with context %s", self.__class__.__name__, context ) assert isinstance(context.message, RouteUpdateResponse) if not context.connection_ready: raise HandlerException("Cannot handle updated routes: no active connection") conn_mgr = ConnectionManager(context) router_id = context.connection_record.connection_id for update in context.message.updated: if update.action == RouteUpdate.ACTION_CREATE: if update.result in ( RouteUpdated.RESULT_NO_CHANGE, RouteUpdated.RESULT_SUCCESS, ): routing_state = ConnectionRecord.ROUTING_STATE_ACTIVE else: routing_state = ConnectionRecord.ROUTING_STATE_ERROR self._logger.warning( f"Unexpected result from inbound route update ({update.action})" ) await conn_mgr.update_inbound( router_id, update.recipient_key, routing_state ) elif update.action == RouteUpdate.ACTION_DELETE: self._logger.info( "Inbound route deletion status: {}, {}".format( update.recipient_key, update.result ) ) else: self._logger.error(f"Unsupported inbound route action: {update.action}")
async def create_invitation( self, my_label: str = None, my_endpoint: str = None, use_public_did: bool = False, include_handshake: bool = False, multi_use: bool = False, attachments: list = None, ) -> InvitationModel: """ Generate new out of band invitation. This interaction represents an out-of-band communication channel. The resulting message is expected to be used to bootstrap the secure peer-to-peer communication channel. Args: my_label: label for this connection my_endpoint: endpoint where other party can reach me public: set to create an invitation from the public DID multi_use: set to True to create an invitation for multiple use attachments: list of dicts in the form of {"id": "jh5k23j5gh2123", "type": "credential-offer"} Returns: A tuple of the new `InvitationModel` and out of band `InvitationMessage` instances """ connection_mgr = ConnectionManager(self.context) connection, connection_invitation = await connection_mgr.create_invitation( auto_accept=True, public=use_public_did, multi_use=multi_use) # wallet: BaseWallet = await self.context.inject(BaseWallet) if not my_label: my_label = self.context.settings.get("default_label") # if not my_endpoint: # my_endpoint = self.context.settings.get("default_endpoint") message_attachments = [] if attachments: for attachment in attachments: if attachment["type"] == "credential-offer": instance_id = attachment["id"] model = await V10CredentialExchange.retrieve_by_id( self.context, instance_id) # Wrap as attachment decorators message_attachments.append( InvitationMessage.wrap_message( model.credential_offer_dict)) elif attachment["type"] == "present-proof": instance_id = attachment["id"] model = await V10PresentationExchange.retrieve_by_id( self.context, instance_id) # Wrap as attachment decorators message_attachments.append( InvitationMessage.wrap_message( model.presentation_request_dict)) else: raise OutOfBandManagerError( f"Unknown attachment type: {attachment['type']}") # We plug into existing connection structure during migration phase if use_public_did: # service = (await wallet.get_public_did()).did service = connection_invitation.did else: # connection_key = await wallet.create_signing_key() # service = ServiceMessage( # id="#inline", # type="did-communication", # recipient_keys=[connection_key.verkey], # routing_keys=[], # service_endpoint=my_endpoint, # ) service = ServiceMessage( _id="#inline", _type="did-communication", recipient_keys=connection_invitation.recipient_keys, routing_keys=connection_invitation.routing_keys, service_endpoint=connection_invitation.endpoint, ).validate() handshake_protocols = [] if include_handshake: # handshake_protocols.append("https://didcomm.org/connections/1.0") # handshake_protocols.append("https://didcomm.org/didexchange/1.0") handshake_protocols.append( "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation" ) invitation_message = InvitationMessage( label=my_label, service=[service], request_attach=message_attachments, handshake_protocols=handshake_protocols, ).validate() # Create record invitation_model = InvitationModel( state=InvitationModel.STATE_INITIAL, invitation=invitation_message.serialize(), ) await invitation_model.save(self.context, reason="Created new invitation") return invitation_model
async def receive_invitation( self, invitation: InvitationMessage) -> ConnectionRecord: """Receive an out of band invitation message.""" ledger: BaseLedger = await self.context.inject(BaseLedger) # New message format invitation_message = InvitationMessage.deserialize(invitation) # Convert to old format and pass to relevant manager # The following logic adheres to Aries RFC 0496 # There must be exactly 1 service entry if (len(invitation_message.service_blocks) + len(invitation_message.service_dids) != 1): raise OutOfBandManagerError( "service array must have exactly one element") # Get the single service item if invitation_message.service_blocks: service = invitation_message.service_blocks[0] else: # If it's in the did format, we need to convert to a full service block service_did = invitation_message.service_dids[0] async with ledger: verkey = await ledger.get_key_for_did(service_did) endpoint = await ledger.get_endpoint_for_did(service_did) service = ServiceMessage.deserialize({ "id": "#inline", "type": "did-communication", "recipientKeys": [verkey], "routingKeys": [], "serviceEndpoint": endpoint, }) # If we are dealing with an invitation if (len(invitation_message.handshake_protocols) == 1 and invitation_message.handshake_protocols[0] == "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation" ): if len(invitation_message.request_attach) != 0: raise OutOfBandManagerError( "request block must be empty for invitation message type.") # Convert to the old message format connection_invitation = ConnectionInvitation.deserialize({ "@id": invitation_message._id, "@type": invitation_message.handshake_protocols[0], "label": invitation_message.label, "recipientKeys": service.recipient_keys, "serviceEndpoint": service.service_endpoint, "routingKeys": service.routing_keys, }) connection_mgr = ConnectionManager(self.context) connection = await connection_mgr.receive_invitation( connection_invitation, auto_accept=True) elif (len(invitation_message.request_attach) == 1 and invitation_message.request_attach[0].data.json["@type"] == "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec" + "/present-proof/1.0/request-presentation"): raise OutOfBandManagerNotImplementedError( "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec" + "/present-proof/1.0/request-presentation " + "request type not implemented.") else: raise OutOfBandManagerError("Invalid request type") return connection