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 }