Skip to content

Commit

Permalink
feat: VC wallet contents - add/get/remove
Browse files Browse the repository at this point in the history
- fixed: UUID for dbnamespacing instead of user ID.
- added add/get/remove features for credential, collection, diddocument,
metadata, connection data models.
- closes hyperledger-archives#2651

Signed-off-by: sudesh.shetty <sudesh.shetty@securekey.com>
  • Loading branch information
sudeshrshetty committed Mar 18, 2021
1 parent dd56601 commit a4a4d90
Show file tree
Hide file tree
Showing 6 changed files with 542 additions and 43 deletions.
64 changes: 49 additions & 15 deletions pkg/client/vcwallet/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ type Client struct {
// wallet profile
profile *profile

// wallet content store
contents *contentStore

// storage provider
storeProvider storage.Provider
}
Expand All @@ -83,7 +86,12 @@ func New(userID string, ctx provider) (*Client, error) {
return nil, fmt.Errorf("failed to get VC wallet profile: %w", err)
}

return &Client{userID: userID, profile: profile, storeProvider: ctx.StorageProvider()}, nil
contents, err := newContentStore(ctx.StorageProvider(), profile)
if err != nil {
return nil, fmt.Errorf("failed to get wallet content store: %w", err)
}

return &Client{userID: userID, profile: profile, storeProvider: ctx.StorageProvider(), contents: contents}, nil
}

// CreateProfile creates a new verifiable credential wallet profile for given user.
Expand All @@ -107,16 +115,32 @@ func createOrUpdate(userID string, ctx provider, update bool, options ...KeyMana
opt(opts)
}

profile, err := createProfile(userID, opts.passphrase, opts.secretLockSvc, opts.keyServerURL)
if err != nil {
return fmt.Errorf("failed to create new VC wallet client: %w", err)
}

store, err := newProfileStore(ctx.StorageProvider())
if err != nil {
return fmt.Errorf("failed to get store to save VC wallet profile: %w", err)
}

var profile *profile

if update {
// find existing profile and update it.
profile, err = store.get(userID)
if err != nil {
return fmt.Errorf("failed to update wallet user profile: %w", err)
}

err = profile.setKMSOptions(opts.passphrase, opts.secretLockSvc, opts.keyServerURL)
if err != nil {
return fmt.Errorf("failed to update wallet user profile KMS options: %w", err)
}
} else {
// create new profile.
profile, err = createProfile(userID, opts.passphrase, opts.secretLockSvc, opts.keyServerURL)
if err != nil {
return fmt.Errorf("failed to create new wallet user profile: %w", err)
}
}

err = store.save(profile, update)
if err != nil {
return fmt.Errorf("failed to save VC wallet profile: %w", err)
Expand Down Expand Up @@ -170,6 +194,9 @@ func (c *Client) Export(auth string) (json.RawMessage, error) {
// Supported data models:
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Profile
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Credential
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#CachedDIDDocument
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#meta-data
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#connection
//
func (c *Client) Import(auth string, contents json.RawMessage) error {
// TODO to be added #2433
Expand All @@ -181,32 +208,39 @@ func (c *Client) Import(auth string, contents json.RawMessage) error {
// Supported data models:
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Profile
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Credential
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#CachedDIDDocument
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#meta-data
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#connection
//
func (c *Client) Add(model json.RawMessage) error {
// TODO to be added #2433
return fmt.Errorf("to be implemented")
// TODO: (#2433) support for correlation between wallet contents (ex: credentials to a profile/collection).
func (c *Client) Add(contentType ContentType, content json.RawMessage) error {
return c.contents.Save(contentType, content)
}

// Remove removes wallet content by content ID.
//
// Supported data models:
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Profile
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Credential
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#CachedDIDDocument
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#meta-data
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#connection
//
func (c *Client) Remove(contentID string) error {
// TODO to be added #2433
return fmt.Errorf("to be implemented")
func (c *Client) Remove(contentType ContentType, contentID string) error {
return c.contents.Remove(contentType, contentID)
}

// Get fetches a wallet content by content ID.
//
// Supported data models:
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Profile
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#Credential
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#CachedDIDDocument
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#meta-data
// - https://w3c-ccg.github.io/universal-wallet-interop-spec/#connection
//
func (c *Client) Get(contentID string) (json.RawMessage, error) {
// TODO to be added #2433
return nil, fmt.Errorf("to be implemented")
func (c *Client) Get(contentType ContentType, contentID string) (json.RawMessage, error) {
return c.contents.Get(contentType, contentID)
}

// Query returns a collection of results based on current wallet contents.
Expand Down
99 changes: 81 additions & 18 deletions pkg/client/vcwallet/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package vcwallet

import (
"crypto/sha256"
"errors"
"fmt"
"testing"

Expand Down Expand Up @@ -98,21 +99,42 @@ func TestCreate(t *testing.T) {
require.Error(t, err)
require.Empty(t, wallet)
})

t.Run("test create new wallet failure - create content store error", func(t *testing.T) {
mockctx := newMockProvider()
mockctx.storeProvider = &mockStorageProvider{
MockStoreProvider: mockstorage.NewMockStoreProvider(),
failure: fmt.Errorf(sampleClientErr),
}

err := CreateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL))
require.NoError(t, err)

wallet, err := New(sampleUserID, mockctx)
require.Error(t, err)
require.Empty(t, wallet)
require.Contains(t, err.Error(), "failed to get wallet content store:")
})
}

func TestUpdate(t *testing.T) {
t.Run("test update wallet client using local kms passphrase", func(t *testing.T) {
mockctx := newMockProvider()
createSampleProfile(t, mockctx)

err := UpdateProfile(sampleUserID, mockctx, WithPassphrase(samplePassPhrase))
require.NoError(t, err)

wallet, err := New(sampleUserID, mockctx)
require.NoError(t, err)
require.NotEmpty(t, wallet)
require.NotEmpty(t, wallet.profile.MasterLockCipher)
})

t.Run("test update wallet client using local kms secret lock service", func(t *testing.T) {
mockctx := newMockProvider()
createSampleProfile(t, mockctx)

err := UpdateProfile(sampleUserID, mockctx, WithSecretLockService(&secretlock.MockSecretLock{}))
require.NoError(t, err)

Expand All @@ -123,20 +145,38 @@ func TestUpdate(t *testing.T) {

t.Run("test update wallet client using remote kms key server URL", func(t *testing.T) {
mockctx := newMockProvider()
createSampleProfile(t, mockctx)

err := UpdateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL))
require.NoError(t, err)

wallet, err := New(sampleUserID, mockctx)
require.NoError(t, err)
require.NotEmpty(t, wallet)
require.Empty(t, wallet.profile.MasterLockCipher)
require.NotEmpty(t, wallet.profile.KeyServerURL)
})

t.Run("test update wallet failure", func(t *testing.T) {
mockctx := newMockProvider()
createSampleProfile(t, mockctx)

err := UpdateProfile(sampleUserID, mockctx)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid create profile options")

wallet, err := New(sampleUserID, mockctx)
require.NoError(t, err)
require.NotEmpty(t, wallet)
require.NotEmpty(t, wallet.profile.MasterLockCipher)
})

t.Run("test update wallet failure - profile doesn't exists", func(t *testing.T) {
mockctx := newMockProvider()
err := UpdateProfile(sampleUserID, mockctx)
require.Error(t, err)
require.Contains(t, err.Error(), "profile does not exist")

wallet, err := New(sampleUserID, mockctx)
require.Error(t, err)
require.Empty(t, wallet)
Expand All @@ -159,19 +199,19 @@ func TestUpdate(t *testing.T) {

t.Run("test update wallet failure - save profile error", func(t *testing.T) {
mockctx := newMockProvider()
mockctx.storeProvider = &mockstorage.MockStoreProvider{
Store: &mockstorage.MockStore{
ErrPut: fmt.Errorf(sampleClientErr),
},
}
createSampleProfile(t, mockctx)

mockctx.storeProvider.(*mockstorage.MockStoreProvider).Store.ErrPut = fmt.Errorf(sampleClientErr)

err := UpdateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL))
require.Error(t, err)
require.Contains(t, err.Error(), sampleClientErr)

wallet, err := New(sampleUserID, mockctx)
require.Error(t, err)
require.Empty(t, wallet)
require.NoError(t, err)
require.NotEmpty(t, wallet)
require.Empty(t, wallet.profile.KeyServerURL)
require.NotEmpty(t, wallet.profile.MasterLockCipher)
})
}

Expand Down Expand Up @@ -344,12 +384,11 @@ func TestClient_Add(t *testing.T) {
require.NotEmpty(t, vcWalletClient)
require.NoError(t, err)

err = vcWalletClient.Add(nil)
require.Error(t, err)
require.EqualError(t, err, toBeImplementedErr)
err = vcWalletClient.Add(Metadata, []byte(sampleContentValid))
require.NoError(t, err)
}

func TestClient_Remove(t *testing.T) {
func TestClient_Get(t *testing.T) {
mockctx := newMockProvider()
err := CreateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL))
require.NoError(t, err)
Expand All @@ -358,12 +397,16 @@ func TestClient_Remove(t *testing.T) {
require.NotEmpty(t, vcWalletClient)
require.NoError(t, err)

err = vcWalletClient.Remove("")
require.Error(t, err)
require.EqualError(t, err, toBeImplementedErr)
err = vcWalletClient.Add(Metadata, []byte(sampleContentValid))
require.NoError(t, err)

content, err := vcWalletClient.Get(Metadata, "did:example:123456789abcdefghi")
require.NoError(t, err)
require.NotEmpty(t, content)
require.Equal(t, sampleContentValid, string(content))
}

func TestClient_Get(t *testing.T) {
func TestClient_Remove(t *testing.T) {
mockctx := newMockProvider()
err := CreateProfile(sampleUserID, mockctx, WithKeyServerURL(sampleKeyServerURL))
require.NoError(t, err)
Expand All @@ -372,10 +415,20 @@ func TestClient_Get(t *testing.T) {
require.NotEmpty(t, vcWalletClient)
require.NoError(t, err)

result, err := vcWalletClient.Get("")
require.Empty(t, result)
err = vcWalletClient.Add(Metadata, []byte(sampleContentValid))
require.NoError(t, err)

content, err := vcWalletClient.Get(Metadata, "did:example:123456789abcdefghi")
require.NoError(t, err)
require.NotEmpty(t, content)

err = vcWalletClient.Remove(Metadata, "did:example:123456789abcdefghi")
require.NoError(t, err)

content, err = vcWalletClient.Get(Metadata, "did:example:123456789abcdefghi")
require.Empty(t, content)
require.Error(t, err)
require.EqualError(t, err, toBeImplementedErr)
require.True(t, errors.Is(err, storage.ErrDataNotFound))
}

func TestClient_Query(t *testing.T) {
Expand Down Expand Up @@ -450,3 +503,13 @@ func (p *mockProvider) StorageProvider() storage.Provider {
func newMockProvider() *mockProvider {
return &mockProvider{storeProvider: mockstorage.NewMockStoreProvider()}
}

func createSampleProfile(t *testing.T, mockctx *mockProvider) {
err := CreateProfile(sampleUserID, mockctx, WithPassphrase(samplePassPhrase))
require.NoError(t, err)

wallet, err := New(sampleUserID, mockctx)
require.NoError(t, err)
require.NotEmpty(t, wallet)
require.NotEmpty(t, wallet.profile.MasterLockCipher)
}

0 comments on commit a4a4d90

Please sign in to comment.