private void UpdateStateOnMessage(TestHarnessCredentialExchange testHarnessCredentialExchange, TestHarnessCredentialExchangeState nextState, Func <ServiceMessageProcessingEvent, bool> predicate)
 {
     _eventAggregator.GetEventByType <ServiceMessageProcessingEvent>()
     .Where(predicate)
     .Take(1)
     .Subscribe(_ => { testHarnessCredentialExchange.State = nextState; });
 }
Exemple #2
0
 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; });
 }
Exemple #3
0
        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));
        }
Exemple #4
0
        /// <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()}");
            }
        }
Exemple #5
0
        /// <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));
        }