diff --git a/pkg/controller/command/basewallet/command.go b/pkg/controller/command/basewallet/command.go new file mode 100644 index 0000000000..47d3566d71 --- /dev/null +++ b/pkg/controller/command/basewallet/command.go @@ -0,0 +1,876 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package basewallet + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "time" + + "github.com/piprate/json-gold/ld" + + "github.com/hyperledger/aries-framework-go/component/storage/edv" + "github.com/hyperledger/aries-framework-go/pkg/common/log" + "github.com/hyperledger/aries-framework-go/pkg/controller/command" + "github.com/hyperledger/aries-framework-go/pkg/controller/internal/cmdutil" + "github.com/hyperledger/aries-framework-go/pkg/crypto" + "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" + "github.com/hyperledger/aries-framework-go/pkg/internal/logutil" + "github.com/hyperledger/aries-framework-go/pkg/kms/webkms" + "github.com/hyperledger/aries-framework-go/pkg/wallet" + "github.com/hyperledger/aries-framework-go/spi/storage" +) + +var logger = log.New("aries-framework/command/vcwallet") + +// Error codes. +const ( + // InvalidRequestErrorCode is typically a code for invalid requests. + InvalidRequestErrorCode = command.Code(iota + command.VCWallet) + + // CreateProfileErrorCode for errors during create wallet profile operations. + CreateProfileErrorCode + + // UpdateProfileErrorCode for errors during update wallet profile operations. + UpdateProfileErrorCode + + // OpenWalletErrorCode for errors during wallet unlock operations. + OpenWalletErrorCode + + // CloseWalletErrorCode for errors during wallet lock operations. + CloseWalletErrorCode + + // AddToWalletErrorCode for errors while adding contents to wallet. + AddToWalletErrorCode + + // RemoveFromWalletErrorCode for errors while removing contents from wallet. + RemoveFromWalletErrorCode + + // GetFromWalletErrorCode for errors while getting a content from wallet. + GetFromWalletErrorCode + + // GetAllFromWalletErrorCode for errors while getting all contents from wallet. + GetAllFromWalletErrorCode + + // QueryWalletErrorCode for errors while querying credentials contents from wallet. + QueryWalletErrorCode + + // IssueFromWalletErrorCode for errors while issuing a credential from wallet. + IssueFromWalletErrorCode + + // ProveFromWalletErrorCode for errors while producing a presentation from wallet. + ProveFromWalletErrorCode + + // VerifyFromWalletErrorCode for errors while verifying a presentation or credential from wallet. + VerifyFromWalletErrorCode + + // DeriveFromWalletErrorCode for errors while deriving a credential from wallet. + DeriveFromWalletErrorCode + + // CreateKeyPairFromWalletErrorCode for errors while creating key pair from wallet. + CreateKeyPairFromWalletErrorCode + + // ProfileExistsErrorCode for errors while checking if profile exists for a wallet user. + ProfileExistsErrorCode +) + +// All command operations. +const ( + CommandName = "vcwallet" + + // command methods. + CreateProfileMethod = "CreateProfile" + UpdateProfileMethod = "UpdateProfile" + ProfileExistsMethod = "ProfileExists" + OpenMethod = "Open" + CloseMethod = "Close" + AddMethod = "Add" + RemoveMethod = "Remove" + GetMethod = "Get" + GetAllMethod = "GetAll" + QueryMethod = "Query" + IssueMethod = "Issue" + ProveMethod = "Prove" + VerifyMethod = "Verify" + DeriveMethod = "Derive" + CreateKeyPairMethod = "CreateKeyPair" +) + +// miscellaneous constants for the vc wallet command controller. +const ( + // log constants. + logSuccess = "success" + logUserIDKey = "userID" + + emptyRawLength = 4 + + defaultTokenExpiry = 5 * time.Minute +) + +// AuthCapabilityProvider is for providing Authorization Capabilities (ZCAP-LD) feature for +// wallet's EDV and WebKMS components. +type AuthCapabilityProvider interface { + // Returns HTTP Header Signer. + GetHeaderSigner(authzKeyStoreURL, accessToken, secretShare string) HTTPHeaderSigner +} + +// HTTPHeaderSigner is for http header signing, typically used for zcapld functionality. +type HTTPHeaderSigner interface { + // SignHeader header with capability. + SignHeader(req *http.Request, capabilityBytes []byte) (*http.Header, error) +} + +// GNAPHeaderSigner signs a request using GNAP, for resource server access authorization. +type GNAPHeaderSigner func(req *http.Request) (*http.Header, error) + +// Config contains properties to customize verifiable credential wallet controller. +// All properties of this config are optional, but they can be used to customize wallet's webkms and edv client's. +type Config struct { + // EDV header signer, typically used for introducing zcapld feature. + EdvAuthzProvider AuthCapabilityProvider + // Web KMS header signer, typically used for introducing zcapld feature. + WebKMSAuthzProvider AuthCapabilityProvider + // Web KMS header signer for GNAP authorization. + WebKMSGNAPSigner GNAPHeaderSigner + // EDV header signer for GNAP authorization. + EDVGNAPSigner GNAPHeaderSigner + // option is a performance optimization that speeds up queries by getting full documents from + // the EDV server instead of only document locations. + EDVReturnFullDocumentsOnQuery bool + // this EDV option is a performance optimization that allows for restStore.Batch to only require one REST call. + EDVBatchEndpointExtensionEnabled bool + // Aries Web KMS cache size configuration. + WebKMSCacheSize int + // Default token expiry for all wallet profiles created. + // Will be used only if wallet unlock request doesn't supply default timeout value. + DefaultTokenExpiry time.Duration + // Indicate if a data model of json-ld content stored in the wallet should be validated. + ValidateDataModel bool +} + +// provider contains dependencies for the verifiable credential wallet command controller +// and is typically created by using aries.Context(). +type provider interface { + StorageProvider() storage.Provider + VDRegistry() vdr.Registry + Crypto() crypto.Crypto + JSONLDDocumentLoader() ld.DocumentLoader + MediaTypeProfiles() []string +} + +// Command contains operations provided by verifiable credential wallet controller. +type Command struct { + ctx provider + config *Config +} + +// New returns new verifiable credential wallet controller command instance. +func New(p provider, config *Config) *Command { + cmd := &Command{ctx: p, config: &Config{}} + + if config != nil { + cmd.config = config + } + + if cmd.config.DefaultTokenExpiry == 0 { + cmd.config.DefaultTokenExpiry = defaultTokenExpiry + } + + return cmd +} + +// GetHandlers returns list of all commands supported by this controller command. +func (o *Command) GetHandlers() []command.Handler { + return []command.Handler{ + cmdutil.NewCommandHandler(CommandName, CreateProfileMethod, o.CreateProfile), + cmdutil.NewCommandHandler(CommandName, UpdateProfileMethod, o.UpdateProfile), + cmdutil.NewCommandHandler(CommandName, ProfileExistsMethod, o.ProfileExists), + cmdutil.NewCommandHandler(CommandName, OpenMethod, o.Open), + cmdutil.NewCommandHandler(CommandName, CloseMethod, o.Close), + cmdutil.NewCommandHandler(CommandName, AddMethod, o.Add), + cmdutil.NewCommandHandler(CommandName, RemoveMethod, o.Remove), + cmdutil.NewCommandHandler(CommandName, GetMethod, o.Get), + cmdutil.NewCommandHandler(CommandName, GetAllMethod, o.GetAll), + cmdutil.NewCommandHandler(CommandName, QueryMethod, o.Query), + cmdutil.NewCommandHandler(CommandName, IssueMethod, o.Issue), + cmdutil.NewCommandHandler(CommandName, ProveMethod, o.Prove), + cmdutil.NewCommandHandler(CommandName, VerifyMethod, o.Verify), + cmdutil.NewCommandHandler(CommandName, DeriveMethod, o.Derive), + cmdutil.NewCommandHandler(CommandName, CreateKeyPairMethod, o.CreateKeyPair), + } +} + +// CreateProfile creates new wallet profile for given user. +func (o *Command) CreateProfile(rw io.Writer, req io.Reader) command.Error { + request := &CreateOrUpdateProfileRequest{} + + err := json.NewDecoder(req).Decode(request) + if err != nil { + logutil.LogInfo(logger, CommandName, CreateProfileMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + // create profile. + err = wallet.CreateProfile(request.UserID, o.ctx, prepareProfileOptions(request)...) + if err != nil { + logutil.LogInfo(logger, CommandName, CreateProfileMethod, err.Error()) + + return command.NewExecuteError(CreateProfileErrorCode, err) + } + + // create EDV keys if profile is using local kms. + if request.LocalKMSPassphrase != "" && request.EDVConfiguration != nil { + err = wallet.CreateDataVaultKeyPairs(request.UserID, o.ctx, wallet.WithUnlockByPassphrase(request.LocalKMSPassphrase)) + if err != nil { + logutil.LogInfo(logger, CommandName, CreateProfileMethod, err.Error()) + + return command.NewExecuteError(CreateProfileErrorCode, err) + } + } + + logutil.LogDebug(logger, CommandName, CreateProfileMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// UpdateProfile updates an existing wallet profile for given user. +func (o *Command) UpdateProfile(rw io.Writer, req io.Reader) command.Error { + request := &CreateOrUpdateProfileRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, UpdateProfileMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + // update profile. + err = wallet.UpdateProfile(request.UserID, o.ctx, prepareProfileOptions(request)...) + if err != nil { + logutil.LogInfo(logger, CommandName, UpdateProfileMethod, err.Error()) + + return command.NewExecuteError(UpdateProfileErrorCode, err) + } + + logutil.LogDebug(logger, CommandName, UpdateProfileMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// ProfileExists checks if wallet profile exists for given wallet user. +func (o *Command) ProfileExists(rw io.Writer, req io.Reader) command.Error { + user := &WalletUser{} + + err := json.NewDecoder(req).Decode(&user) + if err != nil { + logutil.LogInfo(logger, CommandName, ProfileExistsMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + err = wallet.ProfileExists(user.ID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ProfileExistsMethod, err.Error()) + + return command.NewExecuteError(ProfileExistsErrorCode, err) + } + + logutil.LogDebug(logger, CommandName, ProfileExistsMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, user.ID)) + + return nil +} + +// Open unlocks given user's wallet and returns a token for subsequent use of wallet features. +func (o *Command) Open(rw io.Writer, req io.Reader) command.Error { + request := &UnlockWalletRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + opts, err := prepareUnlockOptions(request, o.config) + if err != nil { + logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) + + return command.NewExecuteError(OpenWalletErrorCode, err) + } + + token, err := vcWallet.Open(opts...) + if err != nil { + logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) + + return command.NewExecuteError(OpenWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, UnlockWalletResponse{Token: token}, logger) + + logutil.LogDebug(logger, CommandName, OpenMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Close locks given user's wallet. +func (o *Command) Close(rw io.Writer, req io.Reader) command.Error { + request := &LockWalletRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, CloseMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, CloseMethod, err.Error()) + + return command.NewExecuteError(CloseWalletErrorCode, err) + } + + closed := vcWallet.Close() + + command.WriteNillableResponse(rw, LockWalletResponse{Closed: closed}, logger) + + logutil.LogDebug(logger, CommandName, CloseMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Add adds given data model to wallet content store. +func (o *Command) Add(rw io.Writer, req io.Reader) command.Error { + request := &AddContentRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, AddMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, AddMethod, err.Error()) + + return command.NewExecuteError(AddToWalletErrorCode, err) + } + + addOpts := []wallet.AddContentOptions{ + wallet.AddByCollection(request.CollectionID), + } + + if o.config.ValidateDataModel { + addOpts = append(addOpts, wallet.ValidateContent()) + } + + err = vcWallet.Add(request.Auth, request.ContentType, request.Content, addOpts...) + if err != nil { + logutil.LogInfo(logger, CommandName, AddMethod, err.Error()) + + return command.NewExecuteError(AddToWalletErrorCode, err) + } + + logutil.LogDebug(logger, CommandName, AddMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Remove deletes given content from wallet content store. +func (o *Command) Remove(rw io.Writer, req io.Reader) command.Error { + request := &RemoveContentRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, RemoveMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, RemoveMethod, err.Error()) + + return command.NewExecuteError(RemoveFromWalletErrorCode, err) + } + + err = vcWallet.Remove(request.Auth, request.ContentType, request.ContentID) + if err != nil { + logutil.LogInfo(logger, CommandName, RemoveMethod, err.Error()) + + return command.NewExecuteError(RemoveFromWalletErrorCode, err) + } + + logutil.LogDebug(logger, CommandName, RemoveMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Get returns wallet content by ID from wallet content store. +func (o *Command) Get(rw io.Writer, req io.Reader) command.Error { + request := &GetContentRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, GetMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, GetMethod, err.Error()) + + return command.NewExecuteError(GetFromWalletErrorCode, err) + } + + content, err := vcWallet.Get(request.Auth, request.ContentType, request.ContentID) + if err != nil { + logutil.LogInfo(logger, CommandName, GetMethod, err.Error()) + + return command.NewExecuteError(GetFromWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, GetContentResponse{Content: content}, logger) + + logutil.LogDebug(logger, CommandName, GetMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// GetAll gets all wallet content from wallet content store for given type. +func (o *Command) GetAll(rw io.Writer, req io.Reader) command.Error { + request := &GetAllContentRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) + + return command.NewExecuteError(GetAllFromWalletErrorCode, err) + } + + contents, err := vcWallet.GetAll(request.Auth, request.ContentType, + wallet.FilterByCollection(request.CollectionID)) + if err != nil { + logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) + + return command.NewExecuteError(GetAllFromWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, GetAllContentResponse{Contents: contents}, logger) + + logutil.LogDebug(logger, CommandName, GetAllMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Query runs credential queries against wallet credential contents and +// returns presentation containing credential results. +func (o *Command) Query(rw io.Writer, req io.Reader) command.Error { + request := &ContentQueryRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) + + return command.NewExecuteError(QueryWalletErrorCode, err) + } + + presentations, err := vcWallet.Query(request.Auth, request.Query...) + if err != nil { + logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) + + return command.NewExecuteError(QueryWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, &ContentQueryResponse{Results: presentations}, logger) + + logutil.LogDebug(logger, CommandName, GetAllMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Issue adds proof to a Verifiable Credential from wallet. +func (o *Command) Issue(rw io.Writer, req io.Reader) command.Error { + request := &IssueRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, IssueMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, IssueMethod, err.Error()) + + return command.NewExecuteError(IssueFromWalletErrorCode, err) + } + + credential, err := vcWallet.Issue(request.Auth, request.Credential, request.ProofOptions) + if err != nil { + logutil.LogInfo(logger, CommandName, IssueMethod, err.Error()) + + return command.NewExecuteError(IssueFromWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, &IssueResponse{Credential: credential}, logger) + + logutil.LogDebug(logger, CommandName, IssueMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Prove produces a Verifiable Presentation from wallet. +func (o *Command) Prove(rw io.Writer, req io.Reader) command.Error { + request := &ProveRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, ProveMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ProveMethod, err.Error()) + + return command.NewExecuteError(ProveFromWalletErrorCode, err) + } + + vp, err := vcWallet.Prove(request.Auth, request.ProofOptions, prepareProveOptions(request)...) + if err != nil { + logutil.LogInfo(logger, CommandName, ProveMethod, err.Error()) + + return command.NewExecuteError(ProveFromWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, &ProveResponse{Presentation: vp}, logger) + + logutil.LogDebug(logger, CommandName, ProveMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Verify verifies credential/presentation from wallet. +func (o *Command) Verify(rw io.Writer, req io.Reader) command.Error { + request := &VerifyRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, VerifyMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, VerifyMethod, err.Error()) + + return command.NewExecuteError(VerifyFromWalletErrorCode, err) + } + + option, err := prepareVerifyOption(request) + if err != nil { + logutil.LogInfo(logger, CommandName, VerifyMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + verified, err := vcWallet.Verify(request.Auth, option) + + response := &VerifyResponse{Verified: verified} + + if err != nil { + response.Error = err.Error() + } + + command.WriteNillableResponse(rw, response, logger) + + logutil.LogDebug(logger, CommandName, VerifyMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// Derive derives a credential from wallet. +func (o *Command) Derive(rw io.Writer, req io.Reader) command.Error { + request := &DeriveRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, DeriveMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, DeriveMethod, err.Error()) + + return command.NewExecuteError(DeriveFromWalletErrorCode, err) + } + + derived, err := vcWallet.Derive(request.Auth, prepareDeriveOption(request), request.DeriveOptions) + if err != nil { + logutil.LogInfo(logger, CommandName, DeriveMethod, err.Error()) + + return command.NewExecuteError(DeriveFromWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, &DeriveResponse{Credential: derived}, logger) + + logutil.LogDebug(logger, CommandName, DeriveMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// CreateKeyPair creates key pair from wallet. +func (o *Command) CreateKeyPair(rw io.Writer, req io.Reader) command.Error { + request := &CreateKeyPairRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, CreateKeyPairMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, CreateKeyPairMethod, err.Error()) + + return command.NewExecuteError(CreateKeyPairFromWalletErrorCode, err) + } + + response, err := vcWallet.CreateKeyPair(request.Auth, request.KeyType) + if err != nil { + logutil.LogInfo(logger, CommandName, CreateKeyPairMethod, err.Error()) + + return command.NewExecuteError(CreateKeyPairFromWalletErrorCode, err) + } + + command.WriteNillableResponse(rw, &CreateKeyPairResponse{response}, logger) + + logutil.LogDebug(logger, CommandName, CreateKeyPairMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// prepareProfileOptions prepares options for creating wallet profile. +func prepareProfileOptions(rqst *CreateOrUpdateProfileRequest) []wallet.ProfileOptions { + var options []wallet.ProfileOptions + + if rqst.LocalKMSPassphrase != "" { + options = append(options, wallet.WithPassphrase(rqst.LocalKMSPassphrase)) + } + + if rqst.KeyStoreURL != "" { + options = append(options, wallet.WithKeyServerURL(rqst.KeyStoreURL)) + } + + if rqst.EDVConfiguration != nil { + options = append(options, wallet.WithEDVStorage( + rqst.EDVConfiguration.ServerURL, rqst.EDVConfiguration.VaultID, + rqst.EDVConfiguration.EncryptionKeyID, rqst.EDVConfiguration.MACKeyID, + )) + } + + return options +} + +// prepareUnlockOptions prepares options for unlocking wallet. +//nolint: lll +func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) ([]wallet.UnlockOptions, error) { // nolint:funlen,gocyclo + var options []wallet.UnlockOptions + + if rqst.LocalKMSPassphrase != "" { + options = append(options, wallet.WithUnlockByPassphrase(rqst.LocalKMSPassphrase)) + } + + var webkmsOpts []webkms.Opt + + if rqst.WebKMSAuth != nil { + var webKMSHeader func(*http.Request) (*http.Header, error) + + switch { + case rqst.WebKMSAuth.Capability != "": // zcap ld signing + if conf.WebKMSAuthzProvider == nil { + return nil, fmt.Errorf("authorization capability for WebKMS is not configured") + } + + signer := conf.WebKMSAuthzProvider.GetHeaderSigner(rqst.WebKMSAuth.AuthZKeyStoreURL, + rqst.WebKMSAuth.AuthToken, rqst.WebKMSAuth.SecretShare) + + webKMSHeader = func(req *http.Request) (*http.Header, error) { + return signer.SignHeader(req, []byte(rqst.WebKMSAuth.Capability)) + } + case rqst.WebKMSAuth.AuthToken != "": // auth token + webKMSHeader = func(req *http.Request) (*http.Header, error) { + req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.WebKMSAuth.AuthToken)) + + return &req.Header, nil + } + case rqst.WebKMSAuth.GNAPToken != "": // GNAP token + if conf.WebKMSGNAPSigner != nil { + webKMSHeader = conf.WebKMSGNAPSigner + } else { + webKMSHeader = func(req *http.Request) (*http.Header, error) { + req.Header.Set("authorization", fmt.Sprintf("GNAP %s", rqst.WebKMSAuth.GNAPToken)) + + return &req.Header, nil + } + } + } + + webkmsOpts = append(webkmsOpts, webkms.WithHeaders(webKMSHeader)) + } + + if conf.WebKMSCacheSize > 0 { + webkmsOpts = append(webkmsOpts, webkms.WithCache(conf.WebKMSCacheSize)) + } + + var edvOpts []edv.RESTProviderOption + + if rqst.EDVUnlock != nil { + var edvHeader func(*http.Request) (*http.Header, error) + + switch { + case rqst.EDVUnlock.Capability != "": // zcap ld signing + if conf.EdvAuthzProvider == nil { + return nil, fmt.Errorf("authorization capability for EDV is not configured") + } + + signer := conf.EdvAuthzProvider.GetHeaderSigner(rqst.EDVUnlock.AuthZKeyStoreURL, + rqst.EDVUnlock.AuthToken, rqst.EDVUnlock.SecretShare) + + edvHeader = func(req *http.Request) (*http.Header, error) { + return signer.SignHeader(req, []byte(rqst.EDVUnlock.Capability)) + } + case rqst.EDVUnlock.AuthToken != "": // auth token + edvHeader = func(req *http.Request) (*http.Header, error) { + req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.EDVUnlock.AuthToken)) + + return &req.Header, nil + } + case rqst.EDVUnlock.GNAPToken != "": // GNAP token + if conf.EDVGNAPSigner != nil { + edvHeader = conf.EDVGNAPSigner + } else { + edvHeader = func(req *http.Request) (*http.Header, error) { + req.Header.Set("authorization", fmt.Sprintf("GNAP %s", rqst.EDVUnlock.GNAPToken)) + + return &req.Header, nil + } + } + } + + edvOpts = append(edvOpts, edv.WithHeaders(edvHeader)) + } + + if conf.EDVBatchEndpointExtensionEnabled { + edvOpts = append(edvOpts, edv.WithBatchEndpointExtension()) + } + + if conf.EDVReturnFullDocumentsOnQuery { + edvOpts = append(edvOpts, edv.WithFullDocumentsReturnedFromQueries()) + } + + tokenExpiry := conf.DefaultTokenExpiry + if rqst.Expiry > 0 { + tokenExpiry = rqst.Expiry + } + + options = append(options, wallet.WithUnlockWebKMSOptions(webkmsOpts...), wallet.WithUnlockEDVOptions(edvOpts...), + wallet.WithUnlockExpiry(tokenExpiry)) + + return options, nil +} + +func prepareProveOptions(rqst *ProveRequest) []wallet.ProveOptions { + var options []wallet.ProveOptions + + if len(rqst.StoredCredentials) > 0 { + options = append(options, wallet.WithStoredCredentialsToProve(rqst.StoredCredentials...)) + } + + if len(rqst.RawCredentials) > 0 { + options = append(options, wallet.WithRawCredentialsToProve(rqst.RawCredentials...)) + } + + if len(rqst.Presentation) > emptyRawLength { + options = append(options, wallet.WithRawPresentationToProve(rqst.Presentation)) + } + + return options +} + +func prepareVerifyOption(rqst *VerifyRequest) (wallet.VerificationOption, error) { + if len(rqst.StoredCredentialID) > 0 { + return wallet.WithStoredCredentialToVerify(rqst.StoredCredentialID), nil + } + + if len(rqst.RawCredential) > emptyRawLength { + return wallet.WithRawCredentialToVerify(rqst.RawCredential), nil + } + + if len(rqst.Presentation) > emptyRawLength { + return wallet.WithRawPresentationToVerify(rqst.Presentation), nil + } + + return nil, errors.New("invalid option") +} + +func prepareDeriveOption(rqst *DeriveRequest) wallet.CredentialToDerive { + if len(rqst.StoredCredentialID) > 0 { + return wallet.FromStoredCredential(rqst.StoredCredentialID) + } + + return wallet.FromRawCredential(rqst.RawCredential) +} diff --git a/pkg/controller/command/basewallet/command_test.go b/pkg/controller/command/basewallet/command_test.go new file mode 100644 index 0000000000..c7a4bf87a1 --- /dev/null +++ b/pkg/controller/command/basewallet/command_test.go @@ -0,0 +1,1923 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package basewallet + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/internal/testdata" + "github.com/hyperledger/aries-framework-go/pkg/controller/command" + "github.com/hyperledger/aries-framework-go/pkg/crypto/tinkcrypto" + "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" + outofbandSvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofband" + oobv2 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofbandv2" + presentproofSvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/presentproof" + "github.com/hyperledger/aries-framework-go/pkg/doc/did" + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" + mockoutofbandv2 "github.com/hyperledger/aries-framework-go/pkg/internal/gomocks/client/outofbandv2" + "github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil" + "github.com/hyperledger/aries-framework-go/pkg/kms" + mockdidexchange "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/didexchange" + mockissuecredential "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/issuecredential" + mockmediator "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator" + mockoutofband "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/outofband" + mockpresentproof "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/presentproof" + mockprovider "github.com/hyperledger/aries-framework-go/pkg/mock/provider" + mockstorage "github.com/hyperledger/aries-framework-go/pkg/mock/storage" + mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr" + "github.com/hyperledger/aries-framework-go/pkg/vdr/key" + "github.com/hyperledger/aries-framework-go/pkg/wallet" +) + +const ( + sampleUserID = "sample-user01" + samplePassPhrase = "fakepassphrase" + sampleKeyStoreURL = "sample/keyserver/test" + sampleEDVServerURL = "sample-edv-url" + sampleEDVVaultID = "sample-edv-vault-id" + sampleEDVEncryptionKID = "sample-edv-encryption-kid" + sampleEDVMacKID = "sample-edv-mac-kid" + sampleCommandError = "sample-command-error-01" + sampleFakeTkn = "sample-fake-token-01" + sampleFakeCapability = "sample-fake-capability-01" + sampleDIDKey = "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv4S5" +) + +func TestNew(t *testing.T) { + t.Run("successfully create new command instance", func(t *testing.T) { + cmd := New(newMockProvider(t), &Config{}) + require.NotNil(t, cmd) + + require.Len(t, cmd.GetHandlers(), 15) + }) +} + +func TestCommand_CreateProfile(t *testing.T) { + t.Run("successfully create a new wallet profile (localkms)", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + LocalKMSPassphrase: samplePassPhrase, + } + + var b bytes.Buffer + cmdErr := cmd.CreateProfile(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + // if wallet instance can be creates it means profile exists + walletInstance, err := wallet.New(request.UserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, walletInstance) + }) + + t.Run("successfully create a new wallet profile (webkms/remotekms)", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + KeyStoreURL: sampleKeyStoreURL, + } + + var b bytes.Buffer + cmdErr := cmd.CreateProfile(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + // if wallet instance can be creates it means profile exists + walletInstance, err := wallet.New(request.UserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, walletInstance) + }) + + t.Run("successfully create a new wallet profile with EDV configuration", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + // create with remote kms. + request := &CreateOrUpdateProfileRequest{ + UserID: uuid.New().String(), + KeyStoreURL: sampleKeyStoreURL, + EDVConfiguration: &EDVConfiguration{ + ServerURL: sampleEDVServerURL, + VaultID: sampleEDVVaultID, + MACKeyID: sampleEDVMacKID, + EncryptionKeyID: sampleEDVEncryptionKID, + }, + } + + var b1 bytes.Buffer + cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) + require.NoError(t, cmdErr) + + // if wallet instance can be creates it means profile exists + walletInstance, err := wallet.New(request.UserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, walletInstance) + + // create with local kms. + request = &CreateOrUpdateProfileRequest{ + UserID: uuid.New().String(), + LocalKMSPassphrase: samplePassPhrase, + EDVConfiguration: &EDVConfiguration{ + ServerURL: sampleEDVServerURL, + VaultID: sampleEDVVaultID, + }, + } + + var b2 bytes.Buffer + cmdErr = cmd.CreateProfile(&b2, getReader(t, &request)) + require.NoError(t, cmdErr) + + // if wallet instance can be creates it means profile exists + walletInstance, err = wallet.New(request.UserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, walletInstance) + }) + + t.Run("failed to create duplicate profile", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + LocalKMSPassphrase: samplePassPhrase, + } + + var b1 bytes.Buffer + cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) + require.NoError(t, cmdErr) + + request = &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + KeyStoreURL: sampleKeyStoreURL, + } + + var b2 bytes.Buffer + cmdErr = cmd.CreateProfile(&b2, getReader(t, &request)) + require.Error(t, cmdErr) + require.Equal(t, cmdErr.Type(), command.ExecuteError) + require.Equal(t, cmdErr.Code(), CreateProfileErrorCode) + }) + + t.Run("failed to create profile due to invalid settings", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + } + + var b1 bytes.Buffer + cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) + require.Error(t, cmdErr) + require.Equal(t, cmdErr.Code(), CreateProfileErrorCode) + require.Equal(t, cmdErr.Type(), command.ExecuteError) + }) + + t.Run("failed to create profile due to invalid request", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + var b1 bytes.Buffer + cmdErr := cmd.CreateProfile(&b1, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + require.Equal(t, cmdErr.Code(), InvalidRequestErrorCode) + require.Equal(t, cmdErr.Type(), command.ValidationError) + }) + + t.Run("failed to create profile due to EDV key set creation failure", func(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + mockStProv, ok := mockctx.StorageProviderValue.(*mockstorage.MockStoreProvider) + require.True(t, ok) + require.NotEmpty(t, mockStProv) + + mockStProv.Store.ErrGet = errors.New(sampleCommandError) + + request := &CreateOrUpdateProfileRequest{ + UserID: uuid.New().String(), + LocalKMSPassphrase: samplePassPhrase, + EDVConfiguration: &EDVConfiguration{ + ServerURL: sampleEDVServerURL, + VaultID: sampleEDVVaultID, + }, + } + + var b1 bytes.Buffer + cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) + require.Error(t, cmdErr) + require.Equal(t, cmdErr.Code(), CreateProfileErrorCode) + require.Equal(t, cmdErr.Type(), command.ExecuteError) + require.Contains(t, cmdErr.Error(), sampleCommandError) + }) +} + +func TestCommand_ProfileExists(t *testing.T) { + const ( + sampleUser1 = "sample-user-01" + sampleUser2 = "sample-user-02" + ) + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + t.Run("profile exists", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + var b bytes.Buffer + cmdErr := cmd.ProfileExists(&b, getReader(t, &WalletUser{ + ID: sampleUser1, + })) + require.NoError(t, cmdErr) + }) + + t.Run("profile doesn't exists", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + var b bytes.Buffer + cmdErr := cmd.ProfileExists(&b, getReader(t, &WalletUser{ + ID: sampleUser2, + })) + + validateError(t, cmdErr, command.ExecuteError, ProfileExistsErrorCode, wallet.ErrProfileNotFound.Error()) + }) + + t.Run("invalid request", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + var b bytes.Buffer + cmdErr := cmd.ProfileExists(&b, bytes.NewBufferString("")) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "EOF") + }) +} + +func TestCommand_UpdateProfile(t *testing.T) { + mockctx := newMockProvider(t) + + cmd := New(mockctx, &Config{}) + require.NotNil(t, cmd) + + createRqst := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + LocalKMSPassphrase: samplePassPhrase, + } + + var c bytes.Buffer + cmdErr := cmd.CreateProfile(&c, getReader(t, &createRqst)) + require.NoError(t, cmdErr) + + t.Run("successfully update a wallet profile", func(t *testing.T) { + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + KeyStoreURL: sampleKeyStoreURL, + } + + var b bytes.Buffer + cmdErr := cmd.UpdateProfile(&b, getReader(t, &createRqst)) + require.NoError(t, cmdErr) + + // if wallet instance can be creates it means profile exists + walletInstance, err := wallet.New(request.UserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, walletInstance) + }) + + t.Run("successfully update a wallet profile with EDV configuration", func(t *testing.T) { + // create with remote kms. + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + KeyStoreURL: sampleKeyStoreURL, + EDVConfiguration: &EDVConfiguration{ + ServerURL: sampleEDVServerURL, + VaultID: sampleEDVVaultID, + MACKeyID: sampleEDVMacKID, + EncryptionKeyID: sampleEDVEncryptionKID, + }, + } + + var b1 bytes.Buffer + cmdErr := cmd.UpdateProfile(&b1, getReader(t, &request)) + require.NoError(t, cmdErr) + + // if wallet instance can be creates it means profile exists + walletInstance, err := wallet.New(request.UserID, mockctx) + require.NoError(t, err) + require.NotEmpty(t, walletInstance) + }) + + t.Run("failed to update profile due to invalid settings", func(t *testing.T) { + request := &CreateOrUpdateProfileRequest{ + UserID: sampleUserID, + } + + var b1 bytes.Buffer + cmdErr := cmd.UpdateProfile(&b1, getReader(t, &request)) + require.Error(t, cmdErr) + require.Equal(t, cmdErr.Code(), UpdateProfileErrorCode) + require.Equal(t, cmdErr.Type(), command.ExecuteError) + }) + + t.Run("failed to update profile due to invalid request", func(t *testing.T) { + var b1 bytes.Buffer + cmdErr := cmd.UpdateProfile(&b1, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + require.Equal(t, cmdErr.Code(), InvalidRequestErrorCode) + require.Equal(t, cmdErr.Type(), command.ValidationError) + }) +} + +func TestCommand_OpenAndClose(t *testing.T) { + const ( + sampleUser1 = "sample-user-01" + sampleUser2 = "sample-user-02" + sampleUser3 = "sample-user-03" + ) + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser2, + KeyStoreURL: sampleKeyStoreURL, + }) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser3, + LocalKMSPassphrase: samplePassPhrase, + EDVConfiguration: &EDVConfiguration{ + ServerURL: sampleEDVServerURL, + VaultID: sampleEDVVaultID, + }, + }) + + t.Run("successfully unlock & lock wallet (gnap auth, no custom header func)", func(t *testing.T) { + cmd := New(mockctx, &Config{ + WebKMSCacheSize: 99, + }) + + request := &UnlockWalletRequest{ + UserID: sampleUser2, + WebKMSAuth: &UnlockAuth{ + GNAPToken: sampleFakeTkn, + }, + EDVUnlock: &UnlockAuth{ + GNAPToken: sampleFakeTkn, + }, + Expiry: 10 * time.Second, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("successfully unlock & lock wallet (gnap auth, with custom header func)", func(t *testing.T) { + headerFunc := func(r *http.Request) (*http.Header, error) { + return &r.Header, nil + } + + cmd := New(mockctx, &Config{ + WebKMSCacheSize: 99, + WebKMSGNAPSigner: headerFunc, + EDVGNAPSigner: headerFunc, + }) + + request := &UnlockWalletRequest{ + UserID: sampleUser2, + WebKMSAuth: &UnlockAuth{ + GNAPToken: sampleFakeTkn, + }, + EDVUnlock: &UnlockAuth{ + GNAPToken: sampleFakeTkn, + }, + Expiry: 10 * time.Second, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("successfully unlock & lock wallet (local kms)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + request := &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser1})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser1})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("successfully unlock & lock wallet (remote kms)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + request := &UnlockWalletRequest{ + UserID: sampleUser2, + WebKMSAuth: &UnlockAuth{AuthToken: sampleFakeTkn}, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("successfully unlock & lock wallet (remote kms, capability)", func(t *testing.T) { + cmd := New(mockctx, &Config{ + WebKMSCacheSize: 99, + WebKMSAuthzProvider: &mockAuthZCapability{}, + }) + + request := &UnlockWalletRequest{ + UserID: sampleUser2, + WebKMSAuth: &UnlockAuth{Capability: sampleFakeCapability}, + Expiry: 10 * time.Second, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("successfully unlock & lock wallet (local kms, edv user)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + request := &UnlockWalletRequest{ + UserID: sampleUser3, + LocalKMSPassphrase: samplePassPhrase, + EDVUnlock: &UnlockAuth{ + AuthToken: sampleFakeTkn, + }, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("successfully unlock & lock wallet (local kms, edv capability user)", func(t *testing.T) { + cmd := New(mockctx, &Config{ + EDVReturnFullDocumentsOnQuery: true, + EDVBatchEndpointExtensionEnabled: true, + EdvAuthzProvider: &mockAuthZCapability{}, + }) + + request := &UnlockWalletRequest{ + UserID: sampleUser3, + LocalKMSPassphrase: samplePassPhrase, + EDVUnlock: &UnlockAuth{ + Capability: sampleFakeCapability, + }, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + require.NotEmpty(t, getUnlockToken(t, b)) + b.Reset() + + // try again, should get error, wallet already unlocked + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) + require.Empty(t, b.Len()) + b.Reset() + + // lock wallet + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) + require.NoError(t, cmdErr) + var lockResponse LockWalletResponse + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.True(t, lockResponse.Closed) + b.Reset() + + // lock wallet again + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) + require.NoError(t, cmdErr) + require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) + require.False(t, lockResponse.Closed) + b.Reset() + }) + + t.Run("lock & unlock failures", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Open(&b, getReader(t, &UnlockWalletRequest{})) + require.Error(t, cmdErr) + validateError(t, cmdErr, command.ExecuteError, OpenWalletErrorCode, "profile does not exist") + require.Empty(t, b.Len()) + b.Reset() + + cmdErr = cmd.Open(&b, getReader(t, "")) + require.Error(t, cmdErr) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "cannot unmarshal string into Go") + require.Empty(t, b.Len()) + b.Reset() + + cmdErr = cmd.Close(&b, getReader(t, &UnlockWalletRequest{})) + require.Error(t, cmdErr) + validateError(t, cmdErr, command.ExecuteError, CloseWalletErrorCode, "profile does not exist") + require.Empty(t, b.Len()) + b.Reset() + + cmdErr = cmd.Close(&b, getReader(t, "")) + require.Error(t, cmdErr) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "cannot unmarshal string into Go") + require.Empty(t, b.Len()) + b.Reset() + + // EDV authz error + request := &UnlockWalletRequest{ + UserID: sampleUser3, + LocalKMSPassphrase: samplePassPhrase, + EDVUnlock: &UnlockAuth{ + Capability: sampleFakeCapability, + }, + } + + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, + "authorization capability for EDV is not configured") + require.Empty(t, b.Len()) + b.Reset() + + // webKMS authz error + request = &UnlockWalletRequest{ + UserID: sampleUser3, + WebKMSAuth: &UnlockAuth{ + Capability: sampleFakeCapability, + }, + } + + cmdErr = cmd.Open(&b, getReader(t, &request)) + require.Error(t, cmdErr) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, + "authorization capability for WebKMS is not configured") + require.Empty(t, b.Len()) + b.Reset() + }) +} + +func TestCommand_AddRemoveGetGetAll(t *testing.T) { + const ( + sampleUser1 = "sample-user-01" + sampleUser2 = "sample-user-02" + sampleUser3 = "sample-user-03" + ) + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + token1, lock1 := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock1() + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser2, + KeyStoreURL: sampleKeyStoreURL, + }) + + token2, lock2 := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser2, + WebKMSAuth: &UnlockAuth{AuthToken: sampleFakeTkn}, + }) + + defer lock2() + + t.Run("add a credential to wallet", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: testdata.SampleUDCVC, + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + }) + + t.Run("add a metadata to wallet with validation", func(t *testing.T) { + cmd := New(mockctx, &Config{ValidateDataModel: true}) + + var b bytes.Buffer + + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: testdata.SampleWalletContentMetadata, + ContentType: "metadata", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + }) + + t.Run("get a credential from wallet", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Get(&b, getReader(t, &GetContentRequest{ + ContentID: "http://example.edu/credentials/1872", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + + var response GetContentResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response.Content) + }) + + t.Run("get all credentials from wallet", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + // save multiple credentials, one already saved + const count = 6 + for i := 1; i < count; i++ { + var b bytes.Buffer + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: []byte(strings.ReplaceAll(string(testdata.SampleUDCVC), `"http://example.edu/credentials/1872"`, + fmt.Sprintf(`"http://example.edu/credentials/1872%d"`, i))), + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + + b.Reset() + } + + var b bytes.Buffer + + cmdErr := cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + + var response GetAllContentResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Len(t, response.Contents, count) + }) + + t.Run("add a collection to wallet with validation failed", func(t *testing.T) { + const orgCollectionWithInvalidStructure = `{ + "@context": ["https://w3id.org/wallet/v1"], + "id": "did:example:acme123456789abcdefghi", + "type": "Organization", + "name": "Acme Corp.", + "image": "https://via.placeholder.com/150", + "description" : "A software company.", + "tags": ["professional", "organization"], + "incorrectProp": "incorrectProp", + "correlation": ["4058a72a-9523-11ea-bb37-0242ac130002"] + }` + + cmd := New(mockctx, &Config{ValidateDataModel: true}) + + var b bytes.Buffer + + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: []byte(orgCollectionWithInvalidStructure), + ContentType: wallet.Collection, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.Contains(t, cmdErr.Error(), "JSON-LD doc has different structure after compaction") + }) + + t.Run("get all credentials from wallet by collection ID", func(t *testing.T) { + const orgCollection = `{ + "@context": ["https://w3id.org/wallet/v1"], + "id": "did:example:acme123456789abcdefghi", + "type": "Organization", + "name": "Acme Corp.", + "image": "https://via.placeholder.com/150", + "description" : "A software company.", + "tags": ["professional", "organization"], + "correlation": ["4058a72a-9523-11ea-bb37-0242ac130002"] + }` + + const collectionID = "did:example:acme123456789abcdefghi" + + cmd := New(mockctx, &Config{}) + + // save a collection + var b bytes.Buffer + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: []byte(orgCollection), + ContentType: wallet.Collection, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + + // save only 2 credentials by collection, + const count = 6 + for i := 1; i < count; i++ { + var cid string + + if i%2 == 0 { + cid = collectionID + } + + var vcb bytes.Buffer + cErr := cmd.Add(&vcb, getReader(t, &AddContentRequest{ + Content: []byte(strings.ReplaceAll(string(testdata.SampleUDCVC), `"http://example.edu/credentials/1872"`, + fmt.Sprintf(`"http://example.edu/credentials/18722%d"`, i))), + ContentType: "credential", + CollectionID: cid, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cErr) + + vcb.Reset() + } + + b.Reset() + + cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ + ContentType: "credential", + CollectionID: collectionID, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + + var response GetAllContentResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Len(t, response.Contents, 2) + }) + + t.Run("remove a credential from wallet", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Remove(&b, getReader(t, &RemoveContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + }) + + t.Run("get a credential from different wallet", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Get(&b, getReader(t, &GetContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser2, Auth: token2}, + })) + validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, "data not found") + }) + + t.Run("try content operations from invalid auth", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + const expectedErr = "invalid auth token" + + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: testdata.SampleUDCVC, + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, AddToWalletErrorCode, expectedErr) + b.Reset() + + cmdErr = cmd.Get(&b, getReader(t, &GetContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, expectedErr) + + cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, GetAllFromWalletErrorCode, expectedErr) + + cmdErr = cmd.Remove(&b, getReader(t, &RemoveContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, RemoveFromWalletErrorCode, expectedErr) + }) + + t.Run("try content operations from invalid content type", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: testdata.SampleUDCVC, + ContentType: "mango", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + validateError(t, cmdErr, command.ExecuteError, AddToWalletErrorCode, "invalid content type") + b.Reset() + + cmdErr = cmd.Get(&b, getReader(t, &GetContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "pineapple", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, "data not found") + + cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ + ContentType: "orange", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + + var response GetAllContentResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.Empty(t, response.Contents) + b.Reset() + + cmdErr = cmd.Remove(&b, getReader(t, &RemoveContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "strawberry", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, + })) + require.NoError(t, cmdErr) + }) + + t.Run("try content operations from invalid profile", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + const expectedErr = "profile does not exist" + + cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ + Content: testdata.SampleUDCVC, + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, AddToWalletErrorCode, expectedErr) + b.Reset() + + cmdErr = cmd.Get(&b, getReader(t, &GetContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, expectedErr) + + cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, GetAllFromWalletErrorCode, expectedErr) + + cmdErr = cmd.Remove(&b, getReader(t, &RemoveContentRequest{ + ContentID: "http://example.edu/credentials/1877", + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, RemoveFromWalletErrorCode, expectedErr) + }) + + t.Run("try content operations from invalid request", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + const expectedErr = "invalid character" + + cmdErr := cmd.Add(&b, bytes.NewBufferString("invalid request")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) + b.Reset() + + cmdErr = cmd.Get(&b, bytes.NewBufferString("invalid request")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) + + cmdErr = cmd.GetAll(&b, bytes.NewBufferString("invalid request")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) + + cmdErr = cmd.Remove(&b, bytes.NewBufferString("invalid request")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) + }) +} + +func TestCommand_Query(t *testing.T) { + const sampleUser1 = "sample-user-q01" + + mockctx := newMockProvider(t) + mockctx.VDRegistryValue = getMockDIDKeyVDR() + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + sampleNewUDCVc := strings.ReplaceAll(string(testdata.SampleUDCVC), + "http://example.edu/credentials/1872", "http://example.edu/credentials/18722") + + addContent(t, mockctx, &AddContentRequest{ + Content: []byte(sampleNewUDCVc), + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + + addContent(t, mockctx, &AddContentRequest{ + Content: testdata.SampleUDCVCWithProofBBS, + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + + t.Run("successfully query credentials", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ + Query: []*wallet.QueryParams{ + { + Type: "QueryByExample", + Query: []json.RawMessage{testdata.SampleWalletQueryByExample}, + }, + { + Type: "QueryByFrame", + Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, + }, + }, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + })) + require.NoError(t, cmdErr) + + var response map[string]interface{} + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response["results"]) + }) + + t.Run("query credentials with invalid auth", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ + Query: []*wallet.QueryParams{ + { + Type: "QueryByFrame", + Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, + }, + }, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, QueryWalletErrorCode, "invalid auth token") + }) + + t.Run("query credentials with invalid wallet profile", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ + Query: []*wallet.QueryParams{ + { + Type: "QueryByFrame", + Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, + }, + }, + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ExecuteError, QueryWalletErrorCode, "profile does not exist") + }) + + t.Run("query credentials with invalid query type", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ + Query: []*wallet.QueryParams{ + { + Type: "QueryByOrange", + Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, + }, + }, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + })) + validateError(t, cmdErr, command.ExecuteError, QueryWalletErrorCode, "unsupported query type") + }) + + t.Run("query credentials with invalid request", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Query(&b, bytes.NewBufferString("--")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + }) +} + +func TestCommand_IssueProveVerify(t *testing.T) { + const sampleUser1 = "sample-user-01" + + mockctx := newMockProvider(t) + mockctx.VDRegistryValue = getMockDIDKeyVDR() + + tcrypto, err := tinkcrypto.New() + require.NoError(t, err) + + mockctx.CryptoValue = tcrypto + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + addContent(t, mockctx, &AddContentRequest{ + Content: testdata.SampleWalletContentKeyBase58, + ContentType: wallet.Key, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + addContent(t, mockctx, &AddContentRequest{ + Content: testdata.SampleDocResolutionResponse, + ContentType: wallet.DIDResolutionResponse, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + + var rawCredentialToVerify json.RawMessage + + t.Run("issue a credential", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Issue(&b, getReader(t, &IssueRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Credential: testdata.SampleUDCVC, + ProofOptions: &wallet.ProofOptions{ + Controller: sampleDIDKey, + }, + })) + require.NoError(t, cmdErr) + + credentialIssued := parseCredential(t, b) + require.Len(t, credentialIssued.Proofs, 1) + b.Reset() + + rawCredentialToVerify, err = credentialIssued.MarshalJSON() + require.NoError(t, err) + }) + + // save it in store for next tests + addContent(t, mockctx, &AddContentRequest{ + Content: rawCredentialToVerify, + ContentType: wallet.Credential, + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + + t.Run("verify a credential from store", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + StoredCredentialID: "http://example.edu/credentials/1872", + })) + require.NoError(t, cmdErr) + + var response VerifyResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.True(t, response.Verified) + require.Empty(t, response.Error) + }) + + t.Run("verify a raw credential", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + RawCredential: rawCredentialToVerify, + })) + require.NoError(t, cmdErr) + + var response VerifyResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.True(t, response.Verified) + require.Empty(t, response.Error) + }) + + t.Run("verify a invalid credential", func(t *testing.T) { + // tamper a credential + invalidVC := string(rawCredentialToVerify) + invalidVC = strings.ReplaceAll(invalidVC, "Jayden Doe", "John Smith") + + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + RawCredential: []byte(invalidVC), + })) + require.NoError(t, cmdErr) + + var response VerifyResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.False(t, response.Verified) + require.NotEmpty(t, response.Error) + require.Contains(t, response.Error, "invalid signature") + }) + + var presentation *verifiable.Presentation + + t.Run("prove credentials", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + RawCredentials: []json.RawMessage{rawCredentialToVerify}, + StoredCredentials: []string{"http://example.edu/credentials/1872"}, + ProofOptions: &wallet.ProofOptions{ + Controller: sampleDIDKey, + }, + })) + require.NoError(t, cmdErr) + + presentation = parsePresentation(t, b) + require.NotEmpty(t, presentation.Proofs) + require.Len(t, presentation.Credentials(), 2) + require.Len(t, presentation.Proofs, 1) + b.Reset() + + // prove using raw presentation + rawPresentation, err := presentation.MarshalJSON() + require.NoError(t, err) + + cmdErr = cmd.Prove(&b, getReader(t, &ProveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + StoredCredentials: []string{"http://example.edu/credentials/1872"}, + Presentation: rawPresentation, + ProofOptions: &wallet.ProofOptions{ + Controller: sampleDIDKey, + }, + })) + require.NoError(t, cmdErr) + presentation2 := parsePresentation(t, b) + require.NotEmpty(t, presentation2.Proofs) + require.Len(t, presentation2.Credentials(), 3) + require.Len(t, presentation2.Proofs, 2) + }) + + t.Run("verify a raw presentation", func(t *testing.T) { + vpBytes, err := presentation.MarshalJSON() + require.NoError(t, err) + + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Presentation: vpBytes, + })) + require.NoError(t, cmdErr) + + var response VerifyResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.True(t, response.Verified) + require.Empty(t, response.Error) + b.Reset() + + // tamper it and try + invalidVP := string(vpBytes) + invalidVP = strings.ReplaceAll(invalidVP, "Jayden Doe", "John Smith") + + cmdErr = cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Presentation: []byte(invalidVP), + })) + require.NoError(t, cmdErr) + + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.False(t, response.Verified) + require.NotEmpty(t, response.Error) + require.Contains(t, response.Error, "invalid signature") + b.Reset() + }) + + t.Run("failed to prove a credential", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Issue(&b, getReader(t, &IssueRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Credential: testdata.SampleUDCVC, + ProofOptions: &wallet.ProofOptions{ + Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", + }, + })) + validateError(t, cmdErr, command.ExecuteError, IssueFromWalletErrorCode, "failed to prepare proof") + }) + + t.Run("failed to prove a credential", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + RawCredentials: []json.RawMessage{rawCredentialToVerify}, + StoredCredentials: []string{"http://example.edu/credentials/1872"}, + ProofOptions: &wallet.ProofOptions{ + Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", + }, + })) + validateError(t, cmdErr, command.ExecuteError, ProveFromWalletErrorCode, "failed to prepare proof") + }) + + t.Run("issue,prove,verify with invalid profile", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + const errMsg = "profile does not exist" + + cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, + StoredCredentials: []string{"http://example.edu/credentials/1877"}, + ProofOptions: &wallet.ProofOptions{ + Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", + }, + })) + validateError(t, cmdErr, command.ExecuteError, ProveFromWalletErrorCode, errMsg) + b.Reset() + + cmdErr = cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, + StoredCredentialID: "http://example.edu/credentials/1877", + })) + validateError(t, cmdErr, command.ExecuteError, VerifyFromWalletErrorCode, errMsg) + b.Reset() + + cmdErr = cmd.Issue(&b, getReader(t, &IssueRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, + Credential: testdata.SampleUDCVC, + ProofOptions: &wallet.ProofOptions{ + Controller: sampleDIDKey, + }, + })) + validateError(t, cmdErr, command.ExecuteError, IssueFromWalletErrorCode, errMsg) + b.Reset() + }) + + t.Run("issue,prove,verify with invalid auth", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + const errMsg = "invalid auth token" + + cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + StoredCredentials: []string{"http://example.edu/credentials/1877"}, + ProofOptions: &wallet.ProofOptions{ + Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", + }, + })) + validateError(t, cmdErr, command.ExecuteError, ProveFromWalletErrorCode, errMsg) + b.Reset() + + cmdErr = cmd.Issue(&b, getReader(t, &IssueRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + Credential: testdata.SampleUDCVC, + ProofOptions: &wallet.ProofOptions{ + Controller: sampleDIDKey, + }, + })) + validateError(t, cmdErr, command.ExecuteError, IssueFromWalletErrorCode, wallet.ErrWalletLocked.Error()) + b.Reset() + }) + + t.Run("issue,prove,verify with invalid request", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + })) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid option") + b.Reset() + + const errMsg = "invalid character" + + cmdErr = cmd.Prove(&b, bytes.NewBufferString("----")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, errMsg) + b.Reset() + + cmdErr = cmd.Verify(&b, bytes.NewBufferString("----")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, errMsg) + b.Reset() + + cmdErr = cmd.Issue(&b, bytes.NewBufferString("----")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, errMsg) + b.Reset() + }) +} + +func TestCommand_Derive(t *testing.T) { + const sampleUser1 = "sample-user-01" + + mockctx := newMockProvider(t) + mockctx.VDRegistryValue = getMockDIDKeyVDR() + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + addContent(t, mockctx, &AddContentRequest{ + Content: testdata.SampleUDCVCWithProofBBS, + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + + // prepare frame + var frameDoc map[string]interface{} + + require.NoError(t, json.Unmarshal(testdata.SampleFrame, &frameDoc)) + + t.Run("derive a credential from stored credential", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + StoredCredentialID: "http://example.edu/credentials/1872", + DeriveOptions: &wallet.DeriveOptions{ + Frame: frameDoc, + Nonce: uuid.New().String(), + }, + })) + require.NoError(t, cmdErr) + + var response DeriveResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response.Credential) + }) + + t.Run("derive a credential from raw credential", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + RawCredential: testdata.SampleUDCVCWithProofBBS, + DeriveOptions: &wallet.DeriveOptions{ + Frame: frameDoc, + Nonce: uuid.New().String(), + }, + })) + require.NoError(t, cmdErr) + + var response DeriveResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response.Credential) + }) + + t.Run("derive a credential using invalid auth", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + StoredCredentialID: "http://example.edu/credentials/1872", + DeriveOptions: &wallet.DeriveOptions{ + Frame: frameDoc, + Nonce: uuid.New().String(), + }, + })) + validateError(t, cmdErr, command.ExecuteError, DeriveFromWalletErrorCode, "invalid auth token") + require.Empty(t, b.Bytes()) + }) + + t.Run("derive a credential using invalid profile", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, + StoredCredentialID: "http://example.edu/credentials/1872", + DeriveOptions: &wallet.DeriveOptions{ + Frame: frameDoc, + Nonce: uuid.New().String(), + }, + })) + validateError(t, cmdErr, command.ExecuteError, DeriveFromWalletErrorCode, "profile does not exist") + require.Empty(t, b.Bytes()) + }) + + t.Run("derive a credential using invalid request", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + var b bytes.Buffer + + cmdErr := cmd.Derive(&b, bytes.NewBufferString("--")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + b.Reset() + + cmdErr = cmd.Derive(&b, getReader(t, &DeriveRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + DeriveOptions: &wallet.DeriveOptions{ + Frame: frameDoc, + Nonce: uuid.New().String(), + }, + })) + validateError(t, cmdErr, command.ExecuteError, DeriveFromWalletErrorCode, "failed to resolve request") + require.Empty(t, b.Bytes()) + }) +} + +func TestCommand_CreateKeyPair(t *testing.T) { + const sampleUser1 = "sample-user-01" + + mockctx := newMockProvider(t) + mockctx.VDRegistryValue = getMockDIDKeyVDR() + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + t.Run("successfully create key pair (local kms)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + request := &CreateKeyPairRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + KeyType: kms.ED25519, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.CreateKeyPair(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response CreateKeyPairResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response.KeyID) + require.NotEmpty(t, response.PublicKey) + }) + + t.Run("create key pair using invalid auth (local kms)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + request := &CreateKeyPairRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, + KeyType: kms.ED25519, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.CreateKeyPair(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, CreateKeyPairFromWalletErrorCode, "invalid auth token") + require.Empty(t, b.Bytes()) + }) + + t.Run("create key pair using invalid request (local kms)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.CreateKeyPair(&b, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + }) + + t.Run("create key pair using invalid profile (local kms)", func(t *testing.T) { + cmd := New(mockctx, &Config{}) + + request := &CreateKeyPairRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, + KeyType: kms.ED25519, + } + + // unlock wallet + var b bytes.Buffer + cmdErr := cmd.CreateKeyPair(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, CreateKeyPairFromWalletErrorCode, "failed to get VC wallet profile") + require.Empty(t, b.Bytes()) + }) +} + +func createSampleUserProfile(t *testing.T, ctx *mockprovider.Provider, request *CreateOrUpdateProfileRequest) { + cmd := New(ctx, &Config{}) + require.NotNil(t, cmd) + + var l bytes.Buffer + cmdErr := cmd.CreateProfile(&l, getReader(t, request)) + require.NoError(t, cmdErr) +} + +func getReader(t *testing.T, v interface{}) io.Reader { + vcReqBytes, err := json.Marshal(v) + require.NoError(t, err) + + return bytes.NewBuffer(vcReqBytes) +} + +func getUnlockToken(t *testing.T, b bytes.Buffer) string { + var response UnlockWalletResponse + + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + + return response.Token +} + +func unlockWallet(t *testing.T, ctx *mockprovider.Provider, request *UnlockWalletRequest) (string, func()) { + cmd := New(ctx, nil) + + var b bytes.Buffer + + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + return getUnlockToken(t, b), func() { + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: request.UserID})) + if cmdErr != nil { + t.Log(t, cmdErr) + } + } +} + +func addContent(t *testing.T, ctx *mockprovider.Provider, request *AddContentRequest) { + cmd := New(ctx, &Config{}) + + var b bytes.Buffer + defer b.Reset() + + cmdErr := cmd.Add(&b, getReader(t, &request)) + require.NoError(t, cmdErr) +} + +func validateError(t *testing.T, err command.Error, + expectedType command.Type, expectedCode command.Code, contains string) { + require.Error(t, err) + require.Equal(t, err.Type(), expectedType) + require.Equal(t, err.Code(), expectedCode) + + if contains != "" { + require.Contains(t, err.Error(), contains) + } +} + +func newMockProvider(t *testing.T) *mockprovider.Provider { + t.Helper() + + loader, err := ldtestutil.DocumentLoader() + require.NoError(t, err) + + serviceMap := map[string]interface{}{ + presentproofSvc.Name: &mockpresentproof.MockPresentProofSvc{}, + outofbandSvc.Name: &mockoutofband.MockOobService{}, + didexchange.DIDExchange: &mockdidexchange.MockDIDExchangeSvc{}, + mediator.Coordination: &mockmediator.MockMediatorSvc{}, + issuecredentialsvc.Name: &mockissuecredential.MockIssueCredentialSvc{}, + oobv2.Name: &mockoutofbandv2.MockOobService{}, + } + + return &mockprovider.Provider{ + StorageProviderValue: mockstorage.NewMockStoreProvider(), + ProtocolStateStorageProviderValue: mockstorage.NewMockStoreProvider(), + DocumentLoaderValue: loader, + ServiceMap: serviceMap, + } +} + +func getMockDIDKeyVDR() *mockvdr.MockVDRegistry { + return &mockvdr.MockVDRegistry{ + ResolveFunc: func(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { + if strings.HasPrefix(didID, "did:key:") { + k := key.New() + + d, e := k.Read(didID) + if e != nil { + return nil, e + } + + return d, nil + } + + return nil, fmt.Errorf("did not found") + }, + } +} + +func parseCredential(t *testing.T, b bytes.Buffer) *verifiable.Credential { + var response struct { + Credential json.RawMessage + } + + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + + loader, err := ldtestutil.DocumentLoader() + require.NoError(t, err) + + vc, err := verifiable.ParseCredential(response.Credential, verifiable.WithDisabledProofCheck(), + verifiable.WithJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + return vc +} + +func parsePresentation(t *testing.T, b bytes.Buffer) *verifiable.Presentation { + var response struct { + Presentation json.RawMessage + } + + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + + loader, err := ldtestutil.DocumentLoader() + require.NoError(t, err) + + vp, err := verifiable.ParsePresentation(response.Presentation, verifiable.WithPresDisabledProofCheck(), + verifiable.WithPresJSONLDDocumentLoader(loader)) + require.NoError(t, err) + + return vp +} + +// mock authz capability. +type mockAuthZCapability struct{} + +// GetHeaderSigner mock implementation. +func (s *mockAuthZCapability) GetHeaderSigner(authzKeyStoreURL, accessToken, secretShare string) HTTPHeaderSigner { + return &mockHeaderSigner{} +} + +// mock header signer. +type mockHeaderSigner struct{} + +// SignHeader mock implementation. +func (s *mockHeaderSigner) SignHeader(req *http.Request, capabilityBytes []byte) (*http.Header, error) { + return &http.Header{}, nil +} diff --git a/pkg/controller/command/basewallet/models.go b/pkg/controller/command/basewallet/models.go new file mode 100644 index 0000000000..9bb9a2af5d --- /dev/null +++ b/pkg/controller/command/basewallet/models.go @@ -0,0 +1,312 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package basewallet + +import ( + "encoding/json" + "time" + + "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" + "github.com/hyperledger/aries-framework-go/pkg/kms" + "github.com/hyperledger/aries-framework-go/pkg/wallet" +) + +// CreateOrUpdateProfileRequest is request model for +// creating a new wallet profile or updating an existing wallet profile. +type CreateOrUpdateProfileRequest struct { + // Unique identifier to identify wallet user + UserID string `json:"userID"` + + // passphrase for local kms for key operations. + // Optional, if this option is provided then wallet for this profile will use local KMS for key operations. + LocalKMSPassphrase string `json:"localKMSPassphrase,omitempty"` + + // passphrase for web/remote kms for key operations. + // Optional, if this option is provided then wallet for this profile will use web/remote KMS for key operations. + KeyStoreURL string `json:"keyStoreURL,omitempty"` + + // edv configuration for storing wallet contents for this profile + // Optional, if not provided then agent storage provider will be used as store provider. + EDVConfiguration *EDVConfiguration `json:"edvConfiguration,omitempty"` +} + +// EDVConfiguration contains configuration for EDV settings for profile creation. +type EDVConfiguration struct { + // EDV server URL for storing wallet contents. + ServerURL string `json:"serverURL,omitempty"` + + // EDV vault ID for storing the wallet contents. + VaultID string `json:"vaultID,omitempty"` + + // Encryption key ID of already existing key in wallet profile kms. + // If profile is using localkms then wallet will create this key set for wallet user. + EncryptionKeyID string `json:"encryptionKID,omitempty"` + + // MAC operation key ID of already existing key in wallet profile kms. + // If profile is using localkms then wallet will create this key set for wallet user. + MACKeyID string `json:"macKID,omitempty"` +} + +// UnlockWalletRequest contains different options for unlocking wallet. +type UnlockWalletRequest struct { + // user ID of the wallet to be unlocked. + UserID string `json:"userID"` + + // passphrase for local kms for key operations. + // Optional, to be used if profile for this wallet user is setup with local KMS. + LocalKMSPassphrase string `json:"localKMSPassphrase,omitempty"` + + // WebKMSAuth for authorizing acccess to web/remote kms. + // Optional, to be used if profile for this wallet user is setup with web/remote KMS. + WebKMSAuth *UnlockAuth `json:"webKMSAuth"` + + // Options for authorizing access to wallet's EDV content store. + // Optional, to be used only if profile for this wallet user is setup to use EDV as content store. + EDVUnlock *UnlockAuth `json:"edvUnlocks"` + + // Time duration in milliseconds after which wallet will expire its unlock status. + Expiry time.Duration `json:"expiry,omitempty"` +} + +// UnlockAuth contains different options for authorizing access to wallet's EDV content store & webkms. +type UnlockAuth struct { + // Http header 'authorization' bearer token to be used. + // Optional, only if required by wallet user (for webkms or edv). + AuthToken string `json:"authToken,omitempty"` + + // Http header 'authorization' GNAP token to be used. + // Optional, only if required by wallet user (for webkms or edv). + GNAPToken string `json:"gnapToken,omitempty"` + + // Capability if ZCAP sign header feature to be used for authorizing access. + // Optional, can be used only if ZCAP sign header feature is configured with command controller. + Capability string `json:"capability,omitempty"` + + // AuthZKeyStoreURL if ZCAP sign header feature to be used for authorizing access. + // Optional, can be used only if ZCAP sign header feature is configured with command controller. + AuthZKeyStoreURL string `json:"authzKeyStoreURL,omitempty"` + + // SecretShare if ZCAP sign header feature to be used for authorizing access. + // Optional, can be used only if ZCAP sign header feature is configured with command controller. + SecretShare string `json:"secretShare,omitempty"` +} + +// UnlockWalletResponse contains response for wallet unlock operation. +type UnlockWalletResponse struct { + // Token for granting access to wallet for subsequent wallet operations. + Token string `json:"token,omitempty"` +} + +// LockWalletRequest contains options for locking wallet. +type LockWalletRequest struct { + // user ID of the wallet to be locked. + UserID string `json:"userID"` +} + +// LockWalletResponse contains response for wallet lock operation. +type LockWalletResponse struct { + // Closed status of the wallet lock operation. + // if true, wallet is closed successfully + // if false, wallet is already closed or never unlocked. + Closed bool `json:"closed"` +} + +// WalletAuth contains wallet auth parameters for performing wallet operations. +type WalletAuth struct { + // Authorization token for performing wallet operations. + Auth string `json:"auth"` + + // ID of wallet user. + UserID string `json:"userID"` +} + +// WalletUser contains wallet user info for performing profile operations. +type WalletUser struct { + // ID of wallet user. + ID string `json:"userID"` +} + +// AddContentRequest is request for adding a content to wallet. +type AddContentRequest struct { + WalletAuth + + // type of the content to be added to the wallet. + // supported types: collection, credential, didResolutionResponse, metadata, connection, key + ContentType wallet.ContentType `json:"contentType"` + + // content to be added to wallet content store. + Content json.RawMessage `json:"content"` + + // ID of the wallet collection to which this content should belong. + CollectionID string `json:"collectionID"` +} + +// RemoveContentRequest is request for removing a content from wallet. +type RemoveContentRequest struct { + WalletAuth + + // type of the content to be removed from the wallet. + // supported types: collection, credential, didResolutionResponse, metadata, connection + ContentType wallet.ContentType `json:"contentType"` + + // ID of the content to be removed from wallet + ContentID string `json:"contentID"` +} + +// GetContentRequest is request for getting a content from wallet. +type GetContentRequest struct { + WalletAuth + + // type of the content to be returned from wallet. + // supported types: collection, credential, didResolutionResponse, metadata, connection + ContentType wallet.ContentType `json:"contentType"` + + // ID of the content to be returned from wallet + ContentID string `json:"contentID"` +} + +// GetContentResponse response for get content from wallet operation. +type GetContentResponse struct { + // content retrieved from wallet content store. + Content json.RawMessage `json:"content"` +} + +// GetAllContentRequest is request for getting all contents from wallet for given content type. +type GetAllContentRequest struct { + WalletAuth + + // type of the contents to be returned from wallet. + // supported types: collection, credential, didResolutionResponse, metadata, connection + ContentType wallet.ContentType `json:"contentType"` + + // ID of the collection on which the response contents to be filtered. + CollectionID string `json:"collectionID,omitempty"` +} + +// GetAllContentResponse response for get all content by content type wallet operation. +type GetAllContentResponse struct { + // contents retrieved from wallet content store. + // map of content ID to content. + Contents map[string]json.RawMessage `json:"contents"` +} + +// ContentQueryRequest is request model for querying wallet contents. +type ContentQueryRequest struct { + WalletAuth + + // credential query(s) for querying wallet contents. + Query []*wallet.QueryParams `json:"query"` +} + +// ContentQueryResponse response for wallet content query. +type ContentQueryResponse struct { + // response presentation(s) containing query results. + Results []*verifiable.Presentation `json:"results"` +} + +// IssueRequest is request model for issuing credential from wallet. +type IssueRequest struct { + WalletAuth + + // raw credential to be issued from wallet. + Credential json.RawMessage `json:"credential"` + + // proof options for issuing credential + ProofOptions *wallet.ProofOptions `json:"proofOptions"` +} + +// IssueResponse is response for issue credential interface from wallet. +type IssueResponse struct { + // credential issued. + Credential *verifiable.Credential `json:"credential"` +} + +// ProveRequest for producing verifiable presentation from wallet. +// Contains options for proofs and credential. Any combination of credential option can be mixed. +type ProveRequest struct { + WalletAuth + + // IDs of credentials already saved in wallet content store. + StoredCredentials []string `json:"storedCredentials"` + + // List of raw credentials to be presented. + RawCredentials []json.RawMessage `json:"rawCredentials"` + + // Presentation to be proved. + Presentation json.RawMessage `json:"presentation"` + + // proof options for issuing credential. + ProofOptions *wallet.ProofOptions `json:"proofOptions"` +} + +// ProveResponse contains response presentation from prove operation. +type ProveResponse struct { + // presentation response from prove operation. + Presentation *verifiable.Presentation `json:"presentation"` +} + +// VerifyRequest request for verifying a credential or presentation from wallet. +// Any one of the credential option should be used. +type VerifyRequest struct { + WalletAuth + + // ID of the credential already saved in wallet content store. + // optional, if provided then this option takes precedence over other options. + StoredCredentialID string `json:"storedCredentialID"` + + // List of raw credential to be presented. + // optional, if provided then this option takes precedence over presentation options. + RawCredential json.RawMessage `json:"rawCredential"` + + // Presentation to be proved. + // optional, will be used only if other options are not provided. + Presentation json.RawMessage `json:"presentation"` +} + +// VerifyResponse is response model for wallet verify operation. +type VerifyResponse struct { + // if true then verification is successful. + Verified bool `json:"verified"` + + // error details if verified is false. + Error string `json:"error,omitempty"` +} + +// DeriveRequest is request model for deriving a credential from wallet. +type DeriveRequest struct { + WalletAuth + + // ID of the credential already saved in wallet content store. + // optional, if provided then this option takes precedence. + StoredCredentialID string `json:"storedCredentialID"` + + // List of raw credential to be presented. + // optional, will be used only if other options is not provided. + RawCredential json.RawMessage `json:"rawCredential"` + + // DeriveOptions options for deriving credential + *wallet.DeriveOptions `json:"deriveOption"` +} + +// DeriveResponse is response for derived credential operation. +type DeriveResponse struct { + // credential derived. + Credential *verifiable.Credential `json:"credential"` +} + +// CreateKeyPairRequest is request model for creating key pair from wallet. +type CreateKeyPairRequest struct { + WalletAuth + + // type of the key to be created. + KeyType kms.KeyType `json:"keyType,omitempty"` +} + +// CreateKeyPairResponse is response model for creating key pair from wallet. +type CreateKeyPairResponse struct { + *wallet.KeyPair +} diff --git a/pkg/controller/command/vcwallet/command.go b/pkg/controller/command/vcwallet/command.go index beb9345e04..7b7f483e57 100644 --- a/pkg/controller/command/vcwallet/command.go +++ b/pkg/controller/command/vcwallet/command.go @@ -7,25 +7,14 @@ SPDX-License-Identifier: Apache-2.0 package vcwallet import ( - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "time" - "github.com/piprate/json-gold/ld" - "github.com/hyperledger/aries-framework-go/component/storage/edv" "github.com/hyperledger/aries-framework-go/pkg/common/log" "github.com/hyperledger/aries-framework-go/pkg/controller/command" - "github.com/hyperledger/aries-framework-go/pkg/controller/internal/cmdutil" + "github.com/hyperledger/aries-framework-go/pkg/controller/command/basewallet" "github.com/hyperledger/aries-framework-go/pkg/crypto" "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" - "github.com/hyperledger/aries-framework-go/pkg/internal/logutil" "github.com/hyperledger/aries-framework-go/pkg/kms" - "github.com/hyperledger/aries-framework-go/pkg/kms/webkms" - "github.com/hyperledger/aries-framework-go/pkg/wallet" "github.com/hyperledger/aries-framework-go/spi/storage" ) @@ -37,49 +26,49 @@ const ( InvalidRequestErrorCode = command.Code(iota + command.VCWallet) // CreateProfileErrorCode for errors during create wallet profile operations. - CreateProfileErrorCode + CreateProfileErrorCode = basewallet.CreateProfileErrorCode // UpdateProfileErrorCode for errors during update wallet profile operations. - UpdateProfileErrorCode + UpdateProfileErrorCode = basewallet.UpdateProfileErrorCode // OpenWalletErrorCode for errors during wallet unlock operations. - OpenWalletErrorCode + OpenWalletErrorCode = basewallet.OpenWalletErrorCode // CloseWalletErrorCode for errors during wallet lock operations. - CloseWalletErrorCode + CloseWalletErrorCode = basewallet.CloseWalletErrorCode // AddToWalletErrorCode for errors while adding contents to wallet. - AddToWalletErrorCode + AddToWalletErrorCode = basewallet.AddToWalletErrorCode // RemoveFromWalletErrorCode for errors while removing contents from wallet. - RemoveFromWalletErrorCode + RemoveFromWalletErrorCode = basewallet.RemoveFromWalletErrorCode // GetFromWalletErrorCode for errors while getting a content from wallet. - GetFromWalletErrorCode + GetFromWalletErrorCode = basewallet.GetFromWalletErrorCode // GetAllFromWalletErrorCode for errors while getting all contents from wallet. - GetAllFromWalletErrorCode + GetAllFromWalletErrorCode = basewallet.GetAllFromWalletErrorCode // QueryWalletErrorCode for errors while querying credentials contents from wallet. - QueryWalletErrorCode + QueryWalletErrorCode = basewallet.QueryWalletErrorCode // IssueFromWalletErrorCode for errors while issuing a credential from wallet. - IssueFromWalletErrorCode + IssueFromWalletErrorCode = basewallet.IssueFromWalletErrorCode // ProveFromWalletErrorCode for errors while producing a presentation from wallet. - ProveFromWalletErrorCode + ProveFromWalletErrorCode = basewallet.ProveFromWalletErrorCode // VerifyFromWalletErrorCode for errors while verifying a presentation or credential from wallet. - VerifyFromWalletErrorCode + VerifyFromWalletErrorCode = basewallet.VerifyFromWalletErrorCode // DeriveFromWalletErrorCode for errors while deriving a credential from wallet. - DeriveFromWalletErrorCode + DeriveFromWalletErrorCode = basewallet.DeriveFromWalletErrorCode // CreateKeyPairFromWalletErrorCode for errors while creating key pair from wallet. - CreateKeyPairFromWalletErrorCode + CreateKeyPairFromWalletErrorCode = basewallet.CreateKeyPairFromWalletErrorCode // ProfileExistsErrorCode for errors while checking if profile exists for a wallet user. - ProfileExistsErrorCode + ProfileExistsErrorCode = basewallet.ProfileExistsErrorCode // DIDConnectErrorCode for errors while performing DID connect in wallet. DIDConnectErrorCode @@ -105,21 +94,22 @@ const ( CommandName = "vcwallet" // command methods. - CreateProfileMethod = "CreateProfile" - UpdateProfileMethod = "UpdateProfile" - ProfileExistsMethod = "ProfileExists" - OpenMethod = "Open" - CloseMethod = "Close" - AddMethod = "Add" - RemoveMethod = "Remove" - GetMethod = "Get" - GetAllMethod = "GetAll" - QueryMethod = "Query" - IssueMethod = "Issue" - ProveMethod = "Prove" - VerifyMethod = "Verify" - DeriveMethod = "Derive" - CreateKeyPairMethod = "CreateKeyPair" + CreateProfileMethod = basewallet.CreateProfileMethod + UpdateProfileMethod = basewallet.UpdateProfileMethod + ProfileExistsMethod = basewallet.ProfileExistsMethod + OpenMethod = basewallet.OpenMethod + CloseMethod = basewallet.CloseMethod + AddMethod = basewallet.AddMethod + RemoveMethod = basewallet.RemoveMethod + GetMethod = basewallet.GetMethod + GetAllMethod = basewallet.GetAllMethod + QueryMethod = basewallet.QueryMethod + IssueMethod = basewallet.IssueMethod + ProveMethod = basewallet.ProveMethod + VerifyMethod = basewallet.VerifyMethod + DeriveMethod = basewallet.DeriveMethod + CreateKeyPairMethod = basewallet.CreateKeyPairMethod + ConnectMethod = "Connect" ProposePresentationMethod = "ProposePresentation" PresentProofMethod = "PresentProof" @@ -138,50 +128,18 @@ const ( LabelString = "label" emptyRawLength = 4 - - defaultTokenExpiry = 5 * time.Minute ) // AuthCapabilityProvider is for providing Authorization Capabilities (ZCAP-LD) feature for // wallet's EDV and WebKMS components. -type AuthCapabilityProvider interface { - // Returns HTTP Header Signer. - GetHeaderSigner(authzKeyStoreURL, accessToken, secretShare string) HTTPHeaderSigner -} +type AuthCapabilityProvider = basewallet.AuthCapabilityProvider // HTTPHeaderSigner is for http header signing, typically used for zcapld functionality. -type HTTPHeaderSigner interface { - // SignHeader header with capability. - SignHeader(req *http.Request, capabilityBytes []byte) (*http.Header, error) -} - -// GNAPHeaderSigner signs a request using GNAP, for resource server access authorization. -type GNAPHeaderSigner func(req *http.Request) (*http.Header, error) +type HTTPHeaderSigner = basewallet.HTTPHeaderSigner // Config contains properties to customize verifiable credential wallet controller. // All properties of this config are optional, but they can be used to customize wallet's webkms and edv client's. -type Config struct { - // EDV header signer, typically used for introducing zcapld feature. - EdvAuthzProvider AuthCapabilityProvider - // Web KMS header signer, typically used for introducing zcapld feature. - WebKMSAuthzProvider AuthCapabilityProvider - // Web KMS header signer for GNAP authorization. - WebKMSGNAPSigner GNAPHeaderSigner - // EDV header signer for GNAP authorization. - EDVGNAPSigner GNAPHeaderSigner - // option is a performance optimization that speeds up queries by getting full documents from - // the EDV server instead of only document locations. - EDVReturnFullDocumentsOnQuery bool - // this EDV option is a performance optimization that allows for restStore.Batch to only require one REST call. - EDVBatchEndpointExtensionEnabled bool - // Aries Web KMS cache size configuration. - WebKMSCacheSize int - // Default token expiry for all wallet profiles created. - // Will be used only if wallet unlock request doesn't supply default timeout value. - DefaultTokenExpiry time.Duration - // Indicate if a data model of json-ld content stored in the wallet should be validated. - ValidateDataModel bool -} +type Config = basewallet.Config // provider contains dependencies for the verifiable credential wallet command controller // and is typically created by using aries.Context(). @@ -206,22 +164,17 @@ type didCommProvider interface { KeyAgreementType() kms.KeyType } -// Command contains operations provided by verifiable credential wallet controller. +// Command extends basewallet.Command to add didComm functionality. type Command struct { - ctx provider - config *Config + baseWallet *basewallet.Command + didComm *CommandDidComm } // New returns new verifiable credential wallet controller command instance. func New(p provider, config *Config) *Command { - cmd := &Command{ctx: p, config: &Config{}} - - if config != nil { - cmd.config = config - } - - if cmd.config.DefaultTokenExpiry == 0 { - cmd.config.DefaultTokenExpiry = defaultTokenExpiry + cmd := &Command{ + baseWallet: basewallet.New(p, config), + didComm: NewDidCommCommand(p), } return cmd @@ -229,1000 +182,5 @@ func New(p provider, config *Config) *Command { // GetHandlers returns list of all commands supported by this controller command. func (o *Command) GetHandlers() []command.Handler { - return []command.Handler{ - cmdutil.NewCommandHandler(CommandName, CreateProfileMethod, o.CreateProfile), - cmdutil.NewCommandHandler(CommandName, UpdateProfileMethod, o.UpdateProfile), - cmdutil.NewCommandHandler(CommandName, ProfileExistsMethod, o.ProfileExists), - cmdutil.NewCommandHandler(CommandName, OpenMethod, o.Open), - cmdutil.NewCommandHandler(CommandName, CloseMethod, o.Close), - cmdutil.NewCommandHandler(CommandName, AddMethod, o.Add), - cmdutil.NewCommandHandler(CommandName, RemoveMethod, o.Remove), - cmdutil.NewCommandHandler(CommandName, GetMethod, o.Get), - cmdutil.NewCommandHandler(CommandName, GetAllMethod, o.GetAll), - cmdutil.NewCommandHandler(CommandName, QueryMethod, o.Query), - cmdutil.NewCommandHandler(CommandName, IssueMethod, o.Issue), - cmdutil.NewCommandHandler(CommandName, ProveMethod, o.Prove), - cmdutil.NewCommandHandler(CommandName, VerifyMethod, o.Verify), - cmdutil.NewCommandHandler(CommandName, DeriveMethod, o.Derive), - cmdutil.NewCommandHandler(CommandName, CreateKeyPairMethod, o.CreateKeyPair), - cmdutil.NewCommandHandler(CommandName, ConnectMethod, o.Connect), - cmdutil.NewCommandHandler(CommandName, ProposePresentationMethod, o.ProposePresentation), - cmdutil.NewCommandHandler(CommandName, PresentProofMethod, o.PresentProof), - cmdutil.NewCommandHandler(CommandName, ProposeCredentialMethod, o.ProposeCredential), - cmdutil.NewCommandHandler(CommandName, RequestCredentialMethod, o.RequestCredential), - cmdutil.NewCommandHandler(CommandName, ResolveCredentialManifestMethod, o.ResolveCredentialManifest), - } -} - -// CreateProfile creates new wallet profile for given user. -func (o *Command) CreateProfile(rw io.Writer, req io.Reader) command.Error { - request := &CreateOrUpdateProfileRequest{} - - err := json.NewDecoder(req).Decode(request) - if err != nil { - logutil.LogInfo(logger, CommandName, CreateProfileMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - // create profile. - err = wallet.CreateProfile(request.UserID, o.ctx, prepareProfileOptions(request)...) - if err != nil { - logutil.LogInfo(logger, CommandName, CreateProfileMethod, err.Error()) - - return command.NewExecuteError(CreateProfileErrorCode, err) - } - - // create EDV keys if profile is using local kms. - if request.LocalKMSPassphrase != "" && request.EDVConfiguration != nil { - err = wallet.CreateDataVaultKeyPairs(request.UserID, o.ctx, wallet.WithUnlockByPassphrase(request.LocalKMSPassphrase)) - if err != nil { - logutil.LogInfo(logger, CommandName, CreateProfileMethod, err.Error()) - - return command.NewExecuteError(CreateProfileErrorCode, err) - } - } - - logutil.LogDebug(logger, CommandName, CreateProfileMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// UpdateProfile updates an existing wallet profile for given user. -func (o *Command) UpdateProfile(rw io.Writer, req io.Reader) command.Error { - request := &CreateOrUpdateProfileRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, UpdateProfileMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - // update profile. - err = wallet.UpdateProfile(request.UserID, o.ctx, prepareProfileOptions(request)...) - if err != nil { - logutil.LogInfo(logger, CommandName, UpdateProfileMethod, err.Error()) - - return command.NewExecuteError(UpdateProfileErrorCode, err) - } - - logutil.LogDebug(logger, CommandName, UpdateProfileMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// ProfileExists checks if wallet profile exists for given wallet user. -func (o *Command) ProfileExists(rw io.Writer, req io.Reader) command.Error { - user := &WalletUser{} - - err := json.NewDecoder(req).Decode(&user) - if err != nil { - logutil.LogInfo(logger, CommandName, ProfileExistsMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - err = wallet.ProfileExists(user.ID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ProfileExistsMethod, err.Error()) - - return command.NewExecuteError(ProfileExistsErrorCode, err) - } - - logutil.LogDebug(logger, CommandName, ProfileExistsMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, user.ID)) - - return nil -} - -// Open unlocks given user's wallet and returns a token for subsequent use of wallet features. -func (o *Command) Open(rw io.Writer, req io.Reader) command.Error { - request := &UnlockWalletRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - opts, err := prepareUnlockOptions(request, o.config) - if err != nil { - logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) - - return command.NewExecuteError(OpenWalletErrorCode, err) - } - - token, err := vcWallet.Open(opts...) - if err != nil { - logutil.LogInfo(logger, CommandName, OpenMethod, err.Error()) - - return command.NewExecuteError(OpenWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, UnlockWalletResponse{Token: token}, logger) - - logutil.LogDebug(logger, CommandName, OpenMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Close locks given user's wallet. -func (o *Command) Close(rw io.Writer, req io.Reader) command.Error { - request := &LockWalletRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, CloseMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, CloseMethod, err.Error()) - - return command.NewExecuteError(CloseWalletErrorCode, err) - } - - closed := vcWallet.Close() - - command.WriteNillableResponse(rw, LockWalletResponse{Closed: closed}, logger) - - logutil.LogDebug(logger, CommandName, CloseMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Add adds given data model to wallet content store. -func (o *Command) Add(rw io.Writer, req io.Reader) command.Error { - request := &AddContentRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, AddMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, AddMethod, err.Error()) - - return command.NewExecuteError(AddToWalletErrorCode, err) - } - - addOpts := []wallet.AddContentOptions{ - wallet.AddByCollection(request.CollectionID), - } - - if o.config.ValidateDataModel { - addOpts = append(addOpts, wallet.ValidateContent()) - } - - err = vcWallet.Add(request.Auth, request.ContentType, request.Content, addOpts...) - if err != nil { - logutil.LogInfo(logger, CommandName, AddMethod, err.Error()) - - return command.NewExecuteError(AddToWalletErrorCode, err) - } - - logutil.LogDebug(logger, CommandName, AddMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Remove deletes given content from wallet content store. -func (o *Command) Remove(rw io.Writer, req io.Reader) command.Error { - request := &RemoveContentRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, RemoveMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, RemoveMethod, err.Error()) - - return command.NewExecuteError(RemoveFromWalletErrorCode, err) - } - - err = vcWallet.Remove(request.Auth, request.ContentType, request.ContentID) - if err != nil { - logutil.LogInfo(logger, CommandName, RemoveMethod, err.Error()) - - return command.NewExecuteError(RemoveFromWalletErrorCode, err) - } - - logutil.LogDebug(logger, CommandName, RemoveMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Get returns wallet content by ID from wallet content store. -func (o *Command) Get(rw io.Writer, req io.Reader) command.Error { - request := &GetContentRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, GetMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, GetMethod, err.Error()) - - return command.NewExecuteError(GetFromWalletErrorCode, err) - } - - content, err := vcWallet.Get(request.Auth, request.ContentType, request.ContentID) - if err != nil { - logutil.LogInfo(logger, CommandName, GetMethod, err.Error()) - - return command.NewExecuteError(GetFromWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, GetContentResponse{Content: content}, logger) - - logutil.LogDebug(logger, CommandName, GetMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// GetAll gets all wallet content from wallet content store for given type. -func (o *Command) GetAll(rw io.Writer, req io.Reader) command.Error { - request := &GetAllContentRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) - - return command.NewExecuteError(GetAllFromWalletErrorCode, err) - } - - contents, err := vcWallet.GetAll(request.Auth, request.ContentType, - wallet.FilterByCollection(request.CollectionID)) - if err != nil { - logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) - - return command.NewExecuteError(GetAllFromWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, GetAllContentResponse{Contents: contents}, logger) - - logutil.LogDebug(logger, CommandName, GetAllMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Query runs credential queries against wallet credential contents and -// returns presentation containing credential results. -func (o *Command) Query(rw io.Writer, req io.Reader) command.Error { - request := &ContentQueryRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) - - return command.NewExecuteError(QueryWalletErrorCode, err) - } - - presentations, err := vcWallet.Query(request.Auth, request.Query...) - if err != nil { - logutil.LogInfo(logger, CommandName, GetAllMethod, err.Error()) - - return command.NewExecuteError(QueryWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, &ContentQueryResponse{Results: presentations}, logger) - - logutil.LogDebug(logger, CommandName, GetAllMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Issue adds proof to a Verifiable Credential from wallet. -func (o *Command) Issue(rw io.Writer, req io.Reader) command.Error { - request := &IssueRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, IssueMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, IssueMethod, err.Error()) - - return command.NewExecuteError(IssueFromWalletErrorCode, err) - } - - credential, err := vcWallet.Issue(request.Auth, request.Credential, request.ProofOptions) - if err != nil { - logutil.LogInfo(logger, CommandName, IssueMethod, err.Error()) - - return command.NewExecuteError(IssueFromWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, &IssueResponse{Credential: credential}, logger) - - logutil.LogDebug(logger, CommandName, IssueMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Prove produces a Verifiable Presentation from wallet. -func (o *Command) Prove(rw io.Writer, req io.Reader) command.Error { - request := &ProveRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, ProveMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ProveMethod, err.Error()) - - return command.NewExecuteError(ProveFromWalletErrorCode, err) - } - - vp, err := vcWallet.Prove(request.Auth, request.ProofOptions, prepareProveOptions(request)...) - if err != nil { - logutil.LogInfo(logger, CommandName, ProveMethod, err.Error()) - - return command.NewExecuteError(ProveFromWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, &ProveResponse{Presentation: vp}, logger) - - logutil.LogDebug(logger, CommandName, ProveMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Verify verifies credential/presentation from wallet. -func (o *Command) Verify(rw io.Writer, req io.Reader) command.Error { - request := &VerifyRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, VerifyMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, VerifyMethod, err.Error()) - - return command.NewExecuteError(VerifyFromWalletErrorCode, err) - } - - option, err := prepareVerifyOption(request) - if err != nil { - logutil.LogInfo(logger, CommandName, VerifyMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - verified, err := vcWallet.Verify(request.Auth, option) - - response := &VerifyResponse{Verified: verified} - - if err != nil { - response.Error = err.Error() - } - - command.WriteNillableResponse(rw, response, logger) - - logutil.LogDebug(logger, CommandName, VerifyMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Derive derives a credential from wallet. -func (o *Command) Derive(rw io.Writer, req io.Reader) command.Error { - request := &DeriveRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, DeriveMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, DeriveMethod, err.Error()) - - return command.NewExecuteError(DeriveFromWalletErrorCode, err) - } - - derived, err := vcWallet.Derive(request.Auth, prepareDeriveOption(request), request.DeriveOptions) - if err != nil { - logutil.LogInfo(logger, CommandName, DeriveMethod, err.Error()) - - return command.NewExecuteError(DeriveFromWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, &DeriveResponse{Credential: derived}, logger) - - logutil.LogDebug(logger, CommandName, DeriveMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// CreateKeyPair creates key pair from wallet. -func (o *Command) CreateKeyPair(rw io.Writer, req io.Reader) command.Error { - request := &CreateKeyPairRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, CreateKeyPairMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, CreateKeyPairMethod, err.Error()) - - return command.NewExecuteError(CreateKeyPairFromWalletErrorCode, err) - } - - response, err := vcWallet.CreateKeyPair(request.Auth, request.KeyType) - if err != nil { - logutil.LogInfo(logger, CommandName, CreateKeyPairMethod, err.Error()) - - return command.NewExecuteError(CreateKeyPairFromWalletErrorCode, err) - } - - command.WriteNillableResponse(rw, &CreateKeyPairResponse{response}, logger) - - logutil.LogDebug(logger, CommandName, CreateKeyPairMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// Connect accepts out-of-band invitations and performs DID exchange. -func (o *Command) Connect(rw io.Writer, req io.Reader) command.Error { - request := &ConnectRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) - - return command.NewExecuteError(DIDConnectErrorCode, err) - } - - didComm, err := wallet.NewDidComm(vcWallet, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) - - return command.NewExecuteError(DIDConnectErrorCode, err) - } - - connectionID, err := didComm.Connect(request.Auth, request.Invitation, - wallet.WithConnectTimeout(request.Timeout), wallet.WithReuseDID(request.ReuseConnection), - wallet.WithReuseAnyConnection(request.ReuseAnyConnection), wallet.WithMyLabel(request.MyLabel), - wallet.WithRouterConnections(request.RouterConnections...)) - if err != nil { - logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) - - return command.NewExecuteError(DIDConnectErrorCode, err) - } - - command.WriteNillableResponse(rw, &ConnectResponse{ConnectionID: connectionID}, logger) - - logutil.LogDebug(logger, CommandName, ConnectMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID), - logutil.CreateKeyValueString(invitationIDString, request.Invitation.ID), - logutil.CreateKeyValueString(LabelString, request.MyLabel), - logutil.CreateKeyValueString(connectionIDString, connectionID)) - - return nil -} - -// ProposePresentation accepts out-of-band invitation and sends message proposing presentation -// from wallet to relying party. -// https://w3c-ccg.github.io/universal-wallet-interop-spec/#proposepresentation -// -// Currently Supporting -// [0454-present-proof-v2](https://github.com/hyperledger/aries-rfcs/tree/master/features/0454-present-proof-v2) -func (o *Command) ProposePresentation(rw io.Writer, req io.Reader) command.Error { - request := &ProposePresentationRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) - - return command.NewExecuteError(ProposePresentationErrorCode, err) - } - - didComm, err := wallet.NewDidComm(vcWallet, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) - - return command.NewExecuteError(ProposePresentationErrorCode, err) - } - - msg, err := didComm.ProposePresentation(request.Auth, request.Invitation, - wallet.WithFromDID(request.FromDID), wallet.WithInitiateTimeout(request.Timeout), - wallet.WithConnectOptions(wallet.WithConnectTimeout(request.ConnectionOpts.Timeout), - wallet.WithReuseDID(request.ConnectionOpts.ReuseConnection), - wallet.WithReuseAnyConnection(request.ConnectionOpts.ReuseAnyConnection), - wallet.WithMyLabel(request.ConnectionOpts.MyLabel), - wallet.WithRouterConnections(request.ConnectionOpts.RouterConnections...))) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) - - return command.NewExecuteError(ProposePresentationErrorCode, err) - } - - command.WriteNillableResponse(rw, &ProposePresentationResponse{PresentationRequest: msg}, logger) - - logutil.LogDebug(logger, CommandName, ProposePresentationMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// PresentProof sends present proof message from wallet to relying party. -// https://w3c-ccg.github.io/universal-wallet-interop-spec/#presentproof -// -// Currently Supporting -// [0454-present-proof-v2](https://github.com/hyperledger/aries-rfcs/tree/master/features/0454-present-proof-v2) -// -func (o *Command) PresentProof(rw io.Writer, req io.Reader) command.Error { - request := &PresentProofRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) - - return command.NewExecuteError(PresentProofErrorCode, err) - } - - didComm, err := wallet.NewDidComm(vcWallet, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) - - return command.NewExecuteError(PresentProofErrorCode, err) - } - - status, err := didComm.PresentProof(request.Auth, request.ThreadID, - prepareConcludeInteractionOpts(request.WaitForDone, request.Timeout, request.Presentation)...) - if err != nil { - logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) - - return command.NewExecuteError(PresentProofErrorCode, err) - } - - command.WriteNillableResponse(rw, status, logger) - - logutil.LogDebug(logger, CommandName, PresentProofMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// ProposeCredential sends propose credential message from wallet to issuer. -// 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 -// -func (o *Command) ProposeCredential(rw io.Writer, req io.Reader) command.Error { - request := &ProposeCredentialRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) - - return command.NewExecuteError(ProposeCredentialErrorCode, err) - } - - didComm, err := wallet.NewDidComm(vcWallet, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) - - return command.NewExecuteError(ProposeCredentialErrorCode, err) - } - - msg, err := didComm.ProposeCredential(request.Auth, request.Invitation, - wallet.WithFromDID(request.FromDID), wallet.WithInitiateTimeout(request.Timeout), - wallet.WithConnectOptions(wallet.WithConnectTimeout(request.ConnectionOpts.Timeout), - wallet.WithReuseDID(request.ConnectionOpts.ReuseConnection), - wallet.WithReuseAnyConnection(request.ConnectionOpts.ReuseAnyConnection), - wallet.WithMyLabel(request.ConnectionOpts.MyLabel), - wallet.WithRouterConnections(request.ConnectionOpts.RouterConnections...))) - if err != nil { - logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) - - return command.NewExecuteError(ProposeCredentialErrorCode, err) - } - - command.WriteNillableResponse(rw, &ProposeCredentialResponse{OfferCredential: msg}, logger) - - logutil.LogDebug(logger, CommandName, ProposeCredentialMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// RequestCredential sends request credential message from wallet to issuer and -// optionally waits for credential fulfillment. -// 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 -// -func (o *Command) RequestCredential(rw io.Writer, req io.Reader) command.Error { - request := &RequestCredentialRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) - - return command.NewExecuteError(RequestCredentialErrorCode, err) - } - - didComm, err := wallet.NewDidComm(vcWallet, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) - - return command.NewExecuteError(RequestCredentialErrorCode, err) - } - - status, err := didComm.RequestCredential(request.Auth, request.ThreadID, - prepareConcludeInteractionOpts(request.WaitForDone, request.Timeout, request.Presentation)...) - if err != nil { - logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) - - return command.NewExecuteError(RequestCredentialErrorCode, err) - } - - command.WriteNillableResponse(rw, status, logger) - - logutil.LogDebug(logger, CommandName, RequestCredentialMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// ResolveCredentialManifest resolves given credential manifest by credential fulfillment or credential. -// Supports: https://identity.foundation/credential-manifest/ -// -// Writes list of resolved descriptors to writer or returns error if operation fails. -// -func (o *Command) ResolveCredentialManifest(rw io.Writer, req io.Reader) command.Error { - request := &ResolveCredentialManifestRequest{} - - err := json.NewDecoder(req).Decode(&request) - if err != nil { - logutil.LogInfo(logger, CommandName, ResolveCredentialManifestMethod, err.Error()) - - return command.NewValidationError(InvalidRequestErrorCode, err) - } - - vcWallet, err := wallet.New(request.UserID, o.ctx) - if err != nil { - logutil.LogInfo(logger, CommandName, ResolveCredentialManifestMethod, err.Error()) - - return command.NewExecuteError(ResolveCredentialManifestErrorCode, err) - } - - resolved, err := vcWallet.ResolveCredentialManifest(request.Auth, request.Manifest, - prepareResolveManifestOption(request)) - if err != nil { - logutil.LogInfo(logger, CommandName, ResolveCredentialManifestMethod, err.Error()) - - return command.NewExecuteError(ResolveCredentialManifestErrorCode, err) - } - - command.WriteNillableResponse(rw, &ResolveCredentialManifestResponse{Resolved: resolved}, logger) - - logutil.LogDebug(logger, CommandName, ResolveCredentialManifestMethod, logSuccess, - logutil.CreateKeyValueString(logUserIDKey, request.UserID)) - - return nil -} - -// prepareProfileOptions prepares options for creating wallet profile. -func prepareProfileOptions(rqst *CreateOrUpdateProfileRequest) []wallet.ProfileOptions { - var options []wallet.ProfileOptions - - if rqst.LocalKMSPassphrase != "" { - options = append(options, wallet.WithPassphrase(rqst.LocalKMSPassphrase)) - } - - if rqst.KeyStoreURL != "" { - options = append(options, wallet.WithKeyServerURL(rqst.KeyStoreURL)) - } - - if rqst.EDVConfiguration != nil { - options = append(options, wallet.WithEDVStorage( - rqst.EDVConfiguration.ServerURL, rqst.EDVConfiguration.VaultID, - rqst.EDVConfiguration.EncryptionKeyID, rqst.EDVConfiguration.MACKeyID, - )) - } - - return options -} - -// prepareUnlockOptions prepares options for unlocking wallet. -//nolint: lll -func prepareUnlockOptions(rqst *UnlockWalletRequest, conf *Config) ([]wallet.UnlockOptions, error) { // nolint:funlen,gocyclo - var options []wallet.UnlockOptions - - if rqst.LocalKMSPassphrase != "" { - options = append(options, wallet.WithUnlockByPassphrase(rqst.LocalKMSPassphrase)) - } - - var webkmsOpts []webkms.Opt - - if rqst.WebKMSAuth != nil { - var webKMSHeader func(*http.Request) (*http.Header, error) - - switch { - case rqst.WebKMSAuth.Capability != "": // zcap ld signing - if conf.WebKMSAuthzProvider == nil { - return nil, fmt.Errorf("authorization capability for WebKMS is not configured") - } - - signer := conf.WebKMSAuthzProvider.GetHeaderSigner(rqst.WebKMSAuth.AuthZKeyStoreURL, - rqst.WebKMSAuth.AuthToken, rqst.WebKMSAuth.SecretShare) - - webKMSHeader = func(req *http.Request) (*http.Header, error) { - return signer.SignHeader(req, []byte(rqst.WebKMSAuth.Capability)) - } - case rqst.WebKMSAuth.AuthToken != "": // auth token - webKMSHeader = func(req *http.Request) (*http.Header, error) { - req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.WebKMSAuth.AuthToken)) - - return &req.Header, nil - } - case rqst.WebKMSAuth.GNAPToken != "": // GNAP token - if conf.WebKMSGNAPSigner != nil { - webKMSHeader = conf.WebKMSGNAPSigner - } else { - webKMSHeader = func(req *http.Request) (*http.Header, error) { - req.Header.Set("authorization", fmt.Sprintf("GNAP %s", rqst.WebKMSAuth.GNAPToken)) - - return &req.Header, nil - } - } - } - - webkmsOpts = append(webkmsOpts, webkms.WithHeaders(webKMSHeader)) - } - - if conf.WebKMSCacheSize > 0 { - webkmsOpts = append(webkmsOpts, webkms.WithCache(conf.WebKMSCacheSize)) - } - - var edvOpts []edv.RESTProviderOption - - if rqst.EDVUnlock != nil { - var edvHeader func(*http.Request) (*http.Header, error) - - switch { - case rqst.EDVUnlock.Capability != "": // zcap ld signing - if conf.EdvAuthzProvider == nil { - return nil, fmt.Errorf("authorization capability for EDV is not configured") - } - - signer := conf.EdvAuthzProvider.GetHeaderSigner(rqst.EDVUnlock.AuthZKeyStoreURL, - rqst.EDVUnlock.AuthToken, rqst.EDVUnlock.SecretShare) - - edvHeader = func(req *http.Request) (*http.Header, error) { - return signer.SignHeader(req, []byte(rqst.EDVUnlock.Capability)) - } - case rqst.EDVUnlock.AuthToken != "": // auth token - edvHeader = func(req *http.Request) (*http.Header, error) { - req.Header.Set("authorization", fmt.Sprintf("Bearer %s", rqst.EDVUnlock.AuthToken)) - - return &req.Header, nil - } - case rqst.EDVUnlock.GNAPToken != "": // GNAP token - if conf.EDVGNAPSigner != nil { - edvHeader = conf.EDVGNAPSigner - } else { - edvHeader = func(req *http.Request) (*http.Header, error) { - req.Header.Set("authorization", fmt.Sprintf("GNAP %s", rqst.EDVUnlock.GNAPToken)) - - return &req.Header, nil - } - } - } - - edvOpts = append(edvOpts, edv.WithHeaders(edvHeader)) - } - - if conf.EDVBatchEndpointExtensionEnabled { - edvOpts = append(edvOpts, edv.WithBatchEndpointExtension()) - } - - if conf.EDVReturnFullDocumentsOnQuery { - edvOpts = append(edvOpts, edv.WithFullDocumentsReturnedFromQueries()) - } - - tokenExpiry := conf.DefaultTokenExpiry - if rqst.Expiry > 0 { - tokenExpiry = rqst.Expiry - } - - options = append(options, wallet.WithUnlockWebKMSOptions(webkmsOpts...), wallet.WithUnlockEDVOptions(edvOpts...), - wallet.WithUnlockExpiry(tokenExpiry)) - - return options, nil -} - -func prepareProveOptions(rqst *ProveRequest) []wallet.ProveOptions { - var options []wallet.ProveOptions - - if len(rqst.StoredCredentials) > 0 { - options = append(options, wallet.WithStoredCredentialsToProve(rqst.StoredCredentials...)) - } - - if len(rqst.RawCredentials) > 0 { - options = append(options, wallet.WithRawCredentialsToProve(rqst.RawCredentials...)) - } - - if len(rqst.Presentation) > emptyRawLength { - options = append(options, wallet.WithRawPresentationToProve(rqst.Presentation)) - } - - return options -} - -func prepareVerifyOption(rqst *VerifyRequest) (wallet.VerificationOption, error) { - if len(rqst.StoredCredentialID) > 0 { - return wallet.WithStoredCredentialToVerify(rqst.StoredCredentialID), nil - } - - if len(rqst.RawCredential) > emptyRawLength { - return wallet.WithRawCredentialToVerify(rqst.RawCredential), nil - } - - if len(rqst.Presentation) > emptyRawLength { - return wallet.WithRawPresentationToVerify(rqst.Presentation), nil - } - - return nil, errors.New("invalid option") -} - -func prepareDeriveOption(rqst *DeriveRequest) wallet.CredentialToDerive { - if len(rqst.StoredCredentialID) > 0 { - return wallet.FromStoredCredential(rqst.StoredCredentialID) - } - - return wallet.FromRawCredential(rqst.RawCredential) -} - -func prepareConcludeInteractionOpts(waitForDone bool, timeout time.Duration, presentation json.RawMessage) []wallet.ConcludeInteractionOptions { //nolint: lll - var options []wallet.ConcludeInteractionOptions - - if waitForDone { - options = append(options, wallet.WaitForDone(timeout)) - } - - return append(options, wallet.FromRawPresentation(presentation)) -} - -func prepareResolveManifestOption(rqst *ResolveCredentialManifestRequest) wallet.ResolveManifestOption { - if len(rqst.Fulfillment) > emptyRawLength { - return wallet.ResolveRawFulfillment(rqst.Fulfillment) - } - - if len(rqst.Credential) > emptyRawLength { - return wallet.ResolveRawCredential(rqst.DescriptorID, rqst.Credential) - } - - if rqst.CredentialID != "" { - return wallet.ResolveCredentialID(rqst.DescriptorID, rqst.CredentialID) - } - - return nil + return append(o.baseWallet.GetHandlers(), o.didComm.GetHandlers()...) } diff --git a/pkg/controller/command/vcwallet/command_test.go b/pkg/controller/command/vcwallet/command_test.go index e546c09b14..c1bdaeeb4a 100644 --- a/pkg/controller/command/vcwallet/command_test.go +++ b/pkg/controller/command/vcwallet/command_test.go @@ -7,65 +7,9 @@ SPDX-License-Identifier: Apache-2.0 package vcwallet import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strings" "testing" - "time" - "github.com/google/uuid" "github.com/stretchr/testify/require" - - "github.com/hyperledger/aries-framework-go/internal/testdata" - outofbandClient "github.com/hyperledger/aries-framework-go/pkg/client/outofband" - "github.com/hyperledger/aries-framework-go/pkg/controller/command" - "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/didexchange" - issuecredentialsvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/issuecredential" - "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator" - outofbandSvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofband" - oobv2 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofbandv2" - presentproofSvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/presentproof" - "github.com/hyperledger/aries-framework-go/pkg/doc/did" - "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" - vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" - mockoutofbandv2 "github.com/hyperledger/aries-framework-go/pkg/internal/gomocks/client/outofbandv2" - "github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil" - "github.com/hyperledger/aries-framework-go/pkg/kms" - mockdidexchange "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/didexchange" - mockissuecredential "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/issuecredential" - mockmediator "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator" - mockoutofband "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/outofband" - mockpresentproof "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/presentproof" - mockprovider "github.com/hyperledger/aries-framework-go/pkg/mock/provider" - mockstorage "github.com/hyperledger/aries-framework-go/pkg/mock/storage" - mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr" - "github.com/hyperledger/aries-framework-go/pkg/store/connection" - "github.com/hyperledger/aries-framework-go/pkg/vdr/key" - "github.com/hyperledger/aries-framework-go/pkg/wallet" -) - -const ( - sampleUserID = "sample-user01" - samplePassPhrase = "fakepassphrase" - sampleKeyStoreURL = "sample/keyserver/test" - sampleEDVServerURL = "sample-edv-url" - sampleEDVVaultID = "sample-edv-vault-id" - sampleEDVEncryptionKID = "sample-edv-encryption-kid" - sampleEDVMacKID = "sample-edv-mac-kid" - sampleCommandError = "sample-command-error-01" - sampleFakeTkn = "sample-fake-token-01" - sampleFakeCapability = "sample-fake-capability-01" - sampleDIDKey = "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv4S5" - webRedirectStatusKey = "status" - webRedirectURLKey = "url" - exampleWebRedirect = "http://example.com/sample" ) func TestNew(t *testing.T) { @@ -76,2676 +20,3 @@ func TestNew(t *testing.T) { require.Len(t, cmd.GetHandlers(), 21) }) } - -func TestCommand_CreateProfile(t *testing.T) { - t.Run("successfully create a new wallet profile (localkms)", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - LocalKMSPassphrase: samplePassPhrase, - } - - var b bytes.Buffer - cmdErr := cmd.CreateProfile(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - // if wallet instance can be creates it means profile exists - walletInstance, err := wallet.New(request.UserID, mockctx) - require.NoError(t, err) - require.NotEmpty(t, walletInstance) - }) - - t.Run("successfully create a new wallet profile (webkms/remotekms)", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - KeyStoreURL: sampleKeyStoreURL, - } - - var b bytes.Buffer - cmdErr := cmd.CreateProfile(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - // if wallet instance can be creates it means profile exists - walletInstance, err := wallet.New(request.UserID, mockctx) - require.NoError(t, err) - require.NotEmpty(t, walletInstance) - }) - - t.Run("successfully create a new wallet profile with EDV configuration", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - // create with remote kms. - request := &CreateOrUpdateProfileRequest{ - UserID: uuid.New().String(), - KeyStoreURL: sampleKeyStoreURL, - EDVConfiguration: &EDVConfiguration{ - ServerURL: sampleEDVServerURL, - VaultID: sampleEDVVaultID, - MACKeyID: sampleEDVMacKID, - EncryptionKeyID: sampleEDVEncryptionKID, - }, - } - - var b1 bytes.Buffer - cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) - require.NoError(t, cmdErr) - - // if wallet instance can be creates it means profile exists - walletInstance, err := wallet.New(request.UserID, mockctx) - require.NoError(t, err) - require.NotEmpty(t, walletInstance) - - // create with local kms. - request = &CreateOrUpdateProfileRequest{ - UserID: uuid.New().String(), - LocalKMSPassphrase: samplePassPhrase, - EDVConfiguration: &EDVConfiguration{ - ServerURL: sampleEDVServerURL, - VaultID: sampleEDVVaultID, - }, - } - - var b2 bytes.Buffer - cmdErr = cmd.CreateProfile(&b2, getReader(t, &request)) - require.NoError(t, cmdErr) - - // if wallet instance can be creates it means profile exists - walletInstance, err = wallet.New(request.UserID, mockctx) - require.NoError(t, err) - require.NotEmpty(t, walletInstance) - }) - - t.Run("failed to create duplicate profile", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - LocalKMSPassphrase: samplePassPhrase, - } - - var b1 bytes.Buffer - cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) - require.NoError(t, cmdErr) - - request = &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - KeyStoreURL: sampleKeyStoreURL, - } - - var b2 bytes.Buffer - cmdErr = cmd.CreateProfile(&b2, getReader(t, &request)) - require.Error(t, cmdErr) - require.Equal(t, cmdErr.Type(), command.ExecuteError) - require.Equal(t, cmdErr.Code(), CreateProfileErrorCode) - }) - - t.Run("failed to create profile due to invalid settings", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - } - - var b1 bytes.Buffer - cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) - require.Error(t, cmdErr) - require.Equal(t, cmdErr.Code(), CreateProfileErrorCode) - require.Equal(t, cmdErr.Type(), command.ExecuteError) - }) - - t.Run("failed to create profile due to invalid request", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - var b1 bytes.Buffer - cmdErr := cmd.CreateProfile(&b1, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - require.Equal(t, cmdErr.Code(), InvalidRequestErrorCode) - require.Equal(t, cmdErr.Type(), command.ValidationError) - }) - - t.Run("failed to create profile due to EDV key set creation failure", func(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - mockStProv, ok := mockctx.StorageProviderValue.(*mockstorage.MockStoreProvider) - require.True(t, ok) - require.NotEmpty(t, mockStProv) - - mockStProv.Store.ErrGet = errors.New(sampleCommandError) - - request := &CreateOrUpdateProfileRequest{ - UserID: uuid.New().String(), - LocalKMSPassphrase: samplePassPhrase, - EDVConfiguration: &EDVConfiguration{ - ServerURL: sampleEDVServerURL, - VaultID: sampleEDVVaultID, - }, - } - - var b1 bytes.Buffer - cmdErr := cmd.CreateProfile(&b1, getReader(t, &request)) - require.Error(t, cmdErr) - require.Equal(t, cmdErr.Code(), CreateProfileErrorCode) - require.Equal(t, cmdErr.Type(), command.ExecuteError) - require.Contains(t, cmdErr.Error(), sampleCommandError) - }) -} - -func TestCommand_ProfileExists(t *testing.T) { - const ( - sampleUser1 = "sample-user-01" - sampleUser2 = "sample-user-02" - ) - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - t.Run("profile exists", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - var b bytes.Buffer - cmdErr := cmd.ProfileExists(&b, getReader(t, &WalletUser{ - ID: sampleUser1, - })) - require.NoError(t, cmdErr) - }) - - t.Run("profile doesn't exists", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - var b bytes.Buffer - cmdErr := cmd.ProfileExists(&b, getReader(t, &WalletUser{ - ID: sampleUser2, - })) - - validateError(t, cmdErr, command.ExecuteError, ProfileExistsErrorCode, wallet.ErrProfileNotFound.Error()) - }) - - t.Run("invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - var b bytes.Buffer - cmdErr := cmd.ProfileExists(&b, bytes.NewBufferString("")) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "EOF") - }) -} - -func TestCommand_UpdateProfile(t *testing.T) { - mockctx := newMockProvider(t) - - cmd := New(mockctx, &Config{}) - require.NotNil(t, cmd) - - createRqst := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - LocalKMSPassphrase: samplePassPhrase, - } - - var c bytes.Buffer - cmdErr := cmd.CreateProfile(&c, getReader(t, &createRqst)) - require.NoError(t, cmdErr) - - t.Run("successfully update a wallet profile", func(t *testing.T) { - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - KeyStoreURL: sampleKeyStoreURL, - } - - var b bytes.Buffer - cmdErr := cmd.UpdateProfile(&b, getReader(t, &createRqst)) - require.NoError(t, cmdErr) - - // if wallet instance can be creates it means profile exists - walletInstance, err := wallet.New(request.UserID, mockctx) - require.NoError(t, err) - require.NotEmpty(t, walletInstance) - }) - - t.Run("successfully update a wallet profile with EDV configuration", func(t *testing.T) { - // create with remote kms. - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - KeyStoreURL: sampleKeyStoreURL, - EDVConfiguration: &EDVConfiguration{ - ServerURL: sampleEDVServerURL, - VaultID: sampleEDVVaultID, - MACKeyID: sampleEDVMacKID, - EncryptionKeyID: sampleEDVEncryptionKID, - }, - } - - var b1 bytes.Buffer - cmdErr := cmd.UpdateProfile(&b1, getReader(t, &request)) - require.NoError(t, cmdErr) - - // if wallet instance can be creates it means profile exists - walletInstance, err := wallet.New(request.UserID, mockctx) - require.NoError(t, err) - require.NotEmpty(t, walletInstance) - }) - - t.Run("failed to update profile due to invalid settings", func(t *testing.T) { - request := &CreateOrUpdateProfileRequest{ - UserID: sampleUserID, - } - - var b1 bytes.Buffer - cmdErr := cmd.UpdateProfile(&b1, getReader(t, &request)) - require.Error(t, cmdErr) - require.Equal(t, cmdErr.Code(), UpdateProfileErrorCode) - require.Equal(t, cmdErr.Type(), command.ExecuteError) - }) - - t.Run("failed to update profile due to invalid request", func(t *testing.T) { - var b1 bytes.Buffer - cmdErr := cmd.UpdateProfile(&b1, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - require.Equal(t, cmdErr.Code(), InvalidRequestErrorCode) - require.Equal(t, cmdErr.Type(), command.ValidationError) - }) -} - -func TestCommand_OpenAndClose(t *testing.T) { - const ( - sampleUser1 = "sample-user-01" - sampleUser2 = "sample-user-02" - sampleUser3 = "sample-user-03" - ) - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser2, - KeyStoreURL: sampleKeyStoreURL, - }) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser3, - LocalKMSPassphrase: samplePassPhrase, - EDVConfiguration: &EDVConfiguration{ - ServerURL: sampleEDVServerURL, - VaultID: sampleEDVVaultID, - }, - }) - - t.Run("successfully unlock & lock wallet (local kms)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser1})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser1})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("successfully unlock & lock wallet (remote kms)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &UnlockWalletRequest{ - UserID: sampleUser2, - WebKMSAuth: &UnlockAuth{AuthToken: sampleFakeTkn}, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("successfully unlock & lock wallet (remote kms, capability)", func(t *testing.T) { - cmd := New(mockctx, &Config{ - WebKMSCacheSize: 99, - WebKMSAuthzProvider: &mockAuthZCapability{}, - }) - - request := &UnlockWalletRequest{ - UserID: sampleUser2, - WebKMSAuth: &UnlockAuth{Capability: sampleFakeCapability}, - Expiry: 10 * time.Second, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("successfully unlock & lock wallet (local kms, edv user)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &UnlockWalletRequest{ - UserID: sampleUser3, - LocalKMSPassphrase: samplePassPhrase, - EDVUnlock: &UnlockAuth{ - AuthToken: sampleFakeTkn, - }, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("successfully unlock & lock wallet (gnap auth, no custom header func)", func(t *testing.T) { - cmd := New(mockctx, &Config{ - WebKMSCacheSize: 99, - }) - - request := &UnlockWalletRequest{ - UserID: sampleUser2, - WebKMSAuth: &UnlockAuth{ - GNAPToken: sampleFakeTkn, - }, - EDVUnlock: &UnlockAuth{ - GNAPToken: sampleFakeTkn, - }, - Expiry: 10 * time.Second, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("successfully unlock & lock wallet (gnap auth, with custom header func)", func(t *testing.T) { - headerFunc := func(r *http.Request) (*http.Header, error) { - return &r.Header, nil - } - - cmd := New(mockctx, &Config{ - WebKMSCacheSize: 99, - WebKMSGNAPSigner: headerFunc, - EDVGNAPSigner: headerFunc, - }) - - request := &UnlockWalletRequest{ - UserID: sampleUser2, - WebKMSAuth: &UnlockAuth{ - GNAPToken: sampleFakeTkn, - }, - EDVUnlock: &UnlockAuth{ - GNAPToken: sampleFakeTkn, - }, - Expiry: 10 * time.Second, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser2})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("successfully unlock & lock wallet (local kms, edv capability user)", func(t *testing.T) { - cmd := New(mockctx, &Config{ - EDVReturnFullDocumentsOnQuery: true, - EDVBatchEndpointExtensionEnabled: true, - EdvAuthzProvider: &mockAuthZCapability{}, - }) - - request := &UnlockWalletRequest{ - UserID: sampleUser3, - LocalKMSPassphrase: samplePassPhrase, - EDVUnlock: &UnlockAuth{ - Capability: sampleFakeCapability, - }, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - require.NotEmpty(t, getUnlockToken(t, b)) - b.Reset() - - // try again, should get error, wallet already unlocked - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - require.Contains(t, cmdErr.Error(), wallet.ErrAlreadyUnlocked.Error()) - require.Empty(t, b.Len()) - b.Reset() - - // lock wallet - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) - require.NoError(t, cmdErr) - var lockResponse LockWalletResponse - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.True(t, lockResponse.Closed) - b.Reset() - - // lock wallet again - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: sampleUser3})) - require.NoError(t, cmdErr) - require.NoError(t, json.NewDecoder(&b).Decode(&lockResponse)) - require.False(t, lockResponse.Closed) - b.Reset() - }) - - t.Run("lock & unlock failures", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Open(&b, getReader(t, &UnlockWalletRequest{})) - require.Error(t, cmdErr) - validateError(t, cmdErr, command.ExecuteError, OpenWalletErrorCode, "profile does not exist") - require.Empty(t, b.Len()) - b.Reset() - - cmdErr = cmd.Open(&b, getReader(t, "")) - require.Error(t, cmdErr) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "cannot unmarshal string into Go") - require.Empty(t, b.Len()) - b.Reset() - - cmdErr = cmd.Close(&b, getReader(t, &UnlockWalletRequest{})) - require.Error(t, cmdErr) - validateError(t, cmdErr, command.ExecuteError, CloseWalletErrorCode, "profile does not exist") - require.Empty(t, b.Len()) - b.Reset() - - cmdErr = cmd.Close(&b, getReader(t, "")) - require.Error(t, cmdErr) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "cannot unmarshal string into Go") - require.Empty(t, b.Len()) - b.Reset() - - // EDV authz error - request := &UnlockWalletRequest{ - UserID: sampleUser3, - LocalKMSPassphrase: samplePassPhrase, - EDVUnlock: &UnlockAuth{ - Capability: sampleFakeCapability, - }, - } - - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, - "authorization capability for EDV is not configured") - require.Empty(t, b.Len()) - b.Reset() - - // webKMS authz error - request = &UnlockWalletRequest{ - UserID: sampleUser3, - WebKMSAuth: &UnlockAuth{ - Capability: sampleFakeCapability, - }, - } - - cmdErr = cmd.Open(&b, getReader(t, &request)) - require.Error(t, cmdErr) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, - "authorization capability for WebKMS is not configured") - require.Empty(t, b.Len()) - b.Reset() - }) -} - -func TestCommand_AddRemoveGetGetAll(t *testing.T) { - const ( - sampleUser1 = "sample-user-01" - sampleUser2 = "sample-user-02" - sampleUser3 = "sample-user-03" - ) - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - token1, lock1 := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock1() - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser2, - KeyStoreURL: sampleKeyStoreURL, - }) - - token2, lock2 := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser2, - WebKMSAuth: &UnlockAuth{AuthToken: sampleFakeTkn}, - }) - - defer lock2() - - t.Run("add a credential to wallet", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: testdata.SampleUDCVC, - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - }) - - t.Run("add a metadata to wallet with validation", func(t *testing.T) { - cmd := New(mockctx, &Config{ValidateDataModel: true}) - - var b bytes.Buffer - - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: testdata.SampleWalletContentMetadata, - ContentType: "metadata", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - }) - - t.Run("get a credential from wallet", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Get(&b, getReader(t, &GetContentRequest{ - ContentID: "http://example.edu/credentials/1872", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - - var response GetContentResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response.Content) - }) - - t.Run("get all credentials from wallet", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - // save multiple credentials, one already saved - const count = 6 - for i := 1; i < count; i++ { - var b bytes.Buffer - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: []byte(strings.ReplaceAll(string(testdata.SampleUDCVC), `"http://example.edu/credentials/1872"`, - fmt.Sprintf(`"http://example.edu/credentials/1872%d"`, i))), - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - - b.Reset() - } - - var b bytes.Buffer - - cmdErr := cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - - var response GetAllContentResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Len(t, response.Contents, count) - }) - - t.Run("add a collection to wallet with validation failed", func(t *testing.T) { - const orgCollectionWithInvalidStructure = `{ - "@context": ["https://w3id.org/wallet/v1"], - "id": "did:example:acme123456789abcdefghi", - "type": "Organization", - "name": "Acme Corp.", - "image": "https://via.placeholder.com/150", - "description" : "A software company.", - "tags": ["professional", "organization"], - "incorrectProp": "incorrectProp", - "correlation": ["4058a72a-9523-11ea-bb37-0242ac130002"] - }` - - cmd := New(mockctx, &Config{ValidateDataModel: true}) - - var b bytes.Buffer - - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: []byte(orgCollectionWithInvalidStructure), - ContentType: wallet.Collection, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.Contains(t, cmdErr.Error(), "JSON-LD doc has different structure after compaction") - }) - - t.Run("get all credentials from wallet by collection ID", func(t *testing.T) { - const orgCollection = `{ - "@context": ["https://w3id.org/wallet/v1"], - "id": "did:example:acme123456789abcdefghi", - "type": "Organization", - "name": "Acme Corp.", - "image": "https://via.placeholder.com/150", - "description" : "A software company.", - "tags": ["professional", "organization"], - "correlation": ["4058a72a-9523-11ea-bb37-0242ac130002"] - }` - - const collectionID = "did:example:acme123456789abcdefghi" - - cmd := New(mockctx, &Config{}) - - // save a collection - var b bytes.Buffer - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: []byte(orgCollection), - ContentType: wallet.Collection, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - - // save only 2 credentials by collection, - const count = 6 - for i := 1; i < count; i++ { - var cid string - - if i%2 == 0 { - cid = collectionID - } - - var vcb bytes.Buffer - cErr := cmd.Add(&vcb, getReader(t, &AddContentRequest{ - Content: []byte(strings.ReplaceAll(string(testdata.SampleUDCVC), `"http://example.edu/credentials/1872"`, - fmt.Sprintf(`"http://example.edu/credentials/18722%d"`, i))), - ContentType: "credential", - CollectionID: cid, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cErr) - - vcb.Reset() - } - - b.Reset() - - cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ - ContentType: "credential", - CollectionID: collectionID, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - - var response GetAllContentResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Len(t, response.Contents, 2) - }) - - t.Run("remove a credential from wallet", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Remove(&b, getReader(t, &RemoveContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - }) - - t.Run("get a credential from different wallet", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Get(&b, getReader(t, &GetContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser2, Auth: token2}, - })) - validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, "data not found") - }) - - t.Run("try content operations from invalid auth", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - const expectedErr = "invalid auth token" - - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: testdata.SampleUDCVC, - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, AddToWalletErrorCode, expectedErr) - b.Reset() - - cmdErr = cmd.Get(&b, getReader(t, &GetContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, expectedErr) - - cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, GetAllFromWalletErrorCode, expectedErr) - - cmdErr = cmd.Remove(&b, getReader(t, &RemoveContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, RemoveFromWalletErrorCode, expectedErr) - }) - - t.Run("try content operations from invalid content type", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: testdata.SampleUDCVC, - ContentType: "mango", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - validateError(t, cmdErr, command.ExecuteError, AddToWalletErrorCode, "invalid content type") - b.Reset() - - cmdErr = cmd.Get(&b, getReader(t, &GetContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "pineapple", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, "data not found") - - cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ - ContentType: "orange", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - - var response GetAllContentResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.Empty(t, response.Contents) - b.Reset() - - cmdErr = cmd.Remove(&b, getReader(t, &RemoveContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "strawberry", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token1}, - })) - require.NoError(t, cmdErr) - }) - - t.Run("try content operations from invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - const expectedErr = "profile does not exist" - - cmdErr := cmd.Add(&b, getReader(t, &AddContentRequest{ - Content: testdata.SampleUDCVC, - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, AddToWalletErrorCode, expectedErr) - b.Reset() - - cmdErr = cmd.Get(&b, getReader(t, &GetContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, GetFromWalletErrorCode, expectedErr) - - cmdErr = cmd.GetAll(&b, getReader(t, &GetAllContentRequest{ - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, GetAllFromWalletErrorCode, expectedErr) - - cmdErr = cmd.Remove(&b, getReader(t, &RemoveContentRequest{ - ContentID: "http://example.edu/credentials/1877", - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser3, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, RemoveFromWalletErrorCode, expectedErr) - }) - - t.Run("try content operations from invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - const expectedErr = "invalid character" - - cmdErr := cmd.Add(&b, bytes.NewBufferString("invalid request")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) - b.Reset() - - cmdErr = cmd.Get(&b, bytes.NewBufferString("invalid request")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) - - cmdErr = cmd.GetAll(&b, bytes.NewBufferString("invalid request")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) - - cmdErr = cmd.Remove(&b, bytes.NewBufferString("invalid request")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, expectedErr) - }) -} - -func TestCommand_Query(t *testing.T) { - const sampleUser1 = "sample-user-q01" - - mockctx := newMockProvider(t) - mockctx.VDRegistryValue = getMockDIDKeyVDR() - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - sampleNewUDCVc := strings.ReplaceAll(string(testdata.SampleUDCVC), - "http://example.edu/credentials/1872", "http://example.edu/credentials/18722") - - addContent(t, mockctx, &AddContentRequest{ - Content: []byte(sampleNewUDCVc), - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - - addContent(t, mockctx, &AddContentRequest{ - Content: testdata.SampleUDCVCWithProofBBS, - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - - t.Run("successfully query credentials", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ - Query: []*wallet.QueryParams{ - { - Type: "QueryByExample", - Query: []json.RawMessage{testdata.SampleWalletQueryByExample}, - }, - { - Type: "QueryByFrame", - Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, - }, - }, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - })) - require.NoError(t, cmdErr) - - var response map[string]interface{} - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response["results"]) - }) - - t.Run("query credentials with invalid auth", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ - Query: []*wallet.QueryParams{ - { - Type: "QueryByFrame", - Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, - }, - }, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, QueryWalletErrorCode, "invalid auth token") - }) - - t.Run("query credentials with invalid wallet profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ - Query: []*wallet.QueryParams{ - { - Type: "QueryByFrame", - Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, - }, - }, - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ExecuteError, QueryWalletErrorCode, "profile does not exist") - }) - - t.Run("query credentials with invalid query type", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Query(&b, getReader(t, &ContentQueryRequest{ - Query: []*wallet.QueryParams{ - { - Type: "QueryByOrange", - Query: []json.RawMessage{testdata.SampleWalletQueryByFrame}, - }, - }, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - })) - validateError(t, cmdErr, command.ExecuteError, QueryWalletErrorCode, "unsupported query type") - }) - - t.Run("query credentials with invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Query(&b, bytes.NewBufferString("--")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - }) -} - -func TestCommand_IssueProveVerify(t *testing.T) { - const sampleUser1 = "sample-user-01" - - mockctx := newMockProvider(t) - mockctx.VDRegistryValue = getMockDIDKeyVDR() - - tcrypto, err := tinkcrypto.New() - require.NoError(t, err) - - mockctx.CryptoValue = tcrypto - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - addContent(t, mockctx, &AddContentRequest{ - Content: testdata.SampleWalletContentKeyBase58, - ContentType: wallet.Key, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - addContent(t, mockctx, &AddContentRequest{ - Content: testdata.SampleDocResolutionResponse, - ContentType: wallet.DIDResolutionResponse, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - - var rawCredentialToVerify json.RawMessage - - t.Run("issue a credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Issue(&b, getReader(t, &IssueRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Credential: testdata.SampleUDCVC, - ProofOptions: &wallet.ProofOptions{ - Controller: sampleDIDKey, - }, - })) - require.NoError(t, cmdErr) - - credentialIssued := parseCredential(t, b) - require.Len(t, credentialIssued.Proofs, 1) - b.Reset() - - rawCredentialToVerify, err = credentialIssued.MarshalJSON() - require.NoError(t, err) - }) - - // save it in store for next tests - addContent(t, mockctx, &AddContentRequest{ - Content: rawCredentialToVerify, - ContentType: wallet.Credential, - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - - t.Run("verify a credential from store", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - StoredCredentialID: "http://example.edu/credentials/1872", - })) - require.NoError(t, cmdErr) - - var response VerifyResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.True(t, response.Verified) - require.Empty(t, response.Error) - }) - - t.Run("verify a raw credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - RawCredential: rawCredentialToVerify, - })) - require.NoError(t, cmdErr) - - var response VerifyResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.True(t, response.Verified) - require.Empty(t, response.Error) - }) - - t.Run("verify a invalid credential", func(t *testing.T) { - // tamper a credential - invalidVC := string(rawCredentialToVerify) - invalidVC = strings.ReplaceAll(invalidVC, "Jayden Doe", "John Smith") - - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - RawCredential: []byte(invalidVC), - })) - require.NoError(t, cmdErr) - - var response VerifyResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.False(t, response.Verified) - require.NotEmpty(t, response.Error) - require.Contains(t, response.Error, "invalid signature") - }) - - var presentation *verifiable.Presentation - - t.Run("prove credentials", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - RawCredentials: []json.RawMessage{rawCredentialToVerify}, - StoredCredentials: []string{"http://example.edu/credentials/1872"}, - ProofOptions: &wallet.ProofOptions{ - Controller: sampleDIDKey, - }, - })) - require.NoError(t, cmdErr) - - presentation = parsePresentation(t, b) - require.NotEmpty(t, presentation.Proofs) - require.Len(t, presentation.Credentials(), 2) - require.Len(t, presentation.Proofs, 1) - b.Reset() - - // prove using raw presentation - rawPresentation, err := presentation.MarshalJSON() - require.NoError(t, err) - - cmdErr = cmd.Prove(&b, getReader(t, &ProveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - StoredCredentials: []string{"http://example.edu/credentials/1872"}, - Presentation: rawPresentation, - ProofOptions: &wallet.ProofOptions{ - Controller: sampleDIDKey, - }, - })) - require.NoError(t, cmdErr) - presentation2 := parsePresentation(t, b) - require.NotEmpty(t, presentation2.Proofs) - require.Len(t, presentation2.Credentials(), 3) - require.Len(t, presentation2.Proofs, 2) - }) - - t.Run("verify a raw presentation", func(t *testing.T) { - vpBytes, err := presentation.MarshalJSON() - require.NoError(t, err) - - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Presentation: vpBytes, - })) - require.NoError(t, cmdErr) - - var response VerifyResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.True(t, response.Verified) - require.Empty(t, response.Error) - b.Reset() - - // tamper it and try - invalidVP := string(vpBytes) - invalidVP = strings.ReplaceAll(invalidVP, "Jayden Doe", "John Smith") - - cmdErr = cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Presentation: []byte(invalidVP), - })) - require.NoError(t, cmdErr) - - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.False(t, response.Verified) - require.NotEmpty(t, response.Error) - require.Contains(t, response.Error, "invalid signature") - b.Reset() - }) - - t.Run("failed to prove a credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Issue(&b, getReader(t, &IssueRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Credential: testdata.SampleUDCVC, - ProofOptions: &wallet.ProofOptions{ - Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", - }, - })) - validateError(t, cmdErr, command.ExecuteError, IssueFromWalletErrorCode, "failed to prepare proof") - }) - - t.Run("failed to prove a credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - RawCredentials: []json.RawMessage{rawCredentialToVerify}, - StoredCredentials: []string{"http://example.edu/credentials/1872"}, - ProofOptions: &wallet.ProofOptions{ - Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", - }, - })) - validateError(t, cmdErr, command.ExecuteError, ProveFromWalletErrorCode, "failed to prepare proof") - }) - - t.Run("issue,prove,verify with invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - const errMsg = "profile does not exist" - - cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, - StoredCredentials: []string{"http://example.edu/credentials/1877"}, - ProofOptions: &wallet.ProofOptions{ - Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", - }, - })) - validateError(t, cmdErr, command.ExecuteError, ProveFromWalletErrorCode, errMsg) - b.Reset() - - cmdErr = cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, - StoredCredentialID: "http://example.edu/credentials/1877", - })) - validateError(t, cmdErr, command.ExecuteError, VerifyFromWalletErrorCode, errMsg) - b.Reset() - - cmdErr = cmd.Issue(&b, getReader(t, &IssueRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, - Credential: testdata.SampleUDCVC, - ProofOptions: &wallet.ProofOptions{ - Controller: sampleDIDKey, - }, - })) - validateError(t, cmdErr, command.ExecuteError, IssueFromWalletErrorCode, errMsg) - b.Reset() - }) - - t.Run("issue,prove,verify with invalid auth", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - const errMsg = "invalid auth token" - - cmdErr := cmd.Prove(&b, getReader(t, &ProveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - StoredCredentials: []string{"http://example.edu/credentials/1877"}, - ProofOptions: &wallet.ProofOptions{ - Controller: "did:key:z6MknC1wwS6DEYwtGbZZo2QvjQjkh2qSBjb4GYmbye8dv464", - }, - })) - validateError(t, cmdErr, command.ExecuteError, ProveFromWalletErrorCode, errMsg) - b.Reset() - - cmdErr = cmd.Issue(&b, getReader(t, &IssueRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - Credential: testdata.SampleUDCVC, - ProofOptions: &wallet.ProofOptions{ - Controller: sampleDIDKey, - }, - })) - validateError(t, cmdErr, command.ExecuteError, IssueFromWalletErrorCode, wallet.ErrWalletLocked.Error()) - b.Reset() - }) - - t.Run("issue,prove,verify with invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Verify(&b, getReader(t, &VerifyRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - })) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid option") - b.Reset() - - const errMsg = "invalid character" - - cmdErr = cmd.Prove(&b, bytes.NewBufferString("----")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, errMsg) - b.Reset() - - cmdErr = cmd.Verify(&b, bytes.NewBufferString("----")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, errMsg) - b.Reset() - - cmdErr = cmd.Issue(&b, bytes.NewBufferString("----")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, errMsg) - b.Reset() - }) -} - -func TestCommand_Derive(t *testing.T) { - const sampleUser1 = "sample-user-01" - - mockctx := newMockProvider(t) - mockctx.VDRegistryValue = getMockDIDKeyVDR() - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - addContent(t, mockctx, &AddContentRequest{ - Content: testdata.SampleUDCVCWithProofBBS, - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - - // prepare frame - var frameDoc map[string]interface{} - - require.NoError(t, json.Unmarshal(testdata.SampleFrame, &frameDoc)) - - t.Run("derive a credential from stored credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - StoredCredentialID: "http://example.edu/credentials/1872", - DeriveOptions: &wallet.DeriveOptions{ - Frame: frameDoc, - Nonce: uuid.New().String(), - }, - })) - require.NoError(t, cmdErr) - - var response DeriveResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response.Credential) - }) - - t.Run("derive a credential from raw credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - RawCredential: testdata.SampleUDCVCWithProofBBS, - DeriveOptions: &wallet.DeriveOptions{ - Frame: frameDoc, - Nonce: uuid.New().String(), - }, - })) - require.NoError(t, cmdErr) - - var response DeriveResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response.Credential) - }) - - t.Run("derive a credential using invalid auth", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - StoredCredentialID: "http://example.edu/credentials/1872", - DeriveOptions: &wallet.DeriveOptions{ - Frame: frameDoc, - Nonce: uuid.New().String(), - }, - })) - validateError(t, cmdErr, command.ExecuteError, DeriveFromWalletErrorCode, "invalid auth token") - require.Empty(t, b.Bytes()) - }) - - t.Run("derive a credential using invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Derive(&b, getReader(t, &DeriveRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, - StoredCredentialID: "http://example.edu/credentials/1872", - DeriveOptions: &wallet.DeriveOptions{ - Frame: frameDoc, - Nonce: uuid.New().String(), - }, - })) - validateError(t, cmdErr, command.ExecuteError, DeriveFromWalletErrorCode, "profile does not exist") - require.Empty(t, b.Bytes()) - }) - - t.Run("derive a credential using invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - - cmdErr := cmd.Derive(&b, bytes.NewBufferString("--")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - b.Reset() - - cmdErr = cmd.Derive(&b, getReader(t, &DeriveRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - DeriveOptions: &wallet.DeriveOptions{ - Frame: frameDoc, - Nonce: uuid.New().String(), - }, - })) - validateError(t, cmdErr, command.ExecuteError, DeriveFromWalletErrorCode, "failed to resolve request") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_CreateKeyPair(t *testing.T) { - const sampleUser1 = "sample-user-01" - - mockctx := newMockProvider(t) - mockctx.VDRegistryValue = getMockDIDKeyVDR() - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - t.Run("successfully create key pair (local kms)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &CreateKeyPairRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - KeyType: kms.ED25519, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.CreateKeyPair(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response CreateKeyPairResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response.KeyID) - require.NotEmpty(t, response.PublicKey) - }) - - t.Run("create key pair using invalid auth (local kms)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &CreateKeyPairRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: sampleFakeTkn}, - KeyType: kms.ED25519, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.CreateKeyPair(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, CreateKeyPairFromWalletErrorCode, "invalid auth token") - require.Empty(t, b.Bytes()) - }) - - t.Run("create key pair using invalid request (local kms)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.CreateKeyPair(&b, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - }) - - t.Run("create key pair using invalid profile (local kms)", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &CreateKeyPairRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, - KeyType: kms.ED25519, - } - - // unlock wallet - var b bytes.Buffer - cmdErr := cmd.CreateKeyPair(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, CreateKeyPairFromWalletErrorCode, "failed to get VC wallet profile") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_Connect(t *testing.T) { - const sampleDIDCommUser = "sample-didcomm-user01" - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - t.Run("successfully perform DID connect", 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 - - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.Connect(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response ConnectResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Equal(t, sampleConnID, response.ConnectionID) - }) - - t.Run("did connect failure", func(t *testing.T) { - oobSvc := &mockoutofband.MockOobService{ - AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) { - return "", fmt.Errorf(sampleCommandError) - }, - } - mockctx.ServiceMap[outofbandSvc.Name] = oobSvc - - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.Connect(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, DIDConnectErrorCode, sampleCommandError) - validateError(t, cmdErr, command.ExecuteError, DIDConnectErrorCode, "failed to accept invitation") - require.Empty(t, b.Bytes()) - }) - - t.Run("did connect failure - invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - cmdErr := cmd.Connect(&b, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - }) - - t.Run("attempt to didconnect with invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.Connect(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, DIDConnectErrorCode, "failed to get VC wallet profile") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_ProposePresentation(t *testing.T) { - const sampleDIDCommUser = "sample-didcomm-user02" - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - const ( - myDID = "did:mydid:123" - theirDID = "did:theirdid:123" - ) - - t.Run("successfully send propose presentation", 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() - ppSvc := &mockpresentproof.MockPresentProofSvc{ - ActionsFunc: func() ([]presentproofSvc.Action, error) { - return []presentproofSvc.Action{ - { - PIID: thID, - Msg: service.NewDIDCommMsgMap(&presentproofSvc.RequestPresentationV2{ - Comment: "mock msg", - }), - MyDID: myDID, - TheirDID: theirDID, - }, - }, nil - }, - HandleFunc: func(service.DIDCommMsg) (string, error) { - return thID, nil - }, - } - mockctx.ServiceMap[presentproofSvc.Name] = ppSvc - - 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)) - - cmd := New(mockctx, &Config{}) - - request := &ProposePresentationRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - Invitation: &wallet.GenericInvitation{}, - } - - var b bytes.Buffer - cmdErr := cmd.ProposePresentation(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response ProposePresentationResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response.PresentationRequest) - }) - - t.Run("failed to send propose presentation", func(t *testing.T) { - oobSvc := &mockoutofband.MockOobService{ - AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) { - return "", fmt.Errorf(sampleCommandError) - }, - } - - mockctx.ServiceMap[outofbandSvc.Name] = oobSvc - - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.ProposePresentation(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, ProposePresentationErrorCode, sampleCommandError) - validateError(t, cmdErr, command.ExecuteError, ProposePresentationErrorCode, "failed to accept invitation") - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to send propose presentation - invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - cmdErr := cmd.ProposePresentation(&b, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to send propose presentation - invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.ProposePresentation(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, ProposePresentationErrorCode, "failed to get VC wallet profile") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_PresentProof(t *testing.T) { - const sampleDIDCommUser = "sample-didcomm-user03" - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - t.Run("successfully present proof", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &PresentProofRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - ThreadID: uuid.New().String(), - Presentation: json.RawMessage{}, - } - - var b bytes.Buffer - cmdErr := cmd.PresentProof(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - }) - - t.Run("successfully present proof - wait for done", func(t *testing.T) { - thID := uuid.New().String() - mockPresentProofSvc := &mockpresentproof.MockPresentProofSvc{ - RegisterMsgEventHandle: func(ch chan<- service.StateMsg) error { - ch <- service.StateMsg{ - Type: service.PostState, - StateID: presentproofSvc.StateNameDone, - Properties: &mockdidexchange.MockEventProperties{ - Properties: map[string]interface{}{ - webRedirectStatusKey: model.AckStatusOK, - webRedirectURLKey: exampleWebRedirect, - }, - }, - Msg: &mockMsg{thID: thID}, - } - - return nil - }, - } - mockctx.ServiceMap[presentproofSvc.Name] = mockPresentProofSvc - - cmd := New(mockctx, &Config{}) - - request := &PresentProofRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - ThreadID: thID, - Presentation: json.RawMessage{}, - WaitForDone: true, - Timeout: 1 * time.Millisecond, - } - - var b bytes.Buffer - cmdErr := cmd.PresentProof(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response wallet.CredentialInteractionStatus - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Equal(t, exampleWebRedirect, response.RedirectURL) - require.Equal(t, model.AckStatusOK, response.Status) - }) - - t.Run("failed to present proof", func(t *testing.T) { - ppSvc := &mockpresentproof.MockPresentProofSvc{ - ActionContinueFunc: func(string, ...presentproofSvc.Opt) error { - return fmt.Errorf(sampleCommandError) - }, - } - - mockctx.ServiceMap[presentproofSvc.Name] = ppSvc - - cmd := New(mockctx, &Config{}) - - request := &PresentProofRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - ThreadID: uuid.New().String(), - Presentation: json.RawMessage{}, - } - - var b bytes.Buffer - cmdErr := cmd.PresentProof(&b, getReader(t, &request)) - validateError(t, cmdErr, command.ExecuteError, PresentProofErrorCode, sampleCommandError) - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to present proof - invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - cmdErr := cmd.PresentProof(&b, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to present proof - invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &PresentProofRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, - ThreadID: uuid.New().String(), - Presentation: json.RawMessage{}, - } - - var b bytes.Buffer - cmdErr := cmd.PresentProof(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, PresentProofErrorCode, "failed to get VC wallet profile") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_ProposeCredential(t *testing.T) { - const ( - sampleDIDCommUser = "sample-didcomm-user02" - sampleMsgComment = "sample mock msg" - myDID = "did:mydid:123" - theirDID = "did:theirdid:123" - ) - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - t.Run("successfully send propose credential", 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.OfferCredentialV2{ - 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)) - - cmd := New(mockctx, &Config{}) - - request := &ProposeCredentialRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - Invitation: &wallet.GenericInvitation{}, - } - - var b bytes.Buffer - cmdErr := cmd.ProposeCredential(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response ProposeCredentialResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.NotEmpty(t, response.OfferCredential) - - offer := &issuecredentialsvc.OfferCredentialV2{} - - err = response.OfferCredential.Decode(offer) - require.NoError(t, err) - require.NotEmpty(t, offer) - require.Equal(t, sampleMsgComment, offer.Comment) - }) - - t.Run("failed to send propose credential", func(t *testing.T) { - oobSvc := &mockoutofband.MockOobService{ - AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) { - return "", fmt.Errorf(sampleCommandError) - }, - } - - mockctx.ServiceMap[outofbandSvc.Name] = oobSvc - - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.ProposeCredential(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, ProposeCredentialErrorCode, sampleCommandError) - validateError(t, cmdErr, command.ExecuteError, ProposeCredentialErrorCode, "failed to accept invitation") - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to send propose credential - invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - cmdErr := cmd.ProposeCredential(&b, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to send propose credential - invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &ConnectRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, - Invitation: &outofbandClient.Invitation{}, - ConnectOpts: ConnectOpts{ - MyLabel: "sample-label", - }, - } - - var b bytes.Buffer - cmdErr := cmd.ProposeCredential(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, ProposeCredentialErrorCode, "failed to get VC wallet profile") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_RequestCredential(t *testing.T) { - const sampleDIDCommUser = "sample-didcomm-user03" - - mockctx := newMockProvider(t) - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleDIDCommUser, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - t.Run("successfully request credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &RequestCredentialRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - ThreadID: uuid.New().String(), - Presentation: json.RawMessage{}, - } - - var b bytes.Buffer - cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - }) - - t.Run("successfully request credential - wait for done", func(t *testing.T) { - thID := uuid.New().String() - - icSvc := &mockissuecredential.MockIssueCredentialSvc{ - RegisterMsgEventHandle: func(ch chan<- service.StateMsg) error { - ch <- service.StateMsg{ - Type: service.PostState, - StateID: "done", - Properties: &mockdidexchange.MockEventProperties{ - Properties: map[string]interface{}{ - webRedirectStatusKey: model.AckStatusOK, - webRedirectURLKey: exampleWebRedirect, - }, - }, - Msg: &mockMsg{thID: thID}, - } - - return nil - }, - } - mockctx.ServiceMap[issuecredentialsvc.Name] = icSvc - - cmd := New(mockctx, &Config{}) - - request := &RequestCredentialRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - ThreadID: thID, - Presentation: json.RawMessage{}, - WaitForDone: true, - Timeout: 600 * time.Millisecond, - } - - var b bytes.Buffer - cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response wallet.CredentialInteractionStatus - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Equal(t, exampleWebRedirect, response.RedirectURL) - require.Equal(t, model.AckStatusOK, response.Status) - }) - - t.Run("failed to request credential", func(t *testing.T) { - icSvc := &mockissuecredential.MockIssueCredentialSvc{ - ActionContinueFunc: func(string, ...issuecredentialsvc.Opt) error { - return fmt.Errorf(sampleCommandError) - }, - } - mockctx.ServiceMap[issuecredentialsvc.Name] = icSvc - - cmd := New(mockctx, &Config{}) - - request := &RequestCredentialRequest{ - WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, - ThreadID: uuid.New().String(), - Presentation: json.RawMessage{}, - } - - var b bytes.Buffer - cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) - validateError(t, cmdErr, command.ExecuteError, RequestCredentialErrorCode, sampleCommandError) - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to request credential - invalid request", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - var b bytes.Buffer - cmdErr := cmd.RequestCredential(&b, bytes.NewBufferString("--")) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - require.Empty(t, b.Bytes()) - }) - - t.Run("failed to request credential - invalid profile", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &RequestCredentialRequest{ - WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, - ThreadID: uuid.New().String(), - Presentation: json.RawMessage{}, - } - - var b bytes.Buffer - cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) - require.Error(t, cmdErr) - - validateError(t, cmdErr, command.ExecuteError, RequestCredentialErrorCode, "failed to get VC wallet profile") - require.Empty(t, b.Bytes()) - }) -} - -func TestCommand_ResolveCredentialManifest(t *testing.T) { - const sampleUser1 = "sample-user-r01" - - mockctx := newMockProvider(t) - mockctx.VDRegistryValue = getMockDIDKeyVDR() - - createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ - UserID: sampleUser1, - LocalKMSPassphrase: samplePassPhrase, - }) - - defer lock() - - addContent(t, mockctx, &AddContentRequest{ - Content: testdata.SampleUDCVC, - ContentType: "credential", - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - }) - - t.Run("successfully resolve credential fulfillment", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &ResolveCredentialManifestRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Manifest: testdata.CredentialManifestMultipleVCs, - Fulfillment: testdata.CredentialFulfillmentWithMultipleVCs, - } - - var b bytes.Buffer - cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, request)) - require.NoError(t, cmdErr) - - var response ResolveCredentialManifestResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Len(t, response.Resolved, 2) - }) - - t.Run("successfully resolve credential", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &ResolveCredentialManifestRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Manifest: testdata.CredentialManifestMultipleVCs, - Credential: testdata.SampleUDCVC, - DescriptorID: "udc_output", - } - - var b bytes.Buffer - cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response ResolveCredentialManifestResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Len(t, response.Resolved, 1) - }) - - t.Run("successfully resolve credential ID", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - request := &ResolveCredentialManifestRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Manifest: testdata.CredentialManifestMultipleVCs, - CredentialID: "http://example.edu/credentials/1872", - DescriptorID: "udc_output", - } - - var b bytes.Buffer - cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - var response ResolveCredentialManifestResponse - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - require.NotEmpty(t, response) - require.Len(t, response.Resolved, 1) - }) - - t.Run("failed to resolve - invalid requests", func(t *testing.T) { - cmd := New(mockctx, &Config{}) - - // missing manifest - request := &ResolveCredentialManifestRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Credential: testdata.SampleUDCVC, - DescriptorID: "udc_output", - } - - var b bytes.Buffer - cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, &request)) - validateError(t, cmdErr, command.ExecuteError, ResolveCredentialManifestErrorCode, - "failed to read credential manifest") - - // missing descriptor ID - request = &ResolveCredentialManifestRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Manifest: testdata.CredentialManifestMultipleVCs, - Credential: testdata.SampleUDCVC, - } - - b.Reset() - cmdErr = cmd.ResolveCredentialManifest(&b, getReader(t, &request)) - validateError(t, cmdErr, command.ExecuteError, ResolveCredentialManifestErrorCode, - "unable to find matching descriptor") - - // missing resolve option - request = &ResolveCredentialManifestRequest{ - WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, - Manifest: testdata.CredentialManifestMultipleVCs, - } - - b.Reset() - cmdErr = cmd.ResolveCredentialManifest(&b, getReader(t, &request)) - validateError(t, cmdErr, command.ExecuteError, ResolveCredentialManifestErrorCode, "invalid option") - - b.Reset() - cmdErr = cmd.ResolveCredentialManifest(&b, bytes.NewBufferString("==")) - validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") - }) -} - -func createSampleUserProfile(t *testing.T, ctx *mockprovider.Provider, request *CreateOrUpdateProfileRequest) { - cmd := New(ctx, &Config{}) - require.NotNil(t, cmd) - - var l bytes.Buffer - cmdErr := cmd.CreateProfile(&l, getReader(t, request)) - require.NoError(t, cmdErr) -} - -func getReader(t *testing.T, v interface{}) io.Reader { - vcReqBytes, err := json.Marshal(v) - require.NoError(t, err) - - return bytes.NewBuffer(vcReqBytes) -} - -func getUnlockToken(t *testing.T, b bytes.Buffer) string { - var response UnlockWalletResponse - - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - - return response.Token -} - -func unlockWallet(t *testing.T, ctx *mockprovider.Provider, request *UnlockWalletRequest) (string, func()) { - cmd := New(ctx, nil) - - var b bytes.Buffer - - cmdErr := cmd.Open(&b, getReader(t, &request)) - require.NoError(t, cmdErr) - - return getUnlockToken(t, b), func() { - cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: request.UserID})) - if cmdErr != nil { - t.Log(t, cmdErr) - } - } -} - -func addContent(t *testing.T, ctx *mockprovider.Provider, request *AddContentRequest) { - cmd := New(ctx, &Config{}) - - var b bytes.Buffer - defer b.Reset() - - cmdErr := cmd.Add(&b, getReader(t, &request)) - require.NoError(t, cmdErr) -} - -func validateError(t *testing.T, err command.Error, - expectedType command.Type, expectedCode command.Code, contains string) { - require.Error(t, err) - require.Equal(t, err.Type(), expectedType) - require.Equal(t, err.Code(), expectedCode) - - if contains != "" { - require.Contains(t, err.Error(), contains) - } -} - -func newMockProvider(t *testing.T) *mockprovider.Provider { - t.Helper() - - loader, err := ldtestutil.DocumentLoader() - require.NoError(t, err) - - serviceMap := map[string]interface{}{ - presentproofSvc.Name: &mockpresentproof.MockPresentProofSvc{}, - outofbandSvc.Name: &mockoutofband.MockOobService{}, - didexchange.DIDExchange: &mockdidexchange.MockDIDExchangeSvc{}, - mediator.Coordination: &mockmediator.MockMediatorSvc{}, - issuecredentialsvc.Name: &mockissuecredential.MockIssueCredentialSvc{}, - oobv2.Name: &mockoutofbandv2.MockOobService{}, - } - - return &mockprovider.Provider{ - StorageProviderValue: mockstorage.NewMockStoreProvider(), - ProtocolStateStorageProviderValue: mockstorage.NewMockStoreProvider(), - DocumentLoaderValue: loader, - ServiceMap: serviceMap, - } -} - -func getMockDIDKeyVDR() *mockvdr.MockVDRegistry { - return &mockvdr.MockVDRegistry{ - ResolveFunc: func(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { - if strings.HasPrefix(didID, "did:key:") { - k := key.New() - - d, e := k.Read(didID) - if e != nil { - return nil, e - } - - return d, nil - } - - return nil, fmt.Errorf("did not found") - }, - } -} - -func parseCredential(t *testing.T, b bytes.Buffer) *verifiable.Credential { - var response struct { - Credential json.RawMessage - } - - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - - loader, err := ldtestutil.DocumentLoader() - require.NoError(t, err) - - vc, err := verifiable.ParseCredential(response.Credential, verifiable.WithDisabledProofCheck(), - verifiable.WithJSONLDDocumentLoader(loader)) - require.NoError(t, err) - - return vc -} - -func parsePresentation(t *testing.T, b bytes.Buffer) *verifiable.Presentation { - var response struct { - Presentation json.RawMessage - } - - require.NoError(t, json.NewDecoder(&b).Decode(&response)) - - loader, err := ldtestutil.DocumentLoader() - require.NoError(t, err) - - vp, err := verifiable.ParsePresentation(response.Presentation, verifiable.WithPresDisabledProofCheck(), - verifiable.WithPresJSONLDDocumentLoader(loader)) - require.NoError(t, err) - - return vp -} - -// mock authz capability. -type mockAuthZCapability struct{} - -// GetHeaderSigner mock implementation. -func (s *mockAuthZCapability) GetHeaderSigner(authzKeyStoreURL, accessToken, secretShare string) HTTPHeaderSigner { - return &mockHeaderSigner{} -} - -// mock header signer. -type mockHeaderSigner struct{} - -// SignHeader mock implementation. -func (s *mockHeaderSigner) SignHeader(req *http.Request, capabilityBytes []byte) (*http.Header, error) { - return &http.Header{}, nil -} - -// mockMsg containing custom parent thread ID. -type mockMsg struct { - *service.DIDCommMsgMap - thID string -} - -func (m *mockMsg) ParentThreadID() string { - return m.thID -} - -func (m *mockMsg) ThreadID() (string, error) { - return m.thID, nil -} diff --git a/pkg/controller/command/vcwallet/didcomm.go b/pkg/controller/command/vcwallet/didcomm.go new file mode 100644 index 0000000000..1d08cddf00 --- /dev/null +++ b/pkg/controller/command/vcwallet/didcomm.go @@ -0,0 +1,347 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package vcwallet + +import ( + "encoding/json" + "io" + "time" + + "github.com/hyperledger/aries-framework-go/pkg/controller/command" + "github.com/hyperledger/aries-framework-go/pkg/controller/internal/cmdutil" + "github.com/hyperledger/aries-framework-go/pkg/internal/logutil" + "github.com/hyperledger/aries-framework-go/pkg/wallet" +) + +// CommandDidComm contains operations provided by verifiable credential wallet controller. +type CommandDidComm struct { + ctx provider +} + +// NewDidCommCommand returns new verifiable credential wallet controller command instance. +func NewDidCommCommand(p provider) *CommandDidComm { + cmd := &CommandDidComm{ctx: p} + + return cmd +} + +// GetHandlers returns list of all commands supported by this controller command. +func (o *CommandDidComm) GetHandlers() []command.Handler { + return []command.Handler{ + cmdutil.NewCommandHandler(CommandName, ConnectMethod, o.Connect), + cmdutil.NewCommandHandler(CommandName, ProposePresentationMethod, o.ProposePresentation), + cmdutil.NewCommandHandler(CommandName, PresentProofMethod, o.PresentProof), + cmdutil.NewCommandHandler(CommandName, ProposeCredentialMethod, o.ProposeCredential), + cmdutil.NewCommandHandler(CommandName, RequestCredentialMethod, o.RequestCredential), + cmdutil.NewCommandHandler(CommandName, ResolveCredentialManifestMethod, o.ResolveCredentialManifest), + } +} + +// Connect accepts out-of-band invitations and performs DID exchange. +func (o *CommandDidComm) Connect(rw io.Writer, req io.Reader) command.Error { + request := &ConnectRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) + + return command.NewExecuteError(DIDConnectErrorCode, err) + } + + didComm, err := wallet.NewDidComm(vcWallet, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) + + return command.NewExecuteError(DIDConnectErrorCode, err) + } + + connectionID, err := didComm.Connect(request.Auth, request.Invitation, + wallet.WithConnectTimeout(request.Timeout), wallet.WithReuseDID(request.ReuseConnection), + wallet.WithReuseAnyConnection(request.ReuseAnyConnection), wallet.WithMyLabel(request.MyLabel), + wallet.WithRouterConnections(request.RouterConnections...)) + if err != nil { + logutil.LogInfo(logger, CommandName, ConnectMethod, err.Error()) + + return command.NewExecuteError(DIDConnectErrorCode, err) + } + + command.WriteNillableResponse(rw, &ConnectResponse{ConnectionID: connectionID}, logger) + + logutil.LogDebug(logger, CommandName, ConnectMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID), + logutil.CreateKeyValueString(invitationIDString, request.Invitation.ID), + logutil.CreateKeyValueString(LabelString, request.MyLabel), + logutil.CreateKeyValueString(connectionIDString, connectionID)) + + return nil +} + +// ProposePresentation accepts out-of-band invitation and sends message proposing presentation +// from wallet to relying party. +// https://w3c-ccg.github.io/universal-wallet-interop-spec/#proposepresentation +// +// Currently Supporting +// [0454-present-proof-v2](https://github.com/hyperledger/aries-rfcs/tree/master/features/0454-present-proof-v2) +func (o *CommandDidComm) ProposePresentation(rw io.Writer, req io.Reader) command.Error { + request := &ProposePresentationRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) + + return command.NewExecuteError(ProposePresentationErrorCode, err) + } + + didComm, err := wallet.NewDidComm(vcWallet, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) + + return command.NewExecuteError(ProposePresentationErrorCode, err) + } + + msg, err := didComm.ProposePresentation(request.Auth, request.Invitation, + wallet.WithFromDID(request.FromDID), wallet.WithInitiateTimeout(request.Timeout), + wallet.WithConnectOptions(wallet.WithConnectTimeout(request.ConnectionOpts.Timeout), + wallet.WithReuseDID(request.ConnectionOpts.ReuseConnection), + wallet.WithReuseAnyConnection(request.ConnectionOpts.ReuseAnyConnection), + wallet.WithMyLabel(request.ConnectionOpts.MyLabel), + wallet.WithRouterConnections(request.ConnectionOpts.RouterConnections...))) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposePresentationMethod, err.Error()) + + return command.NewExecuteError(ProposePresentationErrorCode, err) + } + + command.WriteNillableResponse(rw, &ProposePresentationResponse{PresentationRequest: msg}, logger) + + logutil.LogDebug(logger, CommandName, ProposePresentationMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// PresentProof sends present proof message from wallet to relying party. +// https://w3c-ccg.github.io/universal-wallet-interop-spec/#presentproof +// +// Currently Supporting +// [0454-present-proof-v2](https://github.com/hyperledger/aries-rfcs/tree/master/features/0454-present-proof-v2) +// +func (o *CommandDidComm) PresentProof(rw io.Writer, req io.Reader) command.Error { + request := &PresentProofRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) + + return command.NewExecuteError(PresentProofErrorCode, err) + } + + didComm, err := wallet.NewDidComm(vcWallet, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) + + return command.NewExecuteError(PresentProofErrorCode, err) + } + + status, err := didComm.PresentProof(request.Auth, request.ThreadID, + prepareConcludeInteractionOpts(request.WaitForDone, request.Timeout, request.Presentation)...) + if err != nil { + logutil.LogInfo(logger, CommandName, PresentProofMethod, err.Error()) + + return command.NewExecuteError(PresentProofErrorCode, err) + } + + command.WriteNillableResponse(rw, status, logger) + + logutil.LogDebug(logger, CommandName, PresentProofMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// ProposeCredential sends propose credential message from wallet to issuer. +// 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 +// +func (o *CommandDidComm) ProposeCredential(rw io.Writer, req io.Reader) command.Error { + request := &ProposeCredentialRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) + + return command.NewExecuteError(ProposeCredentialErrorCode, err) + } + + didComm, err := wallet.NewDidComm(vcWallet, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) + + return command.NewExecuteError(ProposeCredentialErrorCode, err) + } + + msg, err := didComm.ProposeCredential(request.Auth, request.Invitation, + wallet.WithFromDID(request.FromDID), wallet.WithInitiateTimeout(request.Timeout), + wallet.WithConnectOptions(wallet.WithConnectTimeout(request.ConnectionOpts.Timeout), + wallet.WithReuseDID(request.ConnectionOpts.ReuseConnection), + wallet.WithReuseAnyConnection(request.ConnectionOpts.ReuseAnyConnection), + wallet.WithMyLabel(request.ConnectionOpts.MyLabel), + wallet.WithRouterConnections(request.ConnectionOpts.RouterConnections...))) + if err != nil { + logutil.LogInfo(logger, CommandName, ProposeCredentialMethod, err.Error()) + + return command.NewExecuteError(ProposeCredentialErrorCode, err) + } + + command.WriteNillableResponse(rw, &ProposeCredentialResponse{OfferCredential: msg}, logger) + + logutil.LogDebug(logger, CommandName, ProposeCredentialMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// RequestCredential sends request credential message from wallet to issuer and +// optionally waits for credential fulfillment. +// 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 +// +func (o *CommandDidComm) RequestCredential(rw io.Writer, req io.Reader) command.Error { + request := &RequestCredentialRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) + + return command.NewExecuteError(RequestCredentialErrorCode, err) + } + + didComm, err := wallet.NewDidComm(vcWallet, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) + + return command.NewExecuteError(RequestCredentialErrorCode, err) + } + + status, err := didComm.RequestCredential(request.Auth, request.ThreadID, + prepareConcludeInteractionOpts(request.WaitForDone, request.Timeout, request.Presentation)...) + if err != nil { + logutil.LogInfo(logger, CommandName, RequestCredentialMethod, err.Error()) + + return command.NewExecuteError(RequestCredentialErrorCode, err) + } + + command.WriteNillableResponse(rw, status, logger) + + logutil.LogDebug(logger, CommandName, RequestCredentialMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +// ResolveCredentialManifest resolves given credential manifest by credential fulfillment or credential. +// Supports: https://identity.foundation/credential-manifest/ +// +// Writes list of resolved descriptors to writer or returns error if operation fails. +// +func (o *CommandDidComm) ResolveCredentialManifest(rw io.Writer, req io.Reader) command.Error { + request := &ResolveCredentialManifestRequest{} + + err := json.NewDecoder(req).Decode(&request) + if err != nil { + logutil.LogInfo(logger, CommandName, ResolveCredentialManifestMethod, err.Error()) + + return command.NewValidationError(InvalidRequestErrorCode, err) + } + + vcWallet, err := wallet.New(request.UserID, o.ctx) + if err != nil { + logutil.LogInfo(logger, CommandName, ResolveCredentialManifestMethod, err.Error()) + + return command.NewExecuteError(ResolveCredentialManifestErrorCode, err) + } + + resolved, err := vcWallet.ResolveCredentialManifest(request.Auth, request.Manifest, + prepareResolveManifestOption(request)) + if err != nil { + logutil.LogInfo(logger, CommandName, ResolveCredentialManifestMethod, err.Error()) + + return command.NewExecuteError(ResolveCredentialManifestErrorCode, err) + } + + command.WriteNillableResponse(rw, &ResolveCredentialManifestResponse{Resolved: resolved}, logger) + + logutil.LogDebug(logger, CommandName, ResolveCredentialManifestMethod, logSuccess, + logutil.CreateKeyValueString(logUserIDKey, request.UserID)) + + return nil +} + +func prepareConcludeInteractionOpts(waitForDone bool, timeout time.Duration, presentation json.RawMessage) []wallet.ConcludeInteractionOptions { //nolint: lll + var options []wallet.ConcludeInteractionOptions + + if waitForDone { + options = append(options, wallet.WaitForDone(timeout)) + } + + return append(options, wallet.FromRawPresentation(presentation)) +} + +func prepareResolveManifestOption(rqst *ResolveCredentialManifestRequest) wallet.ResolveManifestOption { + if len(rqst.Fulfillment) > emptyRawLength { + return wallet.ResolveRawFulfillment(rqst.Fulfillment) + } + + if len(rqst.Credential) > emptyRawLength { + return wallet.ResolveRawCredential(rqst.DescriptorID, rqst.Credential) + } + + if rqst.CredentialID != "" { + return wallet.ResolveCredentialID(rqst.DescriptorID, rqst.CredentialID) + } + + return nil +} diff --git a/pkg/controller/command/vcwallet/didcomm_test.go b/pkg/controller/command/vcwallet/didcomm_test.go new file mode 100644 index 0000000000..b12c819509 --- /dev/null +++ b/pkg/controller/command/vcwallet/didcomm_test.go @@ -0,0 +1,982 @@ +/* +Copyright SecureKey Technologies Inc. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 +*/ + +package vcwallet + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strings" + "testing" + "time" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/hyperledger/aries-framework-go/internal/testdata" + outofbandClient "github.com/hyperledger/aries-framework-go/pkg/client/outofband" + "github.com/hyperledger/aries-framework-go/pkg/controller/command" + "github.com/hyperledger/aries-framework-go/pkg/controller/command/basewallet" + "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/didexchange" + issuecredentialsvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/issuecredential" + "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/mediator" + outofbandSvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofband" + oobv2 "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/outofbandv2" + presentproofSvc "github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/presentproof" + "github.com/hyperledger/aries-framework-go/pkg/doc/did" + vdrapi "github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr" + mockoutofbandv2 "github.com/hyperledger/aries-framework-go/pkg/internal/gomocks/client/outofbandv2" + "github.com/hyperledger/aries-framework-go/pkg/internal/ldtestutil" + mockdidexchange "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/didexchange" + mockissuecredential "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/issuecredential" + mockmediator "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/mediator" + mockoutofband "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/outofband" + mockpresentproof "github.com/hyperledger/aries-framework-go/pkg/mock/didcomm/protocol/presentproof" + mockprovider "github.com/hyperledger/aries-framework-go/pkg/mock/provider" + mockstorage "github.com/hyperledger/aries-framework-go/pkg/mock/storage" + mockvdr "github.com/hyperledger/aries-framework-go/pkg/mock/vdr" + "github.com/hyperledger/aries-framework-go/pkg/store/connection" + "github.com/hyperledger/aries-framework-go/pkg/vdr/key" + "github.com/hyperledger/aries-framework-go/pkg/wallet" +) + +const ( + sampleUserID = "sample-user01" + samplePassPhrase = "fakepassphrase" + sampleCommandError = "sample-command-error-01" + sampleFakeTkn = "sample-fake-token-01" + webRedirectStatusKey = "status" + webRedirectURLKey = "url" + exampleWebRedirect = "http://example.com/sample" +) + +func TestCommand_Connect(t *testing.T) { + const sampleDIDCommUser = "sample-didcomm-user01" + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + t.Run("successfully perform DID connect", 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 + + cmdDidComm := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmdDidComm.Connect(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response ConnectResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Equal(t, sampleConnID, response.ConnectionID) + }) + + t.Run("did connect failure", func(t *testing.T) { + oobSvc := &mockoutofband.MockOobService{ + AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) { + return "", fmt.Errorf(sampleCommandError) + }, + } + mockctx.ServiceMap[outofbandSvc.Name] = oobSvc + + cmd := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmd.Connect(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, DIDConnectErrorCode, sampleCommandError) + validateError(t, cmdErr, command.ExecuteError, DIDConnectErrorCode, "failed to accept invitation") + require.Empty(t, b.Bytes()) + }) + + t.Run("did connect failure - invalid request", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + var b bytes.Buffer + cmdErr := cmd.Connect(&b, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + }) + + t.Run("attempt to didconnect with invalid profile", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmd.Connect(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, DIDConnectErrorCode, "failed to get VC wallet profile") + require.Empty(t, b.Bytes()) + }) +} + +func TestCommand_ProposePresentation(t *testing.T) { + const sampleDIDCommUser = "sample-didcomm-user02" + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + const ( + myDID = "did:mydid:123" + theirDID = "did:theirdid:123" + ) + + t.Run("successfully send propose presentation", 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() + ppSvc := &mockpresentproof.MockPresentProofSvc{ + ActionsFunc: func() ([]presentproofSvc.Action, error) { + return []presentproofSvc.Action{ + { + PIID: thID, + Msg: service.NewDIDCommMsgMap(&presentproofSvc.RequestPresentationV2{ + Comment: "mock msg", + }), + MyDID: myDID, + TheirDID: theirDID, + }, + }, nil + }, + HandleFunc: func(service.DIDCommMsg) (string, error) { + return thID, nil + }, + } + mockctx.ServiceMap[presentproofSvc.Name] = ppSvc + + 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)) + + cmd := NewDidCommCommand(mockctx) + + request := &ProposePresentationRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + Invitation: &wallet.GenericInvitation{}, + } + + var b bytes.Buffer + cmdErr := cmd.ProposePresentation(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response ProposePresentationResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response.PresentationRequest) + }) + + t.Run("failed to send propose presentation", func(t *testing.T) { + oobSvc := &mockoutofband.MockOobService{ + AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) { + return "", fmt.Errorf(sampleCommandError) + }, + } + + mockctx.ServiceMap[outofbandSvc.Name] = oobSvc + + cmd := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmd.ProposePresentation(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, ProposePresentationErrorCode, sampleCommandError) + validateError(t, cmdErr, command.ExecuteError, ProposePresentationErrorCode, "failed to accept invitation") + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to send propose presentation - invalid request", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + var b bytes.Buffer + cmdErr := cmd.ProposePresentation(&b, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to send propose presentation - invalid profile", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmd.ProposePresentation(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, ProposePresentationErrorCode, "failed to get VC wallet profile") + require.Empty(t, b.Bytes()) + }) +} + +func TestCommand_PresentProof(t *testing.T) { + const sampleDIDCommUser = "sample-didcomm-user03" + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + t.Run("successfully present proof", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &PresentProofRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + ThreadID: uuid.New().String(), + Presentation: json.RawMessage{}, + } + + var b bytes.Buffer + cmdErr := cmd.PresentProof(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + }) + + t.Run("successfully present proof - wait for done", func(t *testing.T) { + thID := uuid.New().String() + mockPresentProofSvc := &mockpresentproof.MockPresentProofSvc{ + RegisterMsgEventHandle: func(ch chan<- service.StateMsg) error { + ch <- service.StateMsg{ + Type: service.PostState, + StateID: presentproofSvc.StateNameDone, + Properties: &mockdidexchange.MockEventProperties{ + Properties: map[string]interface{}{ + webRedirectStatusKey: model.AckStatusOK, + webRedirectURLKey: exampleWebRedirect, + }, + }, + Msg: &mockMsg{thID: thID}, + } + + return nil + }, + } + mockctx.ServiceMap[presentproofSvc.Name] = mockPresentProofSvc + + cmd := NewDidCommCommand(mockctx) + + request := &PresentProofRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + ThreadID: thID, + Presentation: json.RawMessage{}, + WaitForDone: true, + Timeout: 1 * time.Millisecond, + } + + var b bytes.Buffer + cmdErr := cmd.PresentProof(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response wallet.CredentialInteractionStatus + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Equal(t, exampleWebRedirect, response.RedirectURL) + require.Equal(t, model.AckStatusOK, response.Status) + }) + + t.Run("failed to present proof", func(t *testing.T) { + ppSvc := &mockpresentproof.MockPresentProofSvc{ + ActionContinueFunc: func(string, ...presentproofSvc.Opt) error { + return fmt.Errorf(sampleCommandError) + }, + } + + mockctx.ServiceMap[presentproofSvc.Name] = ppSvc + + cmd := NewDidCommCommand(mockctx) + + request := &PresentProofRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + ThreadID: uuid.New().String(), + Presentation: json.RawMessage{}, + } + + var b bytes.Buffer + cmdErr := cmd.PresentProof(&b, getReader(t, &request)) + validateError(t, cmdErr, command.ExecuteError, PresentProofErrorCode, sampleCommandError) + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to present proof - invalid request", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + var b bytes.Buffer + cmdErr := cmd.PresentProof(&b, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to present proof - invalid profile", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &PresentProofRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, + ThreadID: uuid.New().String(), + Presentation: json.RawMessage{}, + } + + var b bytes.Buffer + cmdErr := cmd.PresentProof(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, PresentProofErrorCode, "failed to get VC wallet profile") + require.Empty(t, b.Bytes()) + }) +} + +func TestCommand_ProposeCredential(t *testing.T) { + const ( + sampleDIDCommUser = "sample-didcomm-user02" + sampleMsgComment = "sample mock msg" + myDID = "did:mydid:123" + theirDID = "did:theirdid:123" + ) + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + t.Run("successfully send propose credential", 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.OfferCredentialV2{ + 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)) + + cmd := NewDidCommCommand(mockctx) + + request := &ProposeCredentialRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + Invitation: &wallet.GenericInvitation{}, + } + + var b bytes.Buffer + cmdErr := cmd.ProposeCredential(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response ProposeCredentialResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.NotEmpty(t, response.OfferCredential) + + offer := &issuecredentialsvc.OfferCredentialV2{} + + err = response.OfferCredential.Decode(offer) + require.NoError(t, err) + require.NotEmpty(t, offer) + require.Equal(t, sampleMsgComment, offer.Comment) + }) + + t.Run("failed to send propose credential", func(t *testing.T) { + oobSvc := &mockoutofband.MockOobService{ + AcceptInvitationHandle: func(*outofbandSvc.Invitation, outofbandSvc.Options) (string, error) { + return "", fmt.Errorf(sampleCommandError) + }, + } + + mockctx.ServiceMap[outofbandSvc.Name] = oobSvc + + cmd := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmd.ProposeCredential(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, ProposeCredentialErrorCode, sampleCommandError) + validateError(t, cmdErr, command.ExecuteError, ProposeCredentialErrorCode, "failed to accept invitation") + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to send propose credential - invalid request", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + var b bytes.Buffer + cmdErr := cmd.ProposeCredential(&b, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to send propose credential - invalid profile", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &ConnectRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: sampleFakeTkn}, + Invitation: &outofbandClient.Invitation{}, + ConnectOpts: ConnectOpts{ + MyLabel: "sample-label", + }, + } + + var b bytes.Buffer + cmdErr := cmd.ProposeCredential(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, ProposeCredentialErrorCode, "failed to get VC wallet profile") + require.Empty(t, b.Bytes()) + }) +} + +func TestCommand_RequestCredential(t *testing.T) { + const sampleDIDCommUser = "sample-didcomm-user03" + + mockctx := newMockProvider(t) + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleDIDCommUser, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + t.Run("successfully request credential", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &RequestCredentialRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + ThreadID: uuid.New().String(), + Presentation: json.RawMessage{}, + } + + var b bytes.Buffer + cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + }) + + t.Run("successfully request credential - wait for done", func(t *testing.T) { + thID := uuid.New().String() + + icSvc := &mockissuecredential.MockIssueCredentialSvc{ + RegisterMsgEventHandle: func(ch chan<- service.StateMsg) error { + ch <- service.StateMsg{ + Type: service.PostState, + StateID: "done", + Properties: &mockdidexchange.MockEventProperties{ + Properties: map[string]interface{}{ + webRedirectStatusKey: model.AckStatusOK, + webRedirectURLKey: exampleWebRedirect, + }, + }, + Msg: &mockMsg{thID: thID}, + } + + return nil + }, + } + mockctx.ServiceMap[issuecredentialsvc.Name] = icSvc + + cmd := NewDidCommCommand(mockctx) + + request := &RequestCredentialRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + ThreadID: thID, + Presentation: json.RawMessage{}, + WaitForDone: true, + Timeout: 600 * time.Millisecond, + } + + var b bytes.Buffer + cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response wallet.CredentialInteractionStatus + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Equal(t, exampleWebRedirect, response.RedirectURL) + require.Equal(t, model.AckStatusOK, response.Status) + }) + + t.Run("failed to request credential", func(t *testing.T) { + icSvc := &mockissuecredential.MockIssueCredentialSvc{ + ActionContinueFunc: func(string, ...issuecredentialsvc.Opt) error { + return fmt.Errorf(sampleCommandError) + }, + } + mockctx.ServiceMap[issuecredentialsvc.Name] = icSvc + + cmd := NewDidCommCommand(mockctx) + + request := &RequestCredentialRequest{ + WalletAuth: WalletAuth{UserID: sampleDIDCommUser, Auth: token}, + ThreadID: uuid.New().String(), + Presentation: json.RawMessage{}, + } + + var b bytes.Buffer + cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) + validateError(t, cmdErr, command.ExecuteError, RequestCredentialErrorCode, sampleCommandError) + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to request credential - invalid request", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + var b bytes.Buffer + cmdErr := cmd.RequestCredential(&b, bytes.NewBufferString("--")) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + require.Empty(t, b.Bytes()) + }) + + t.Run("failed to request credential - invalid profile", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &RequestCredentialRequest{ + WalletAuth: WalletAuth{UserID: sampleUserID, Auth: token}, + ThreadID: uuid.New().String(), + Presentation: json.RawMessage{}, + } + + var b bytes.Buffer + cmdErr := cmd.RequestCredential(&b, getReader(t, &request)) + require.Error(t, cmdErr) + + validateError(t, cmdErr, command.ExecuteError, RequestCredentialErrorCode, "failed to get VC wallet profile") + require.Empty(t, b.Bytes()) + }) +} + +func TestCommand_ResolveCredentialManifest(t *testing.T) { + const sampleUser1 = "sample-user-r01" + + mockctx := newMockProvider(t) + mockctx.VDRegistryValue = getMockDIDKeyVDR() + + createSampleUserProfile(t, mockctx, &CreateOrUpdateProfileRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + token, lock := unlockWallet(t, mockctx, &UnlockWalletRequest{ + UserID: sampleUser1, + LocalKMSPassphrase: samplePassPhrase, + }) + + defer lock() + + addContent(t, mockctx, &AddContentRequest{ + Content: testdata.SampleUDCVC, + ContentType: "credential", + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + }) + + t.Run("successfully resolve credential fulfillment", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &ResolveCredentialManifestRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Manifest: testdata.CredentialManifestMultipleVCs, + Fulfillment: testdata.CredentialFulfillmentWithMultipleVCs, + } + + var b bytes.Buffer + cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, request)) + require.NoError(t, cmdErr) + + var response ResolveCredentialManifestResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Len(t, response.Resolved, 2) + }) + + t.Run("successfully resolve credential", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &ResolveCredentialManifestRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Manifest: testdata.CredentialManifestMultipleVCs, + Credential: testdata.SampleUDCVC, + DescriptorID: "udc_output", + } + + var b bytes.Buffer + cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response ResolveCredentialManifestResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Len(t, response.Resolved, 1) + }) + + t.Run("successfully resolve credential ID", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + request := &ResolveCredentialManifestRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Manifest: testdata.CredentialManifestMultipleVCs, + CredentialID: "http://example.edu/credentials/1872", + DescriptorID: "udc_output", + } + + var b bytes.Buffer + cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + var response ResolveCredentialManifestResponse + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + require.NotEmpty(t, response) + require.Len(t, response.Resolved, 1) + }) + + t.Run("failed to resolve - invalid requests", func(t *testing.T) { + cmd := NewDidCommCommand(mockctx) + + // missing manifest + request := &ResolveCredentialManifestRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Credential: testdata.SampleUDCVC, + DescriptorID: "udc_output", + } + + var b bytes.Buffer + cmdErr := cmd.ResolveCredentialManifest(&b, getReader(t, &request)) + validateError(t, cmdErr, command.ExecuteError, ResolveCredentialManifestErrorCode, + "failed to read credential manifest") + + // missing descriptor ID + request = &ResolveCredentialManifestRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Manifest: testdata.CredentialManifestMultipleVCs, + Credential: testdata.SampleUDCVC, + } + + b.Reset() + cmdErr = cmd.ResolveCredentialManifest(&b, getReader(t, &request)) + validateError(t, cmdErr, command.ExecuteError, ResolveCredentialManifestErrorCode, + "unable to find matching descriptor") + + // missing resolve option + request = &ResolveCredentialManifestRequest{ + WalletAuth: WalletAuth{UserID: sampleUser1, Auth: token}, + Manifest: testdata.CredentialManifestMultipleVCs, + } + + b.Reset() + cmdErr = cmd.ResolveCredentialManifest(&b, getReader(t, &request)) + validateError(t, cmdErr, command.ExecuteError, ResolveCredentialManifestErrorCode, "invalid option") + + b.Reset() + cmdErr = cmd.ResolveCredentialManifest(&b, bytes.NewBufferString("==")) + validateError(t, cmdErr, command.ValidationError, InvalidRequestErrorCode, "invalid character") + }) +} + +func createSampleUserProfile(t *testing.T, ctx *mockprovider.Provider, request *CreateOrUpdateProfileRequest) { + cmd := basewallet.New(ctx, &Config{}) + require.NotNil(t, cmd) + + var l bytes.Buffer + cmdErr := cmd.CreateProfile(&l, getReader(t, request)) + require.NoError(t, cmdErr) +} + +func getReader(t *testing.T, v interface{}) io.Reader { + vcReqBytes, err := json.Marshal(v) + require.NoError(t, err) + + return bytes.NewBuffer(vcReqBytes) +} + +func getUnlockToken(t *testing.T, b bytes.Buffer) string { + var response UnlockWalletResponse + + require.NoError(t, json.NewDecoder(&b).Decode(&response)) + + return response.Token +} + +func unlockWallet(t *testing.T, ctx *mockprovider.Provider, request *UnlockWalletRequest) (string, func()) { + cmd := basewallet.New(ctx, nil) + + var b bytes.Buffer + + cmdErr := cmd.Open(&b, getReader(t, &request)) + require.NoError(t, cmdErr) + + return getUnlockToken(t, b), func() { + cmdErr = cmd.Close(&b, getReader(t, &LockWalletRequest{UserID: request.UserID})) + if cmdErr != nil { + t.Log(t, cmdErr) + } + } +} + +func addContent(t *testing.T, ctx *mockprovider.Provider, request *AddContentRequest) { + cmd := basewallet.New(ctx, &Config{}) + + var b bytes.Buffer + defer b.Reset() + + cmdErr := cmd.Add(&b, getReader(t, &request)) + require.NoError(t, cmdErr) +} + +func validateError(t *testing.T, err command.Error, + expectedType command.Type, expectedCode command.Code, contains string) { + require.Error(t, err) + require.Equal(t, err.Type(), expectedType) + require.Equal(t, err.Code(), expectedCode) + + if contains != "" { + require.Contains(t, err.Error(), contains) + } +} + +func newMockProvider(t *testing.T) *mockprovider.Provider { + t.Helper() + + loader, err := ldtestutil.DocumentLoader() + require.NoError(t, err) + + serviceMap := map[string]interface{}{ + presentproofSvc.Name: &mockpresentproof.MockPresentProofSvc{}, + outofbandSvc.Name: &mockoutofband.MockOobService{}, + didexchange.DIDExchange: &mockdidexchange.MockDIDExchangeSvc{}, + mediator.Coordination: &mockmediator.MockMediatorSvc{}, + issuecredentialsvc.Name: &mockissuecredential.MockIssueCredentialSvc{}, + oobv2.Name: &mockoutofbandv2.MockOobService{}, + } + + return &mockprovider.Provider{ + StorageProviderValue: mockstorage.NewMockStoreProvider(), + ProtocolStateStorageProviderValue: mockstorage.NewMockStoreProvider(), + DocumentLoaderValue: loader, + ServiceMap: serviceMap, + } +} + +func getMockDIDKeyVDR() *mockvdr.MockVDRegistry { + return &mockvdr.MockVDRegistry{ + ResolveFunc: func(didID string, opts ...vdrapi.DIDMethodOption) (*did.DocResolution, error) { + if strings.HasPrefix(didID, "did:key:") { + k := key.New() + + d, e := k.Read(didID) + if e != nil { + return nil, e + } + + return d, nil + } + + return nil, fmt.Errorf("did not found") + }, + } +} + +// mockMsg containing custom parent thread ID. +type mockMsg struct { + *service.DIDCommMsgMap + thID string +} + +func (m *mockMsg) ParentThreadID() string { + return m.thID +} + +func (m *mockMsg) ThreadID() (string, error) { + return m.thID, nil +} diff --git a/pkg/controller/command/vcwallet/models.go b/pkg/controller/command/vcwallet/models.go index 030948d0b5..de6433ae9c 100644 --- a/pkg/controller/command/vcwallet/models.go +++ b/pkg/controller/command/vcwallet/models.go @@ -11,308 +11,95 @@ import ( "time" "github.com/hyperledger/aries-framework-go/pkg/client/outofband" + "github.com/hyperledger/aries-framework-go/pkg/controller/command/basewallet" "github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service" "github.com/hyperledger/aries-framework-go/pkg/doc/cm" - "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" - "github.com/hyperledger/aries-framework-go/pkg/kms" "github.com/hyperledger/aries-framework-go/pkg/wallet" ) // CreateOrUpdateProfileRequest is request model for // creating a new wallet profile or updating an existing wallet profile. -type CreateOrUpdateProfileRequest struct { - // Unique identifier to identify wallet user - UserID string `json:"userID"` - - // passphrase for local kms for key operations. - // Optional, if this option is provided then wallet for this profile will use local KMS for key operations. - LocalKMSPassphrase string `json:"localKMSPassphrase,omitempty"` - - // passphrase for web/remote kms for key operations. - // Optional, if this option is provided then wallet for this profile will use web/remote KMS for key operations. - KeyStoreURL string `json:"keyStoreURL,omitempty"` - - // edv configuration for storing wallet contents for this profile - // Optional, if not provided then agent storage provider will be used as store provider. - EDVConfiguration *EDVConfiguration `json:"edvConfiguration,omitempty"` -} +type CreateOrUpdateProfileRequest = basewallet.CreateOrUpdateProfileRequest // EDVConfiguration contains configuration for EDV settings for profile creation. -type EDVConfiguration struct { - // EDV server URL for storing wallet contents. - ServerURL string `json:"serverURL,omitempty"` - - // EDV vault ID for storing the wallet contents. - VaultID string `json:"vaultID,omitempty"` - - // Encryption key ID of already existing key in wallet profile kms. - // If profile is using localkms then wallet will create this key set for wallet user. - EncryptionKeyID string `json:"encryptionKID,omitempty"` - - // MAC operation key ID of already existing key in wallet profile kms. - // If profile is using localkms then wallet will create this key set for wallet user. - MACKeyID string `json:"macKID,omitempty"` -} +type EDVConfiguration = basewallet.EDVConfiguration // UnlockWalletRequest contains different options for unlocking wallet. -type UnlockWalletRequest struct { - // user ID of the wallet to be unlocked. - UserID string `json:"userID"` - - // passphrase for local kms for key operations. - // Optional, to be used if profile for this wallet user is setup with local KMS. - LocalKMSPassphrase string `json:"localKMSPassphrase,omitempty"` - - // WebKMSAuth for authorizing acccess to web/remote kms. - // Optional, to be used if profile for this wallet user is setup with web/remote KMS. - WebKMSAuth *UnlockAuth `json:"webKMSAuth"` - - // Options for authorizing access to wallet's EDV content store. - // Optional, to be used only if profile for this wallet user is setup to use EDV as content store. - EDVUnlock *UnlockAuth `json:"edvUnlocks"` - - // Time duration in milliseconds after which wallet will expire its unlock status. - Expiry time.Duration `json:"expiry,omitempty"` -} +type UnlockWalletRequest = basewallet.UnlockWalletRequest // UnlockAuth contains different options for authorizing access to wallet's EDV content store & webkms. -type UnlockAuth struct { - // Http header 'authorization' bearer token to be used. - // Optional, only if required by wallet user (for webkms or edv). - AuthToken string `json:"authToken,omitempty"` - - // Http header 'authorization' GNAP token to be used. - // Optional, only if required by wallet user (for webkms or edv). - GNAPToken string `json:"gnapToken,omitempty"` - - // Capability if ZCAP sign header feature to be used for authorizing access. - // Optional, can be used only if ZCAP sign header feature is configured with command controller. - Capability string `json:"capability,omitempty"` - - // AuthZKeyStoreURL if ZCAP sign header feature to be used for authorizing access. - // Optional, can be used only if ZCAP sign header feature is configured with command controller. - AuthZKeyStoreURL string `json:"authzKeyStoreURL,omitempty"` - - // SecretShare if ZCAP sign header feature to be used for authorizing access. - // Optional, can be used only if ZCAP sign header feature is configured with command controller. - SecretShare string `json:"secretShare,omitempty"` -} +type UnlockAuth = basewallet.UnlockAuth // UnlockWalletResponse contains response for wallet unlock operation. -type UnlockWalletResponse struct { - // Token for granting access to wallet for subsequent wallet operations. - Token string `json:"token,omitempty"` -} +type UnlockWalletResponse = basewallet.UnlockWalletResponse // LockWalletRequest contains options for locking wallet. -type LockWalletRequest struct { - // user ID of the wallet to be locked. - UserID string `json:"userID"` -} +type LockWalletRequest = basewallet.LockWalletRequest // LockWalletResponse contains response for wallet lock operation. -type LockWalletResponse struct { - // Closed status of the wallet lock operation. - // if true, wallet is closed successfully - // if false, wallet is already closed or never unlocked. - Closed bool `json:"closed"` -} +type LockWalletResponse = basewallet.LockWalletResponse // WalletAuth contains wallet auth parameters for performing wallet operations. -type WalletAuth struct { - // Authorization token for performing wallet operations. - Auth string `json:"auth"` - - // ID of wallet user. - UserID string `json:"userID"` -} +type WalletAuth = basewallet.WalletAuth // WalletUser contains wallet user info for performing profile operations. -type WalletUser struct { - // ID of wallet user. - ID string `json:"userID"` -} +type WalletUser = basewallet.WalletUser // AddContentRequest is request for adding a content to wallet. -type AddContentRequest struct { - WalletAuth - - // type of the content to be added to the wallet. - // supported types: collection, credential, didResolutionResponse, metadata, connection, key - ContentType wallet.ContentType `json:"contentType"` - - // content to be added to wallet content store. - Content json.RawMessage `json:"content"` - - // ID of the wallet collection to which this content should belong. - CollectionID string `json:"collectionID"` -} +type AddContentRequest = basewallet.AddContentRequest // RemoveContentRequest is request for removing a content from wallet. -type RemoveContentRequest struct { - WalletAuth - - // type of the content to be removed from the wallet. - // supported types: collection, credential, didResolutionResponse, metadata, connection - ContentType wallet.ContentType `json:"contentType"` - - // ID of the content to be removed from wallet - ContentID string `json:"contentID"` -} +type RemoveContentRequest = basewallet.RemoveContentRequest // GetContentRequest is request for getting a content from wallet. -type GetContentRequest struct { - WalletAuth - - // type of the content to be returned from wallet. - // supported types: collection, credential, didResolutionResponse, metadata, connection - ContentType wallet.ContentType `json:"contentType"` - - // ID of the content to be returned from wallet - ContentID string `json:"contentID"` -} +type GetContentRequest = basewallet.GetContentRequest // GetContentResponse response for get content from wallet operation. -type GetContentResponse struct { - // content retrieved from wallet content store. - Content json.RawMessage `json:"content"` -} +type GetContentResponse = basewallet.GetContentResponse // GetAllContentRequest is request for getting all contents from wallet for given content type. -type GetAllContentRequest struct { - WalletAuth - - // type of the contents to be returned from wallet. - // supported types: collection, credential, didResolutionResponse, metadata, connection - ContentType wallet.ContentType `json:"contentType"` - - // ID of the collection on which the response contents to be filtered. - CollectionID string `json:"collectionID,omitempty"` -} +type GetAllContentRequest = basewallet.GetAllContentRequest // GetAllContentResponse response for get all content by content type wallet operation. -type GetAllContentResponse struct { - // contents retrieved from wallet content store. - // map of content ID to content. - Contents map[string]json.RawMessage `json:"contents"` -} +type GetAllContentResponse = basewallet.GetAllContentResponse // ContentQueryRequest is request model for querying wallet contents. -type ContentQueryRequest struct { - WalletAuth - - // credential query(s) for querying wallet contents. - Query []*wallet.QueryParams `json:"query"` -} +type ContentQueryRequest = basewallet.ContentQueryRequest // ContentQueryResponse response for wallet content query. -type ContentQueryResponse struct { - // response presentation(s) containing query results. - Results []*verifiable.Presentation `json:"results"` -} +type ContentQueryResponse = basewallet.ContentQueryResponse // IssueRequest is request model for issuing credential from wallet. -type IssueRequest struct { - WalletAuth - - // raw credential to be issued from wallet. - Credential json.RawMessage `json:"credential"` - - // proof options for issuing credential - ProofOptions *wallet.ProofOptions `json:"proofOptions"` -} +type IssueRequest = basewallet.IssueRequest // IssueResponse is response for issue credential interface from wallet. -type IssueResponse struct { - // credential issued. - Credential *verifiable.Credential `json:"credential"` -} +type IssueResponse = basewallet.IssueResponse // ProveRequest for producing verifiable presentation from wallet. // Contains options for proofs and credential. Any combination of credential option can be mixed. -type ProveRequest struct { - WalletAuth - - // IDs of credentials already saved in wallet content store. - StoredCredentials []string `json:"storedCredentials"` - - // List of raw credentials to be presented. - RawCredentials []json.RawMessage `json:"rawCredentials"` - - // Presentation to be proved. - Presentation json.RawMessage `json:"presentation"` - - // proof options for issuing credential. - ProofOptions *wallet.ProofOptions `json:"proofOptions"` -} +type ProveRequest = basewallet.ProveRequest // ProveResponse contains response presentation from prove operation. -type ProveResponse struct { - // presentation response from prove operation. - Presentation *verifiable.Presentation `json:"presentation"` -} +type ProveResponse = basewallet.ProveResponse // VerifyRequest request for verifying a credential or presentation from wallet. // Any one of the credential option should be used. -type VerifyRequest struct { - WalletAuth - - // ID of the credential already saved in wallet content store. - // optional, if provided then this option takes precedence over other options. - StoredCredentialID string `json:"storedCredentialID"` - - // List of raw credential to be presented. - // optional, if provided then this option takes precedence over presentation options. - RawCredential json.RawMessage `json:"rawCredential"` - - // Presentation to be proved. - // optional, will be used only if other options are not provided. - Presentation json.RawMessage `json:"presentation"` -} +type VerifyRequest = basewallet.VerifyRequest // VerifyResponse is response model for wallet verify operation. -type VerifyResponse struct { - // if true then verification is successful. - Verified bool `json:"verified"` - - // error details if verified is false. - Error string `json:"error,omitempty"` -} +type VerifyResponse = basewallet.VerifyResponse // DeriveRequest is request model for deriving a credential from wallet. -type DeriveRequest struct { - WalletAuth - - // ID of the credential already saved in wallet content store. - // optional, if provided then this option takes precedence. - StoredCredentialID string `json:"storedCredentialID"` - - // List of raw credential to be presented. - // optional, will be used only if other options is not provided. - RawCredential json.RawMessage `json:"rawCredential"` - - // DeriveOptions options for deriving credential - *wallet.DeriveOptions `json:"deriveOption"` -} +type DeriveRequest = basewallet.DeriveRequest // DeriveResponse is response for derived credential operation. -type DeriveResponse struct { - // credential derived. - Credential *verifiable.Credential `json:"credential"` -} +type DeriveResponse = basewallet.DeriveResponse // CreateKeyPairRequest is request model for creating key pair from wallet. -type CreateKeyPairRequest struct { - WalletAuth - - // type of the key to be created. - KeyType kms.KeyType `json:"keyType,omitempty"` -} +type CreateKeyPairRequest = basewallet.CreateKeyPairRequest // CreateKeyPairResponse is response model for creating key pair from wallet. -type CreateKeyPairResponse struct { - *wallet.KeyPair -} +type CreateKeyPairResponse = basewallet.CreateKeyPairResponse // ConnectRequest is request model for wallet DID connect operation. type ConnectRequest struct { diff --git a/pkg/controller/rest/vcwallet/operation.go b/pkg/controller/rest/vcwallet/operation.go index ee2a561179..14d1459f1c 100644 --- a/pkg/controller/rest/vcwallet/operation.go +++ b/pkg/controller/rest/vcwallet/operation.go @@ -14,6 +14,7 @@ import ( "github.com/gorilla/mux" "github.com/piprate/json-gold/ld" + "github.com/hyperledger/aries-framework-go/pkg/controller/command/basewallet" "github.com/hyperledger/aries-framework-go/pkg/controller/command/vcwallet" "github.com/hyperledger/aries-framework-go/pkg/controller/internal/cmdutil" "github.com/hyperledger/aries-framework-go/pkg/controller/rest" @@ -75,16 +76,19 @@ type didCommProvider interface { } // Operation contains REST operations provided by verifiable credential wallet. +// Wraps basewallet.Command and vcwallet.CommandDidComm in the same ways as vcwallet.Command. type Operation struct { handlers []rest.Handler - command *vcwallet.Command + command *basewallet.Command + didcomm *vcwallet.CommandDidComm } // New returns new verfiable credential wallet REST controller. func New(p provider, config *vcwallet.Config) *Operation { - cmd := vcwallet.New(p, config) + cmd := basewallet.New(p, config) + didcomm := vcwallet.NewDidCommCommand(p) - o := &Operation{command: cmd} + o := &Operation{command: cmd, didcomm: didcomm} o.registerHandler() @@ -356,7 +360,7 @@ func (o *Operation) CreateKeyPair(rw http.ResponseWriter, req *http.Request) { // default: genericError // 200: connectRes func (o *Operation) Connect(rw http.ResponseWriter, req *http.Request) { - rest.Execute(o.command.Connect, rw, req.Body) + rest.Execute(o.didcomm.Connect, rw, req.Body) } // ProposePresentation swagger:route POST /vcwallet/propose-presentation vcwallet proposePresReq @@ -372,7 +376,7 @@ func (o *Operation) Connect(rw http.ResponseWriter, req *http.Request) { // default: genericError // 200: proposePresRes func (o *Operation) ProposePresentation(rw http.ResponseWriter, req *http.Request) { - rest.Execute(o.command.ProposePresentation, rw, req.Body) + rest.Execute(o.didcomm.ProposePresentation, rw, req.Body) } // PresentProof swagger:route POST /vcwallet/present-proof vcwallet presentProofReq @@ -387,7 +391,7 @@ func (o *Operation) ProposePresentation(rw http.ResponseWriter, req *http.Reques // default: genericError // 200: presentProofRes func (o *Operation) PresentProof(rw http.ResponseWriter, req *http.Request) { - rest.Execute(o.command.PresentProof, rw, req.Body) + rest.Execute(o.didcomm.PresentProof, rw, req.Body) } // ProposeCredential swagger:route POST /vcwallet/propose-credential vcwallet proposeCredReq @@ -402,7 +406,7 @@ func (o *Operation) PresentProof(rw http.ResponseWriter, req *http.Request) { // default: genericError // 200: proposeCredRes func (o *Operation) ProposeCredential(rw http.ResponseWriter, req *http.Request) { - rest.Execute(o.command.ProposeCredential, rw, req.Body) + rest.Execute(o.didcomm.ProposeCredential, rw, req.Body) } // RequestCredential swagger:route POST /vcwallet/request-credential vcwallet requestCredReq @@ -417,7 +421,7 @@ func (o *Operation) ProposeCredential(rw http.ResponseWriter, req *http.Request) // default: genericError // 200: requestCredRes func (o *Operation) RequestCredential(rw http.ResponseWriter, req *http.Request) { - rest.Execute(o.command.RequestCredential, rw, req.Body) + rest.Execute(o.didcomm.RequestCredential, rw, req.Body) } // ResolveCredentialManifest swagger:route POST /vcwallet/resolve-credential-manifest vcwallet resolveCredManifest @@ -429,7 +433,7 @@ func (o *Operation) RequestCredential(rw http.ResponseWriter, req *http.Request) // default: genericError // 200: resolveCredManifest func (o *Operation) ResolveCredentialManifest(rw http.ResponseWriter, req *http.Request) { - rest.Execute(o.command.ResolveCredentialManifest, rw, req.Body) + rest.Execute(o.didcomm.ResolveCredentialManifest, rw, req.Body) } // getIDFromRequest returns ID from request.