private void UpdateStateOnMessage(TestHarnessCredentialExchange testHarnessCredentialExchange, TestHarnessCredentialExchangeState nextState, Func <ServiceMessageProcessingEvent, bool> predicate) { _eventAggregator.GetEventByType <ServiceMessageProcessingEvent>() .Where(predicate) .Take(1) .Subscribe(_ => { testHarnessCredentialExchange.State = nextState; }); }
private void UpdateStateOnMessage(TestHarnessCredentialExchange testHarnessCredentialExchange, TestHarnessCredentialExchangeState nextState, Func <ServiceMessageProcessingEvent, bool> predicate) { System.Console.WriteLine($"Waiting to update state to ${nextState}, on credential {testHarnessCredentialExchange.ThreadId}, from state: {testHarnessCredentialExchange.State} "); _eventAggregator.GetEventByType <ServiceMessageProcessingEvent>() .Where(predicate) .Take(1) .Subscribe(_ => { System.Console.WriteLine($"Updated state to ${nextState}, on credential {testHarnessCredentialExchange.ThreadId}, from state: {testHarnessCredentialExchange.State} "); testHarnessCredentialExchange.State = nextState; }); }
public async Task <IActionResult> SendCredentialProposalAsync(OperationBody body) { var context = await _agentContextProvider.GetContextAsync(); var proposal = body.Data; var connectionId = (string)proposal["connection_id"]; var credentialPreview = proposal["credential_proposal"].ToObject <CustomCredentialPreviewMessage>(); var credentialDefinitionId = (string)proposal["cred_def_id"]; var schemaId = (string)proposal["schema_id"]; var connection = await _connectionService.GetAsync(context, connectionId); // TODO: Handle AriesFrameworkException if connection not found var threadId = Guid.NewGuid().ToString(); credentialPreview.Attributes = CleanCredentialPreviewAttributes(credentialPreview.Attributes); // TODO: add support for v1.1 wich includes 'schema_issuer_did', 'schema_name', 'schema_version', 'issuer_did' // TODO: should we support in response to previous message? Not possible with AATH atm I think. var credentialProposeMessage = new CustomCredentialProposeMessage { Id = threadId, Comment = (string)proposal["comment"] ?? "hello", // ACA-Py requires comment CredentialProposal = credentialPreview, CredentialDefinitionId = credentialDefinitionId, SchemaId = schemaId }; var credentialRecord = new CredentialRecord { Id = Guid.NewGuid().ToString(), ConnectionId = connectionId, CredentialAttributesValues = credentialPreview.Attributes, CredentialDefinitionId = credentialDefinitionId, SchemaId = schemaId, // State should be proposal-received State = CredentialState.Offered }; credentialRecord.SetTag(TagConstants.Role, TagConstants.Holder); credentialRecord.SetTag(TagConstants.LastThreadId, threadId); await _recordService.AddAsync(context.Wallet, credentialRecord); var THCredentialExchange = new TestHarnessCredentialExchange { RecordId = credentialRecord.Id, ThreadId = threadId, State = TestHarnessCredentialExchangeState.ProposalSent, }; _credentialCache.Set(THCredentialExchange.ThreadId, THCredentialExchange); // Listen for credential offer to update the state UpdateStateOnMessage(THCredentialExchange, TestHarnessCredentialExchangeState.OfferReceived, _ => _.MessageType == MessageTypes.IssueCredentialNames.OfferCredential && _.ThreadId == THCredentialExchange.ThreadId); await _messageService.SendAsync(context.Wallet, credentialProposeMessage, connection); return(Ok(THCredentialExchange)); }
/// <summary> /// Processes the agent message /// </summary> /// <param name="agentContext"></param> /// <param name="messageContext">The agent message.</param> /// <returns></returns> /// <exception cref="AriesFrameworkException">Unsupported message type {messageType}</exception> public async Task <AgentMessage> ProcessAsync(IAgentContext agentContext, UnpackedMessageContext messageContext) { switch (messageContext.GetMessageType()) { case MessageTypes.IssueCredentialNames.ProposeCredential: case MessageTypesHttps.IssueCredentialNames.ProposeCredential: { var credentialProposal = messageContext.GetMessage <CustomCredentialProposeMessage>(); CredentialRecord credentialRecord; TestHarnessCredentialExchange THCredentialExchange; try { // Credential can be proposed for an existing exchange // so we must first check if the message contains THCredentialExchange = _credentialCache.Get <TestHarnessCredentialExchange>(credentialProposal.GetThreadId()); credentialRecord = await _credentialService.GetByThreadIdAsync(agentContext, THCredentialExchange.ThreadId); // check if the proposal came from the same connection if (messageContext.Connection?.Id != credentialRecord.ConnectionId) { throw new AriesFrameworkException(ErrorCode.RecordInInvalidState, "Connection from credential proposal is not same as previously stored record."); } } catch { // Create new CredentialRecord if no existing credential record exists credentialRecord = new CredentialRecord { Id = Guid.NewGuid().ToString(), ConnectionId = messageContext.Connection?.Id, }; credentialRecord.SetTag(TagConstants.Role, TagConstants.Issuer); credentialRecord.SetTag(TagConstants.LastThreadId, credentialProposal.GetThreadId()); await _recordService.AddAsync(agentContext.Wallet, credentialRecord); THCredentialExchange = new TestHarnessCredentialExchange { RecordId = credentialRecord.Id, ThreadId = credentialProposal.GetThreadId(), State = TestHarnessCredentialExchangeState.ProposalReceived, }; _credentialCache.Set(THCredentialExchange.ThreadId, THCredentialExchange); } // Updates that should be applied both when the credential record already exists or not credentialRecord.CredentialDefinitionId = credentialProposal.CredentialDefinitionId; credentialRecord.SchemaId = credentialProposal.SchemaId; credentialRecord.CredentialAttributesValues = credentialProposal.CredentialProposal?.Attributes .Select(x => new CredentialPreviewAttribute { Name = x.Name, MimeType = x.MimeType, Value = x.Value }).ToArray(); // State should be proposal-received credentialRecord.State = CredentialState.Offered; await _recordService.UpdateAsync(agentContext.Wallet, credentialRecord); _eventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credentialProposal.Type, ThreadId = credentialProposal.GetThreadId() }); messageContext.ContextRecord = credentialRecord; return(null); } case MessageTypes.IssueCredentialNames.OfferCredential: case MessageTypesHttps.IssueCredentialNames.OfferCredential: { var offer = messageContext.GetMessage <CredentialOfferMessage>(); var recordId = await this.ProcessOfferAsync( agentContext, offer, messageContext.Connection); messageContext.ContextRecord = await _credentialService.GetAsync(agentContext, recordId); return(null); } case MessageTypes.IssueCredentialNames.RequestCredential: case MessageTypesHttps.IssueCredentialNames.RequestCredential: { var request = messageContext.GetMessage <CredentialRequestMessage>(); var recordId = await _credentialService.ProcessCredentialRequestAsync( agentContext : agentContext, credentialRequest : request, connection : messageContext.Connection); if (request.ReturnRoutingRequested() && messageContext.Connection == null) { var(message, record) = await _credentialService.CreateCredentialAsync(agentContext, recordId); messageContext.ContextRecord = record; return(message); } else { messageContext.ContextRecord = await _credentialService.GetAsync(agentContext, recordId); return(null); } } case MessageTypes.IssueCredentialNames.IssueCredential: case MessageTypesHttps.IssueCredentialNames.IssueCredential: { var credential = messageContext.GetMessage <CredentialIssueMessage>(); var recordId = await _credentialService.ProcessCredentialAsync( agentContext, credential, messageContext.Connection); messageContext.ContextRecord = await UpdateValuesAsync( credentialId : recordId, credentialIssue : messageContext.GetMessage <CredentialIssueMessage>(), agentContext : agentContext); return(null); } default: throw new AriesFrameworkException(ErrorCode.InvalidMessage, $"Unsupported message type {messageContext.GetMessageType()}"); } }
/// <summary> /// Process an offer, taking previous proposals into account. The framework doesn't fully /// support credential proposals, and I don't know how to override one service function /// so for now we just use a custom handler and this function. /// </summary> /// <param name="agentContext"></param> /// <param name="credentialOffer"></param> /// <param name="connection"></param> /// <returns></returns> private async Task <string> ProcessOfferAsync(IAgentContext agentContext, CredentialOfferMessage credentialOffer, ConnectionRecord connection) { var offerAttachment = credentialOffer.Offers.FirstOrDefault(x => x.Id == "libindy-cred-offer-0") ?? throw new ArgumentNullException(nameof(CredentialOfferMessage.Offers)); var offerJson = offerAttachment.Data.Base64.GetBytesFromBase64().GetUTF8String(); var offer = JObject.Parse(offerJson); var definitionId = offer["cred_def_id"].ToObject <string>(); var schemaId = offer["schema_id"].ToObject <string>(); // check if credential already exists CredentialRecord credentialRecord; try { credentialRecord = await _credentialService.GetByThreadIdAsync(agentContext, credentialOffer.GetThreadId()); if (credentialRecord.ConnectionId != connection.Id) { throw new AriesFrameworkException(ErrorCode.InvalidRecordData, "Connection from credential offer is not same as previously stored record."); } } catch { // Write offer record to local wallet credentialRecord = new CredentialRecord { Id = Guid.NewGuid().ToString(), ConnectionId = connection?.Id, }; credentialRecord.SetTag(TagConstants.Role, TagConstants.Holder); credentialRecord.SetTag(TagConstants.LastThreadId, credentialOffer.GetThreadId()); await _recordService.AddAsync(agentContext.Wallet, credentialRecord); var THCredentialExchange = new TestHarnessCredentialExchange { RecordId = credentialRecord.Id, ThreadId = credentialOffer.GetThreadId(), State = TestHarnessCredentialExchangeState.OfferReceived }; _credentialCache.Set(THCredentialExchange.ThreadId, THCredentialExchange); } credentialRecord.OfferJson = offerJson; credentialRecord.CredentialDefinitionId = definitionId; credentialRecord.SchemaId = schemaId; credentialRecord.CredentialAttributesValues = credentialOffer.CredentialPreview?.Attributes .Select(x => new CredentialPreviewAttribute { Name = x.Name, MimeType = x.MimeType, Value = x.Value }).ToArray(); credentialRecord.State = CredentialState.Offered; await _recordService.UpdateAsync(agentContext.Wallet, credentialRecord); _eventAggregator.Publish(new ServiceMessageProcessingEvent { RecordId = credentialRecord.Id, MessageType = credentialOffer.Type, ThreadId = credentialOffer.GetThreadId() }); return(credentialRecord.Id); }
public async Task <IActionResult> SendCredentialOfferAsync(OperationBody body) { var context = await _agentContextProvider.GetContextAsync(); var issuer = await _provisionService.GetProvisioningAsync(context.Wallet); var threadId = body.Id; string connectionId; TestHarnessCredentialExchange THCredentialExchange; CredentialOfferMessage credentialOffer; CredentialRecord credentialRecord; // Adding a delay here. It seems that with the removal of the state checks in the tests, // Some agents are not yet in the appropriate state for this call. // TODO There may be a better way to do this but adding the wait is a quick fix to get the tests // running again. await Task.Delay(5000); // Send offer in response to proposal if (threadId != null) { THCredentialExchange = _credentialCache.Get <TestHarnessCredentialExchange>(threadId); credentialRecord = await _credentialService.GetAsync(context, THCredentialExchange.RecordId); connectionId = credentialRecord.ConnectionId; var offerJson = await AnonCreds.IssuerCreateCredentialOfferAsync(context.Wallet, credentialRecord.CredentialDefinitionId); var offerJobj = JObject.Parse(offerJson); var schemaId = offerJobj["schema_id"].ToObject <string>(); // Update credential record credentialRecord.SchemaId = schemaId; credentialRecord.OfferJson = offerJson; credentialRecord.State = CredentialState.Offered; credentialRecord.SetTag(TagConstants.IssuerDid, issuer.IssuerDid); await _recordService.UpdateAsync(context.Wallet, credentialRecord); credentialOffer = new CredentialOfferMessage { CredentialPreview = new CredentialPreviewMessage { Attributes = CleanCredentialPreviewAttributes(credentialRecord.CredentialAttributesValues.ToArray()) }, Comment = "credential-offer", // ACA-Py requires comment Offers = new Attachment[] { new Attachment { Id = "libindy-cred-offer-0", MimeType = CredentialMimeTypes.ApplicationJsonMimeType, Data = new AttachmentContent { Base64 = offerJson.GetUTF8Bytes().ToBase64String() } } } }; credentialOffer.ThreadFrom(threadId); THCredentialExchange.State = TestHarnessCredentialExchangeState.OfferSent; } // Send Offer to start credential issuance flow else { var offer = body.Data; connectionId = (string)offer["connection_id"]; var credentialPreview = offer["credential_preview"].ToObject <CustomCredentialPreviewMessage>(); var credentialDefinitionId = (string)offer["cred_def_id"]; credentialPreview.Attributes = CleanCredentialPreviewAttributes(credentialPreview.Attributes); (credentialOffer, credentialRecord) = await _credentialService.CreateOfferAsync(context, new OfferConfiguration { CredentialAttributeValues = credentialPreview.Attributes, CredentialDefinitionId = credentialDefinitionId, IssuerDid = issuer.IssuerDid }, connectionId); THCredentialExchange = new TestHarnessCredentialExchange { RecordId = credentialRecord.Id, ThreadId = credentialRecord.GetTag(TagConstants.LastThreadId), State = TestHarnessCredentialExchangeState.OfferSent }; _credentialCache.Set(THCredentialExchange.ThreadId, THCredentialExchange); } var connection = await _connectionService.GetAsync(context, connectionId); // Listen for credential request to update the state UpdateStateOnMessage(THCredentialExchange, TestHarnessCredentialExchangeState.RequestReceived, _ => new[] { MessageTypes.IssueCredentialNames.RequestCredential, MessageTypesHttps.IssueCredentialNames.RequestCredential, }.Contains(_.MessageType) && _.ThreadId == THCredentialExchange.ThreadId); await _messageService.SendAsync(context, credentialOffer, connection); return(Ok(THCredentialExchange)); }