github.com/MetalBlockchain/metalgo@v1.11.9/api/keystore/service_test.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package keystore
     5  
     6  import (
     7  	"encoding/hex"
     8  	"math/rand"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/MetalBlockchain/metalgo/api"
    14  	"github.com/MetalBlockchain/metalgo/database/memdb"
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    17  	"github.com/MetalBlockchain/metalgo/utils/logging"
    18  	"github.com/MetalBlockchain/metalgo/utils/password"
    19  )
    20  
    21  // strongPassword defines a password used for the following tests that
    22  // scores high enough to pass the password strength scoring system
    23  var strongPassword = "N_+=_jJ;^(<;{4,:*m6CET}'&N;83FYK.wtNpwp-Jt" // #nosec G101
    24  
    25  func TestServiceListNoUsers(t *testing.T) {
    26  	require := require.New(t)
    27  
    28  	ks := New(logging.NoLog{}, memdb.New())
    29  	s := service{ks: ks.(*keystore)}
    30  
    31  	reply := ListUsersReply{}
    32  	require.NoError(s.ListUsers(nil, nil, &reply))
    33  	require.Empty(reply.Users)
    34  }
    35  
    36  func TestServiceCreateUser(t *testing.T) {
    37  	require := require.New(t)
    38  
    39  	ks := New(logging.NoLog{}, memdb.New())
    40  	s := service{ks: ks.(*keystore)}
    41  
    42  	{
    43  		require.NoError(s.CreateUser(nil, &api.UserPass{
    44  			Username: "bob",
    45  			Password: strongPassword,
    46  		}, &api.EmptyReply{}))
    47  	}
    48  
    49  	{
    50  		reply := ListUsersReply{}
    51  		require.NoError(s.ListUsers(nil, nil, &reply))
    52  		require.Len(reply.Users, 1)
    53  		require.Equal("bob", reply.Users[0])
    54  	}
    55  }
    56  
    57  // genStr returns a string of given length
    58  func genStr(n int) string {
    59  	b := make([]byte, n)
    60  	rand.Read(b) // #nosec G404
    61  	return hex.EncodeToString(b)[:n]
    62  }
    63  
    64  // TestServiceCreateUserArgsCheck generates excessively long usernames or
    65  // passwords to assure the sanity checks on string length are not exceeded
    66  func TestServiceCreateUserArgsCheck(t *testing.T) {
    67  	require := require.New(t)
    68  
    69  	ks := New(logging.NoLog{}, memdb.New())
    70  	s := service{ks: ks.(*keystore)}
    71  
    72  	{
    73  		reply := api.EmptyReply{}
    74  		err := s.CreateUser(nil, &api.UserPass{
    75  			Username: genStr(maxUserLen + 1),
    76  			Password: strongPassword,
    77  		}, &reply)
    78  		require.ErrorIs(err, errUserMaxLength)
    79  	}
    80  
    81  	{
    82  		reply := api.EmptyReply{}
    83  		err := s.CreateUser(nil, &api.UserPass{
    84  			Username: "shortuser",
    85  			Password: genStr(maxUserLen + 1),
    86  		}, &reply)
    87  		require.ErrorIs(err, password.ErrPassMaxLength)
    88  	}
    89  
    90  	{
    91  		reply := ListUsersReply{}
    92  		require.NoError(s.ListUsers(nil, nil, &reply))
    93  		require.Empty(reply.Users)
    94  	}
    95  }
    96  
    97  // TestServiceCreateUserWeakPassword tests creating a new user with a weak
    98  // password to ensure the password strength check is working
    99  func TestServiceCreateUserWeakPassword(t *testing.T) {
   100  	require := require.New(t)
   101  
   102  	ks := New(logging.NoLog{}, memdb.New())
   103  	s := service{ks: ks.(*keystore)}
   104  
   105  	{
   106  		reply := api.EmptyReply{}
   107  		err := s.CreateUser(nil, &api.UserPass{
   108  			Username: "bob",
   109  			Password: "weak",
   110  		}, &reply)
   111  		require.ErrorIs(err, password.ErrWeakPassword)
   112  	}
   113  }
   114  
   115  func TestServiceCreateDuplicate(t *testing.T) {
   116  	require := require.New(t)
   117  
   118  	ks := New(logging.NoLog{}, memdb.New())
   119  	s := service{ks: ks.(*keystore)}
   120  
   121  	{
   122  		require.NoError(s.CreateUser(nil, &api.UserPass{
   123  			Username: "bob",
   124  			Password: strongPassword,
   125  		}, &api.EmptyReply{}))
   126  	}
   127  
   128  	{
   129  		err := s.CreateUser(nil, &api.UserPass{
   130  			Username: "bob",
   131  			Password: strongPassword,
   132  		}, &api.EmptyReply{})
   133  		require.ErrorIs(err, errUserAlreadyExists)
   134  	}
   135  }
   136  
   137  func TestServiceCreateUserNoName(t *testing.T) {
   138  	require := require.New(t)
   139  
   140  	ks := New(logging.NoLog{}, memdb.New())
   141  	s := service{ks: ks.(*keystore)}
   142  
   143  	reply := api.EmptyReply{}
   144  	err := s.CreateUser(nil, &api.UserPass{
   145  		Password: strongPassword,
   146  	}, &reply)
   147  	require.ErrorIs(err, errEmptyUsername)
   148  }
   149  
   150  func TestServiceUseBlockchainDB(t *testing.T) {
   151  	require := require.New(t)
   152  
   153  	ks := New(logging.NoLog{}, memdb.New())
   154  	s := service{ks: ks.(*keystore)}
   155  
   156  	{
   157  		require.NoError(s.CreateUser(nil, &api.UserPass{
   158  			Username: "bob",
   159  			Password: strongPassword,
   160  		}, &api.EmptyReply{}))
   161  	}
   162  
   163  	{
   164  		db, err := ks.GetDatabase(ids.Empty, "bob", strongPassword)
   165  		require.NoError(err)
   166  		require.NoError(db.Put([]byte("hello"), []byte("world")))
   167  	}
   168  
   169  	{
   170  		db, err := ks.GetDatabase(ids.Empty, "bob", strongPassword)
   171  		require.NoError(err)
   172  		val, err := db.Get([]byte("hello"))
   173  		require.NoError(err)
   174  		require.Equal([]byte("world"), val)
   175  	}
   176  }
   177  
   178  func TestServiceExportImport(t *testing.T) {
   179  	require := require.New(t)
   180  
   181  	encodings := []formatting.Encoding{formatting.Hex}
   182  	for _, encoding := range encodings {
   183  		ks := New(logging.NoLog{}, memdb.New())
   184  		s := service{ks: ks.(*keystore)}
   185  
   186  		{
   187  			require.NoError(s.CreateUser(nil, &api.UserPass{
   188  				Username: "bob",
   189  				Password: strongPassword,
   190  			}, &api.EmptyReply{}))
   191  		}
   192  
   193  		{
   194  			db, err := ks.GetDatabase(ids.Empty, "bob", strongPassword)
   195  			require.NoError(err)
   196  			require.NoError(db.Put([]byte("hello"), []byte("world")))
   197  		}
   198  
   199  		exportArgs := ExportUserArgs{
   200  			UserPass: api.UserPass{
   201  				Username: "bob",
   202  				Password: strongPassword,
   203  			},
   204  			Encoding: encoding,
   205  		}
   206  		exportReply := ExportUserReply{}
   207  		require.NoError(s.ExportUser(nil, &exportArgs, &exportReply))
   208  
   209  		newKS := New(logging.NoLog{}, memdb.New())
   210  		newS := service{ks: newKS.(*keystore)}
   211  
   212  		{
   213  			err := newS.ImportUser(nil, &ImportUserArgs{
   214  				UserPass: api.UserPass{
   215  					Username: "bob",
   216  					Password: "",
   217  				},
   218  				User: exportReply.User,
   219  			}, &api.EmptyReply{})
   220  			require.ErrorIs(err, errIncorrectPassword)
   221  		}
   222  
   223  		{
   224  			err := newS.ImportUser(nil, &ImportUserArgs{
   225  				UserPass: api.UserPass{
   226  					Username: "",
   227  					Password: "strongPassword",
   228  				},
   229  				User: exportReply.User,
   230  			}, &api.EmptyReply{})
   231  			require.ErrorIs(err, errEmptyUsername)
   232  		}
   233  
   234  		{
   235  			require.NoError(newS.ImportUser(nil, &ImportUserArgs{
   236  				UserPass: api.UserPass{
   237  					Username: "bob",
   238  					Password: strongPassword,
   239  				},
   240  				User:     exportReply.User,
   241  				Encoding: encoding,
   242  			}, &api.EmptyReply{}))
   243  		}
   244  
   245  		{
   246  			db, err := newKS.GetDatabase(ids.Empty, "bob", strongPassword)
   247  			require.NoError(err)
   248  			val, err := db.Get([]byte("hello"))
   249  			require.NoError(err)
   250  			require.Equal([]byte("world"), val)
   251  		}
   252  	}
   253  }
   254  
   255  func TestServiceDeleteUser(t *testing.T) {
   256  	testUser := "testUser"
   257  	password := "passwTest@fake01ord"
   258  	tests := []struct {
   259  		desc        string
   260  		setup       func(ks *keystore) error
   261  		request     *api.UserPass
   262  		want        *api.EmptyReply
   263  		expectedErr error
   264  	}{
   265  		{
   266  			desc:        "empty user name case",
   267  			request:     &api.UserPass{},
   268  			expectedErr: errEmptyUsername,
   269  		},
   270  		{
   271  			desc:        "user not exists case",
   272  			request:     &api.UserPass{Username: "dummy"},
   273  			expectedErr: errNonexistentUser,
   274  		},
   275  		{
   276  			desc: "user exists and invalid password case",
   277  			setup: func(ks *keystore) error {
   278  				s := service{ks: ks}
   279  				return s.CreateUser(nil, &api.UserPass{Username: testUser, Password: password}, &api.EmptyReply{})
   280  			},
   281  			request:     &api.UserPass{Username: testUser, Password: "password"},
   282  			expectedErr: errIncorrectPassword,
   283  		},
   284  		{
   285  			desc: "user exists and valid password case",
   286  			setup: func(ks *keystore) error {
   287  				s := service{ks: ks}
   288  				return s.CreateUser(nil, &api.UserPass{Username: testUser, Password: password}, &api.EmptyReply{})
   289  			},
   290  			request: &api.UserPass{Username: testUser, Password: password},
   291  			want:    &api.EmptyReply{},
   292  		},
   293  		{
   294  			desc: "delete a user, imported from import api case",
   295  			setup: func(ks *keystore) error {
   296  				s := service{ks: ks}
   297  
   298  				reply := api.EmptyReply{}
   299  				if err := s.CreateUser(nil, &api.UserPass{Username: testUser, Password: password}, &reply); err != nil {
   300  					return err
   301  				}
   302  
   303  				// created data in bob db
   304  				db, err := ks.GetDatabase(ids.Empty, testUser, password)
   305  				if err != nil {
   306  					return err
   307  				}
   308  
   309  				return db.Put([]byte("hello"), []byte("world"))
   310  			},
   311  			request: &api.UserPass{Username: testUser, Password: password},
   312  			want:    &api.EmptyReply{},
   313  		},
   314  	}
   315  
   316  	for _, tt := range tests {
   317  		t.Run(tt.desc, func(t *testing.T) {
   318  			require := require.New(t)
   319  
   320  			ksIntf := New(logging.NoLog{}, memdb.New())
   321  			ks := ksIntf.(*keystore)
   322  			s := service{ks: ks}
   323  
   324  			if tt.setup != nil {
   325  				require.NoError(tt.setup(ks))
   326  			}
   327  			got := &api.EmptyReply{}
   328  			err := s.DeleteUser(nil, tt.request, got)
   329  			require.ErrorIs(err, tt.expectedErr)
   330  			if tt.expectedErr != nil {
   331  				return
   332  			}
   333  			require.Equal(tt.want, got)
   334  			require.NotContains(ks.usernameToPassword, testUser) // delete is successful
   335  
   336  			// deleted user details should be available to create user again.
   337  			require.NoError(s.CreateUser(nil, &api.UserPass{Username: testUser, Password: password}, &api.EmptyReply{}))
   338  		})
   339  	}
   340  }