Skip to content

Commit

Permalink
feat: command controllers for wallet issuance flow
Browse files Browse the repository at this point in the history
- added JS, REST command controllers for wallet issuance interfaces.
- Part of  hyperledger-archives#3073

Signed-off-by: sudesh.shetty <sudesh.shetty@securekey.com>
  • Loading branch information
sudeshrshetty committed Nov 27, 2021
1 parent 3a97c2b commit 4fc3101
Show file tree
Hide file tree
Showing 11 changed files with 1,082 additions and 16 deletions.
8 changes: 8 additions & 0 deletions cmd/aries-js-worker/src/agent-rest-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,14 @@ const pkgs = {
path: "/vcwallet/present-proof",
method: "POST",
},
ProposeCredential: {
path: "/vcwallet/propose-credential",
method: "POST",
},
RequestCredential: {
path: "/vcwallet/request-credential",
method: "POST",
},
},
ld: {
AddContexts: {
Expand Down
24 changes: 24 additions & 0 deletions cmd/aries-js-worker/src/aries.js
Original file line number Diff line number Diff line change
Expand Up @@ -1406,6 +1406,30 @@ const Aries = function (opts) {
presentProof: async function (req) {
return invoke(aw, pending, this.pkgname, "PresentProof", req, "timeout while performing present proof from wallet")
},

/**
*
* accepts out-of-band invitation, sends propose credential message from wallet to issuer and optionally waits for offer credential response.
*
* Returns offer credential message response.
*
* @returns {Promise<Object>}
*/
proposeCredential: async function (req) {
return invoke(aw, pending, this.pkgname, "ProposeCredential", req, "timeout while proposing credential from wallet")
},

/**
*
* sends request credential message from wallet to issuer and optionally waits for credential fulfillment.
*
* Returns credential fulfillment and web redirect info.
*
* @returns {Promise<Object>}
*/
requestCredential: async function (req) {
return invoke(aw, pending, this.pkgname, "RequestCredential", req, "timeout while performing request credential from wallet")
},
},
/**
* JSON-LD management API.
Expand Down
47 changes: 47 additions & 0 deletions pkg/client/vcwallet/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,3 +419,50 @@ func (c *Client) PresentProof(thID string, presentProofFrom ...wallet.ConcludeIn

return c.wallet.PresentProof(auth, thID, presentProofFrom...)
}

// ProposeCredential sends propose credential message from wallet to issuer.
// https://w3c-ccg.github.io/universal-wallet-interop-spec/#requestcredential
//
// Currently Supporting : 0453-issueCredentialV2
// https://github.com/hyperledger/aries-rfcs/blob/main/features/0453-issue-credential-v2/README.md
//
// Args:
// - invitation: out-of-band invitation from issuer.
// - options: options for accepting invitation and send propose credential message.
//
// Returns:
// - DIDCommMsgMap containing offer credential message if operation is successful.
// - error if operation fails.
//
func (c *Client) ProposeCredential(invitation *outofband.Invitation, options ...wallet.InitiateInteractionOption) (*service.DIDCommMsgMap, error) { // nolint: lll
auth, err := c.auth()
if err != nil {
return nil, err
}

return c.wallet.ProposeCredential(auth, invitation, options...)
}

// RequestCredential sends request credential message from wallet to issuer and
// optionally waits for credential fulfillment.
// https://w3c-ccg.github.io/universal-wallet-interop-spec/#proposecredential
//
// Currently Supporting : 0453-issueCredentialV2
// https://github.com/hyperledger/aries-rfcs/blob/main/features/0453-issue-credential-v2/README.md
//
// Args:
// - thID: thread ID (action ID) of offer credential message previously received.
// - concludeInteractionOptions: options to conclude interaction like presentation to be shared etc.
//
// Returns:
// - RequestCredentialStatus containing status, redirectURL, credential fullfillment attachments.
// - error if operation fails.
//
func (c *Client) RequestCredential(thID string, options ...wallet.ConcludeInteractionOptions) (*wallet.RequestCredentialStatus, error) { // nolint: lll
auth, err := c.auth()
if err != nil {
return nil, err
}

return c.wallet.RequestCredential(auth, thID, options...)
}
212 changes: 212 additions & 0 deletions pkg/client/vcwallet/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/model"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/didexchange"
issuecredentialsvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/issuecredential"
"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator"
Expand Down Expand Up @@ -348,6 +349,7 @@ const (
webRedirectStatusKey = "status"
webRedirectURLKey = "url"
exampleWebRedirect = "http://example.com/sample"
sampleMsgComment = "sample mock msg"
)

func TestCreateProfile(t *testing.T) {
Expand Down Expand Up @@ -1804,6 +1806,216 @@ func TestClient_PresentProof(t *testing.T) {
})
}

func TestClient_ProposeCredential(t *testing.T) {
sampleUser := uuid.New().String()
mockctx := newMockProvider(t)

err := CreateProfile(sampleUser, mockctx, wallet.WithPassphrase(samplePassPhrase))
require.NoError(t, err)

const (
myDID = "did:mydid:123"
theirDID = "did:theirdid:123"
)

t.Run("test propose credential success", func(t *testing.T) {
sampleConnID := uuid.New().String()

oobSvc := &mockoutofband.MockOobService{
AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) {
return sampleConnID, nil
},
}
mockctx.ServiceMap[outofbandSvc.Name] = oobSvc

didexSvc := &mockdidexchange.MockDIDExchangeSvc{
RegisterMsgEventHandle: func(ch chan<- service.StateMsg) error {
ch <- service.StateMsg{
Type: service.PostState,
StateID: didexchange.StateIDCompleted,
Properties: &mockdidexchange.MockEventProperties{ConnID: sampleConnID},
}

return nil
},
}
mockctx.ServiceMap[didexchange.DIDExchange] = didexSvc

thID := uuid.New().String()

icSvc := &mockissuecredential.MockIssueCredentialSvc{
ActionsFunc: func() ([]issuecredentialsvc.Action, error) {
return []issuecredentialsvc.Action{
{
PIID: thID,
Msg: service.NewDIDCommMsgMap(&issuecredentialsvc.OfferCredential{
Comment: sampleMsgComment,
}),
MyDID: myDID,
TheirDID: theirDID,
},
}, nil
},
HandleFunc: func(service.DIDCommMsg) (string, error) {
return thID, nil
},
}
mockctx.ServiceMap[issuecredentialsvc.Name] = icSvc

store, err := mockctx.StorageProvider().OpenStore(connection.Namespace)
require.NoError(t, err)

record := &connection.Record{
ConnectionID: sampleConnID,
MyDID: myDID,
TheirDID: theirDID,
}
recordBytes, err := json.Marshal(record)
require.NoError(t, err)
require.NoError(t, store.Put(fmt.Sprintf("conn_%s", sampleConnID), recordBytes))

vcWallet, err := New(sampleUser, mockctx)
require.NoError(t, err)
require.NotEmpty(t, vcWallet)

err = vcWallet.Open(wallet.WithUnlockByPassphrase(samplePassPhrase))
require.NoError(t, err)
defer vcWallet.Close()

msg, err := vcWallet.ProposeCredential(&outofband.Invitation{})
require.NoError(t, err)
require.NotEmpty(t, msg)

offer := &issuecredentialsvc.OfferCredential{}

err = msg.Decode(offer)
require.NoError(t, err)
require.NotEmpty(t, offer)
require.Equal(t, sampleMsgComment, offer.Comment)
})

t.Run("test propose presentation failure", func(t *testing.T) {
oobSvc := &mockoutofband.MockOobService{
AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) {
return "", fmt.Errorf(sampleClientErr)
},
}
mockctx.ServiceMap[outofbandSvc.Name] = oobSvc

vcWallet, err := New(sampleUser, mockctx)
require.NoError(t, err)
require.NotEmpty(t, vcWallet)

err = vcWallet.Open(wallet.WithUnlockByPassphrase(samplePassPhrase))
require.NoError(t, err)
defer vcWallet.Close()

msg, err := vcWallet.ProposeCredential(&outofband.Invitation{})
require.Error(t, err)
require.Empty(t, msg)
})

t.Run("test propose presentation failure - auth error", func(t *testing.T) {
vcWallet, err := New(sampleUser, mockctx)
require.NoError(t, err)
require.NotEmpty(t, vcWallet)

msg, err := vcWallet.ProposeCredential(&outofband.Invitation{})
require.True(t, errors.Is(err, ErrWalletLocked))
require.Empty(t, msg)
})
}

func TestClient_RequestCredential(t *testing.T) {
const piidKey = "piid"

sampleUser := uuid.New().String()
mockctx := newMockProvider(t)

err := CreateProfile(sampleUser, mockctx, wallet.WithPassphrase(samplePassPhrase))
require.NoError(t, err)

t.Run("test present proof success", func(t *testing.T) {
vcWallet, err := New(sampleUser, mockctx)
require.NoError(t, err)
require.NotEmpty(t, vcWallet)

err = vcWallet.Open(wallet.WithUnlockByPassphrase(samplePassPhrase))
require.NoError(t, err)
defer vcWallet.Close()

response, err := vcWallet.RequestCredential(uuid.New().String(), wallet.FromPresentation(&verifiable.Presentation{}))
require.NoError(t, err)
require.NotEmpty(t, response)
require.Equal(t, model.AckStatusPENDING, response.Status)
})

t.Run("test present proof success - wait for done", func(t *testing.T) {
thID := uuid.New().String()

loader, err := ldtestutil.DocumentLoader()
require.NoError(t, err)

vc, err := verifiable.ParseCredential([]byte(sampleUDCVC), verifiable.WithJSONLDDocumentLoader(loader))
require.NoError(t, err)
require.NotEmpty(t, vc)

icSvc := &mockissuecredential.MockIssueCredentialSvc{
RegisterActionEventHandle: func(ch chan<- service.DIDCommAction) error {
ch <- service.DIDCommAction{
Message: service.NewDIDCommMsgMap(&issuecredentialsvc.IssueCredential{
Type: issuecredentialsvc.IssueCredentialMsgTypeV2,
CredentialsAttach: []decorator.Attachment{
{Data: decorator.AttachmentData{JSON: vc}},
},
}),
Properties: &mockdidexchange.MockEventProperties{
Properties: map[string]interface{}{
piidKey: thID,
webRedirectURLKey: exampleWebRedirect,
},
},
Continue: func(interface{}) {},
Stop: func(error) {},
}

return nil
},
}
mockctx.ServiceMap[issuecredentialsvc.Name] = icSvc

vcWallet, err := New(sampleUser, mockctx)
require.NoError(t, err)
require.NotEmpty(t, vcWallet)

err = vcWallet.Open(wallet.WithUnlockByPassphrase(samplePassPhrase))
require.NoError(t, err)
defer vcWallet.Close()

response, err := vcWallet.RequestCredential(thID, wallet.FromPresentation(&verifiable.Presentation{}),
wallet.WaitForDone(0))
require.NoError(t, err)
require.NotEmpty(t, response)
require.Equal(t, model.AckStatusOK, response.Status)
require.Equal(t, exampleWebRedirect, response.RedirectURL)

vcFulfilled, err := verifiable.ParseCredential(response.Credentials[0], verifiable.WithJSONLDDocumentLoader(loader))
require.NoError(t, err)
require.NotEmpty(t, vcFulfilled)
require.Equal(t, vc.ID, vcFulfilled.ID)
})

t.Run("test present proof failure - auth error", func(t *testing.T) {
vcWallet, err := New(sampleUser, mockctx)
require.NoError(t, err)
require.NotEmpty(t, vcWallet)

response, err := vcWallet.RequestCredential(uuid.New().String(), wallet.FromPresentation(&verifiable.Presentation{}))
require.True(t, errors.Is(err, ErrWalletLocked))
require.Empty(t, response)
})
}

func newMockProvider(t *testing.T) *mockprovider.Provider {
t.Helper()

Expand Down

0 comments on commit 4fc3101

Please sign in to comment.