code.vegaprotocol.io/vega@v0.79.0/wallet/api/client_list_keys_test.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package api_test
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"testing"
    22  
    23  	"code.vegaprotocol.io/vega/libs/jsonrpc"
    24  	vgrand "code.vegaprotocol.io/vega/libs/rand"
    25  	"code.vegaprotocol.io/vega/wallet/api"
    26  	"code.vegaprotocol.io/vega/wallet/api/mocks"
    27  	"code.vegaprotocol.io/vega/wallet/wallet"
    28  
    29  	"github.com/golang/mock/gomock"
    30  	"github.com/stretchr/testify/assert"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  func TestClientListKeys(t *testing.T) {
    35  	t.Run("Documentation matches the code", testClientListKeysSchemaCorrect)
    36  	t.Run("Listing keys with enough permissions succeeds", testListingKeysWithEnoughPermissionsSucceeds)
    37  	t.Run("Listing keys without enough permissions succeeds", testListingKeysWithoutEnoughPermissionsSucceeds)
    38  	t.Run("Getting internal error during wallet retrieval does not update the permissions", testListingKeysGettingInternalErrorDuringWalletRetrievalDoesNotUpdatePermissions)
    39  	t.Run("Retrieving a locked wallet does not update the permissions", testListingKeysRetrievingLockedWalletDoesNotUpdatePermissions)
    40  	t.Run("Refusing permissions update does not update the permissions", testListingKeysRefusingPermissionsUpdateDoesNotUpdatePermissions)
    41  	t.Run("Cancelling the permissions review does not update the permissions", testListingKeysCancellingTheReviewDoesNotUpdatePermissions)
    42  	t.Run("Interrupting the request does not update the permissions", testListingKeysInterruptingTheRequestDoesNotUpdatePermissions)
    43  	t.Run("Getting internal error during the review does not update the permissions", testListingKeysGettingInternalErrorDuringReviewDoesNotUpdatePermissions)
    44  	t.Run("Getting internal error during the wallet update does not update the permissions", testListingKeysGettingInternalErrorDuringWalletUpdateDoesNotUpdatePermissions)
    45  }
    46  
    47  func testClientListKeysSchemaCorrect(t *testing.T) {
    48  	assertEqualSchema(t, "client.list_keys", nil, api.ClientListKeysResult{})
    49  }
    50  
    51  func testListingKeysWithEnoughPermissionsSucceeds(t *testing.T) {
    52  	// given
    53  	ctx, _ := clientContextForTest()
    54  	hostname := vgrand.RandomStr(5)
    55  	w, kps := walletWithKeys(t, 2)
    56  	if err := w.UpdatePermissions(hostname, wallet.Permissions{
    57  		PublicKeys: wallet.PublicKeysPermission{
    58  			Access: wallet.ReadAccess,
    59  		},
    60  	}); err != nil {
    61  		t.Fatalf(err.Error())
    62  	}
    63  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
    64  	if err != nil {
    65  		t.Fatalf(err.Error())
    66  	}
    67  
    68  	// setup
    69  	handler := newListKeysHandler(t)
    70  	// -- expected calls
    71  
    72  	// when
    73  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
    74  
    75  	// then
    76  	require.Nil(t, errorDetails)
    77  	assert.Equal(t, []api.ClientNamedPublicKey{
    78  		{
    79  			Name:      kps[0].Name(),
    80  			PublicKey: kps[0].PublicKey(),
    81  		}, {
    82  			Name:      kps[1].Name(),
    83  			PublicKey: kps[1].PublicKey(),
    84  		},
    85  	}, result.Keys)
    86  }
    87  
    88  func testListingKeysWithoutEnoughPermissionsSucceeds(t *testing.T) {
    89  	// given
    90  	ctx, traceID := clientContextForTest()
    91  	hostname := vgrand.RandomStr(5)
    92  	w, kps := walletWithKeys(t, 2)
    93  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
    94  	if err != nil {
    95  		t.Fatalf(err.Error())
    96  	}
    97  
    98  	// setup
    99  	handler := newListKeysHandler(t)
   100  	// -- expected calls
   101  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   102  	handler.walletStore.EXPECT().GetWallet(ctx, connectedWallet.Name()).Times(1).Return(w, nil)
   103  	handler.interactor.EXPECT().RequestPermissionsReview(ctx, traceID, uint8(1), hostname, w.Name(), map[string]string{
   104  		"public_keys": "read",
   105  	}).Times(1).Return(true, nil)
   106  	handler.walletStore.EXPECT().UpdateWallet(ctx, w).Times(1).Return(nil)
   107  	handler.interactor.EXPECT().NotifySuccessfulRequest(ctx, traceID, uint8(2), api.PermissionsSuccessfullyUpdated)
   108  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   109  
   110  	// when
   111  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   112  
   113  	// then
   114  	require.Nil(t, errorDetails)
   115  	assert.Equal(t, []api.ClientNamedPublicKey{
   116  		{
   117  			Name:      kps[0].Name(),
   118  			PublicKey: kps[0].PublicKey(),
   119  		}, {
   120  			Name:      kps[1].Name(),
   121  			PublicKey: kps[1].PublicKey(),
   122  		},
   123  	}, result.Keys)
   124  }
   125  
   126  func testListingKeysGettingInternalErrorDuringWalletRetrievalDoesNotUpdatePermissions(t *testing.T) {
   127  	// given
   128  	ctx, traceID := clientContextForTest()
   129  	hostname := vgrand.RandomStr(5)
   130  	w, _ := walletWithKeys(t, 2)
   131  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   132  	if err != nil {
   133  		t.Fatalf(err.Error())
   134  	}
   135  
   136  	// setup
   137  	handler := newListKeysHandler(t)
   138  	// -- expected calls
   139  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   140  	handler.walletStore.EXPECT().GetWallet(ctx, connectedWallet.Name()).Times(1).Return(nil, assert.AnError)
   141  	handler.interactor.EXPECT().NotifyError(ctx, traceID, api.InternalErrorType, fmt.Errorf("could not retrieve the wallet for the permissions update: %w", assert.AnError)).Times(1)
   142  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   143  
   144  	// when
   145  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   146  
   147  	// then
   148  	assertInternalError(t, errorDetails, api.ErrCouldNotListKeys)
   149  	assert.Empty(t, result)
   150  }
   151  
   152  func testListingKeysRetrievingLockedWalletDoesNotUpdatePermissions(t *testing.T) {
   153  	// given
   154  	ctx, traceID := clientContextForTest()
   155  	hostname := vgrand.RandomStr(5)
   156  	w, _ := walletWithKeys(t, 2)
   157  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   158  	if err != nil {
   159  		t.Fatalf(err.Error())
   160  	}
   161  
   162  	// setup
   163  	handler := newListKeysHandler(t)
   164  	// -- expected calls
   165  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   166  	handler.walletStore.EXPECT().GetWallet(ctx, connectedWallet.Name()).Times(1).Return(nil, api.ErrWalletIsLocked)
   167  	handler.interactor.EXPECT().NotifyError(ctx, traceID, api.ApplicationErrorType, api.ErrWalletIsLocked).Times(1)
   168  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   169  
   170  	// when
   171  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   172  
   173  	// then
   174  	assertInternalError(t, errorDetails, api.ErrCouldNotListKeys)
   175  	assert.Empty(t, result)
   176  }
   177  
   178  func testListingKeysRefusingPermissionsUpdateDoesNotUpdatePermissions(t *testing.T) {
   179  	// given
   180  	ctx, traceID := clientContextForTest()
   181  	hostname := vgrand.RandomStr(5)
   182  	w, _ := walletWithKeys(t, 2)
   183  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   184  	if err != nil {
   185  		t.Fatalf(err.Error())
   186  	}
   187  
   188  	// setup
   189  	handler := newListKeysHandler(t)
   190  	// -- expected calls
   191  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   192  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   193  	handler.interactor.EXPECT().RequestPermissionsReview(ctx, traceID, uint8(1), hostname, w.Name(), map[string]string{
   194  		"public_keys": "read",
   195  	}).Times(1).Return(false, nil)
   196  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   197  
   198  	// when
   199  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   200  
   201  	// then
   202  	assertUserRejectionError(t, errorDetails, api.ErrUserRejectedAccessToKeys)
   203  	assert.Empty(t, result)
   204  }
   205  
   206  func testListingKeysCancellingTheReviewDoesNotUpdatePermissions(t *testing.T) {
   207  	// given
   208  	ctx, traceID := clientContextForTest()
   209  	hostname := vgrand.RandomStr(5)
   210  	w, _ := walletWithKeys(t, 2)
   211  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   212  	if err != nil {
   213  		t.Fatalf(err.Error())
   214  	}
   215  
   216  	// setup
   217  	handler := newListKeysHandler(t)
   218  	// -- expected calls
   219  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   220  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   221  	handler.interactor.EXPECT().RequestPermissionsReview(ctx, traceID, uint8(1), hostname, w.Name(), map[string]string{
   222  		"public_keys": "read",
   223  	}).Times(1).Return(false, api.ErrUserCloseTheConnection)
   224  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   225  	handler.interactor.EXPECT().NotifyError(ctx, traceID, api.ApplicationErrorType, api.ErrConnectionClosed)
   226  
   227  	// when
   228  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   229  
   230  	// then
   231  	assertConnectionClosedError(t, errorDetails)
   232  	assert.Empty(t, result)
   233  }
   234  
   235  func testListingKeysInterruptingTheRequestDoesNotUpdatePermissions(t *testing.T) {
   236  	// given
   237  	ctx, traceID := clientContextForTest()
   238  	hostname := vgrand.RandomStr(5)
   239  	w, _ := walletWithKeys(t, 2)
   240  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   241  	if err != nil {
   242  		t.Fatalf(err.Error())
   243  	}
   244  
   245  	// setup
   246  	handler := newListKeysHandler(t)
   247  	// -- expected calls
   248  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   249  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   250  	handler.interactor.EXPECT().RequestPermissionsReview(ctx, traceID, uint8(1), hostname, w.Name(), map[string]string{
   251  		"public_keys": "read",
   252  	}).Times(1).Return(false, api.ErrRequestInterrupted)
   253  	handler.interactor.EXPECT().NotifyError(ctx, traceID, api.ServerErrorType, api.ErrRequestInterrupted).Times(1)
   254  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   255  
   256  	// when
   257  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   258  
   259  	// then
   260  	assertRequestInterruptionError(t, errorDetails)
   261  	assert.Empty(t, result)
   262  }
   263  
   264  func testListingKeysGettingInternalErrorDuringReviewDoesNotUpdatePermissions(t *testing.T) {
   265  	// given
   266  	ctx, traceID := clientContextForTest()
   267  	hostname := vgrand.RandomStr(5)
   268  	w, _ := walletWithKeys(t, 2)
   269  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   270  	if err != nil {
   271  		t.Fatalf(err.Error())
   272  	}
   273  
   274  	// setup
   275  	handler := newListKeysHandler(t)
   276  	// -- expected calls
   277  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   278  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   279  	handler.interactor.EXPECT().RequestPermissionsReview(ctx, traceID, uint8(1), hostname, w.Name(), map[string]string{
   280  		"public_keys": "read",
   281  	}).Times(1).Return(false, assert.AnError)
   282  	handler.interactor.EXPECT().NotifyError(ctx, traceID, api.InternalErrorType, fmt.Errorf("requesting the permissions review failed: %w", assert.AnError)).Times(1)
   283  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   284  
   285  	// when
   286  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   287  
   288  	// then
   289  	assertInternalError(t, errorDetails, api.ErrCouldNotListKeys)
   290  	assert.Empty(t, result)
   291  }
   292  
   293  func testListingKeysGettingInternalErrorDuringWalletUpdateDoesNotUpdatePermissions(t *testing.T) {
   294  	// given
   295  	ctx, traceID := clientContextForTest()
   296  	hostname := vgrand.RandomStr(5)
   297  	w, _ := walletWithKeys(t, 2)
   298  	connectedWallet, err := api.NewConnectedWallet(hostname, w)
   299  	if err != nil {
   300  		t.Fatalf(err.Error())
   301  	}
   302  
   303  	// setup
   304  	handler := newListKeysHandler(t)
   305  	// -- expected calls
   306  	handler.interactor.EXPECT().NotifyInteractionSessionBegan(ctx, traceID, api.PermissionRequestWorkflow, uint8(2)).Times(1).Return(nil)
   307  	handler.walletStore.EXPECT().GetWallet(ctx, w.Name()).Times(1).Return(w, nil)
   308  	handler.interactor.EXPECT().RequestPermissionsReview(ctx, traceID, uint8(1), hostname, w.Name(), map[string]string{
   309  		"public_keys": "read",
   310  	}).Times(1).Return(true, nil)
   311  	handler.walletStore.EXPECT().UpdateWallet(ctx, w).Times(1).Return(assert.AnError)
   312  	handler.interactor.EXPECT().NotifyError(ctx, traceID, api.InternalErrorType, fmt.Errorf("could not save the permissions update on the wallet: %w", assert.AnError)).Times(1)
   313  	handler.interactor.EXPECT().NotifyInteractionSessionEnded(ctx, traceID).Times(1)
   314  
   315  	// when
   316  	result, errorDetails := handler.handle(t, ctx, connectedWallet)
   317  
   318  	// then
   319  	assertInternalError(t, errorDetails, api.ErrCouldNotListKeys)
   320  	assert.Empty(t, result)
   321  }
   322  
   323  type listKeysHandler struct {
   324  	*api.ClientListKeys
   325  	ctrl        *gomock.Controller
   326  	walletStore *mocks.MockWalletStore
   327  	interactor  *mocks.MockInteractor
   328  }
   329  
   330  func (h *listKeysHandler) handle(t *testing.T, ctx context.Context, connectedWallet api.ConnectedWallet) (api.ClientListKeysResult, *jsonrpc.ErrorDetails) {
   331  	t.Helper()
   332  
   333  	rawResult, err := h.Handle(ctx, connectedWallet)
   334  	if rawResult != nil {
   335  		result, ok := rawResult.(api.ClientListKeysResult)
   336  		if !ok {
   337  			t.Fatal("ClientListKeys handler result is not a ClientListKeysResult")
   338  		}
   339  		return result, err
   340  	}
   341  	return api.ClientListKeysResult{}, err
   342  }
   343  
   344  func newListKeysHandler(t *testing.T) *listKeysHandler {
   345  	t.Helper()
   346  
   347  	ctrl := gomock.NewController(t)
   348  	walletStore := mocks.NewMockWalletStore(ctrl)
   349  	interactor := mocks.NewMockInteractor(ctrl)
   350  
   351  	return &listKeysHandler{
   352  		ClientListKeys: api.NewListKeys(walletStore, interactor),
   353  		ctrl:           ctrl,
   354  		walletStore:    walletStore,
   355  		interactor:     interactor,
   356  	}
   357  }