github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libkbfs/keybase_daemon_rpc_test.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package libkbfs
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/keybase/client/go/kbfs/idutil"
    15  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    16  	"github.com/keybase/client/go/kbfs/test/clocktest"
    17  	"github.com/keybase/client/go/kbfs/tlf"
    18  	kbname "github.com/keybase/client/go/kbun"
    19  	"github.com/keybase/client/go/logger"
    20  	"github.com/keybase/client/go/protocol/keybase1"
    21  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  	"golang.org/x/net/context"
    25  )
    26  
    27  // If we cancel the RPC before the RPC returns, the call should error quickly.
    28  func TestKeybaseDaemonRPCIdentifyCanceled(t *testing.T) {
    29  	serverConn, conn := rpc.MakeConnectionForTest(t)
    30  	daemon := newKeybaseDaemonRPCWithClient(
    31  		nil,
    32  		conn.GetClient(),
    33  		logger.NewTestLogger(t))
    34  
    35  	f := func(ctx context.Context) error {
    36  		_, _, err := daemon.Identify(
    37  			ctx, "", "", keybase1.OfflineAvailability_NONE)
    38  		return err
    39  	}
    40  	testRPCWithCanceledContext(t, serverConn, f)
    41  }
    42  
    43  // If we cancel the RPC before the RPC returns, the call should error quickly.
    44  func TestKeybaseDaemonRPCGetCurrentSessionCanceled(t *testing.T) {
    45  	serverConn, conn := rpc.MakeConnectionForTest(t)
    46  	daemon := newKeybaseDaemonRPCWithClient(
    47  		nil,
    48  		conn.GetClient(),
    49  		logger.NewTestLogger(t))
    50  
    51  	f := func(ctx context.Context) error {
    52  		_, err := daemon.CurrentSession(ctx, 0)
    53  		return err
    54  	}
    55  	testRPCWithCanceledContext(t, serverConn, f)
    56  }
    57  
    58  // TODO: Add tests for Favorite* methods, too.
    59  
    60  type fakeKeybaseClient struct {
    61  	session                     idutil.SessionInfo
    62  	users                       map[keybase1.UID]idutil.UserInfo
    63  	currentSessionCalled        bool
    64  	identifyCalled              bool
    65  	loadUserPlusKeysCalled      bool
    66  	loadAllPublicKeysUnverified bool
    67  	editResponse                keybase1.FSEditListArg
    68  }
    69  
    70  var _ rpc.GenericClient = (*fakeKeybaseClient)(nil)
    71  
    72  func (c *fakeKeybaseClient) Call(ctx context.Context, s string, args interface{},
    73  	res interface{}, _ time.Duration) error {
    74  	return c.call(ctx, s, args, res)
    75  }
    76  
    77  func (c *fakeKeybaseClient) CallCompressed(ctx context.Context, s string, args interface{},
    78  	res interface{}, _ rpc.CompressionType, _ time.Duration) error {
    79  	return c.call(ctx, s, args, res)
    80  }
    81  
    82  func (c *fakeKeybaseClient) call(ctx context.Context, s string, args interface{}, res interface{}) error {
    83  	switch s {
    84  	case "keybase.1.session.currentSession":
    85  		*res.(*keybase1.Session) = keybase1.Session{
    86  			Uid:             c.session.UID,
    87  			Username:        "fake username",
    88  			DeviceSubkeyKid: c.session.CryptPublicKey.KID(),
    89  			DeviceSibkeyKid: c.session.VerifyingKey.KID(),
    90  		}
    91  
    92  		c.currentSessionCalled = true
    93  		return nil
    94  
    95  	case "keybase.1.identify.identifyLite":
    96  		arg := args.([]interface{})[0].(keybase1.IdentifyLiteArg)
    97  		uidStr := strings.TrimPrefix(arg.Assertion, "uid:")
    98  		if len(uidStr) == len(arg.Assertion) {
    99  			return fmt.Errorf("Non-uid assertion %s", arg.Assertion)
   100  		}
   101  
   102  		uid := keybase1.UID(uidStr)
   103  		userInfo, ok := c.users[uid]
   104  		if !ok {
   105  			return fmt.Errorf("Could not find user info for UID %s", uid)
   106  		}
   107  
   108  		*res.(*keybase1.IdentifyLiteRes) = keybase1.IdentifyLiteRes{
   109  			Ul: keybase1.UserOrTeamLite{
   110  				Id:   uid.AsUserOrTeam(),
   111  				Name: string(userInfo.Name),
   112  			},
   113  		}
   114  
   115  		c.identifyCalled = true
   116  		return nil
   117  
   118  	case "keybase.1.user.loadUserPlusKeysV2":
   119  		arg := args.([]interface{})[0].(keybase1.LoadUserPlusKeysV2Arg)
   120  
   121  		userInfo, ok := c.users[arg.Uid]
   122  		if !ok {
   123  			return fmt.Errorf("Could not find user info for UID %s", arg.Uid)
   124  		}
   125  
   126  		*res.(*keybase1.UserPlusKeysV2AllIncarnations) =
   127  			keybase1.UserPlusKeysV2AllIncarnations{
   128  				Current: keybase1.UserPlusKeysV2{
   129  					Uid:      arg.Uid,
   130  					Username: string(userInfo.Name),
   131  				},
   132  			}
   133  
   134  		c.loadUserPlusKeysCalled = true
   135  		return nil
   136  
   137  	case "keybase.1.user.loadAllPublicKeysUnverified":
   138  		pk := keybase1.PublicKey{
   139  			KID: kbfscrypto.MakeFakeVerifyingKeyOrBust("foo").KID(),
   140  		}
   141  		*res.(*[]keybase1.PublicKey) = []keybase1.PublicKey{pk}
   142  
   143  		c.loadAllPublicKeysUnverified = true
   144  		return nil
   145  
   146  	case "keybase.1.kbfs.FSEditList":
   147  		c.editResponse = args.([]interface{})[0].(keybase1.FSEditListArg)
   148  		return nil
   149  
   150  	default:
   151  		return fmt.Errorf("Unknown call: %s %v %v", s, args, res)
   152  	}
   153  }
   154  
   155  func (c *fakeKeybaseClient) Notify(_ context.Context, s string, args interface{}, timeout time.Duration) error {
   156  	return fmt.Errorf("Unknown notify: %s %v", s, args)
   157  }
   158  
   159  const expectCall = true
   160  const expectCached = false
   161  
   162  func testCurrentSession(
   163  	t *testing.T, client *fakeKeybaseClient, c *KeybaseDaemonRPC,
   164  	expectedSession idutil.SessionInfo, expectedCalled bool) {
   165  	client.currentSessionCalled = false
   166  
   167  	ctx := context.Background()
   168  	sessionID := 0
   169  	session, err := c.CurrentSession(ctx, sessionID)
   170  	require.NoError(t, err)
   171  
   172  	assert.Equal(t, expectedSession, session)
   173  	assert.Equal(t, expectedCalled, client.currentSessionCalled)
   174  }
   175  
   176  // Test that the session cache works and is invalidated as expected.
   177  func TestKeybaseDaemonSessionCache(t *testing.T) {
   178  	name := kbname.NormalizedUsername("fake username")
   179  	k := idutil.MakeLocalUserCryptPublicKeyOrBust(name)
   180  	v := idutil.MakeLocalUserVerifyingKeyOrBust(name)
   181  	session := idutil.SessionInfo{
   182  		Name:           name,
   183  		UID:            keybase1.MakeTestUID(1),
   184  		CryptPublicKey: k,
   185  		VerifyingKey:   v,
   186  	}
   187  
   188  	client := &fakeKeybaseClient{session: session}
   189  	c := newKeybaseDaemonRPCWithClient(
   190  		nil, client, logger.NewTestLogger(t))
   191  
   192  	// Should fill cache.
   193  	testCurrentSession(t, client, c, session, expectCall)
   194  
   195  	// Should be cached.
   196  	testCurrentSession(t, client, c, session, expectCached)
   197  
   198  	// Should invalidate cache.
   199  	err := c.LoggedOut(context.Background())
   200  	require.NoError(t, err)
   201  
   202  	// Should fill cache again.
   203  	testCurrentSession(t, client, c, session, expectCall)
   204  
   205  	// Should be cached again.
   206  	testCurrentSession(t, client, c, session, expectCached)
   207  
   208  	// Should invalidate cache.
   209  	c.OnDisconnected(context.Background(), rpc.UsingExistingConnection)
   210  
   211  	// Should fill cache again.
   212  	testCurrentSession(t, client, c, session, expectCall)
   213  }
   214  
   215  func testLoadUserPlusKeys(
   216  	t *testing.T, client *fakeKeybaseClient, c *KeybaseDaemonRPC,
   217  	uid keybase1.UID, expectedName kbname.NormalizedUsername,
   218  	expectedCalled bool) {
   219  	client.loadUserPlusKeysCalled = false
   220  
   221  	ctx := context.Background()
   222  	info, err := c.LoadUserPlusKeys(
   223  		ctx, uid, "", keybase1.OfflineAvailability_NONE)
   224  	require.NoError(t, err)
   225  
   226  	assert.Equal(t, expectedName, info.Name)
   227  	assert.Equal(t, expectedCalled, client.loadUserPlusKeysCalled)
   228  }
   229  
   230  func testIdentify(
   231  	t *testing.T, client *fakeKeybaseClient, c *KeybaseDaemonRPC,
   232  	uid keybase1.UID, expectedName kbname.NormalizedUsername,
   233  	expectedCalled bool) {
   234  	client.identifyCalled = false
   235  
   236  	ctx := context.Background()
   237  	name, _, err := c.Identify(
   238  		ctx, "uid:"+string(uid), "", keybase1.OfflineAvailability_NONE)
   239  	require.NoError(t, err)
   240  
   241  	assert.Equal(t, expectedName, name)
   242  	assert.Equal(t, expectedCalled, client.identifyCalled)
   243  }
   244  
   245  // Test that the user cache works and is invalidated as expected.
   246  func TestKeybaseDaemonUserCache(t *testing.T) {
   247  	uid1 := keybase1.MakeTestUID(1)
   248  	uid2 := keybase1.MakeTestUID(2)
   249  	name1 := kbname.NewNormalizedUsername("name1")
   250  	name2 := kbname.NewNormalizedUsername("name2")
   251  	users := map[keybase1.UID]idutil.UserInfo{
   252  		uid1: {Name: name1},
   253  		uid2: {Name: name2},
   254  	}
   255  	client := &fakeKeybaseClient{users: users}
   256  	c := newKeybaseDaemonRPCWithClient(
   257  		nil, client, logger.NewTestLogger(t))
   258  
   259  	// Should fill cache.
   260  	testLoadUserPlusKeys(t, client, c, uid1, name1, expectCall)
   261  
   262  	// Should be cached.
   263  	testLoadUserPlusKeys(t, client, c, uid1, name1, expectCached)
   264  
   265  	// IdentifyLite doesn't fill the cache.
   266  	testIdentify(t, client, c, uid2, name2, expectCall)
   267  
   268  	// Shouldn't be cached yet after just an identify.
   269  	testLoadUserPlusKeys(t, client, c, uid2, name2, expectCall)
   270  
   271  	// Should be cached.
   272  	testLoadUserPlusKeys(t, client, c, uid2, name2, expectCached)
   273  
   274  	// Should not be cached.
   275  	testIdentify(t, client, c, uid2, name2, expectCall)
   276  
   277  	// Should invalidate cache for uid1.
   278  	err := c.KeyfamilyChanged(context.Background(), uid1)
   279  	require.NoError(t, err)
   280  
   281  	// Should fill cache again.
   282  	testLoadUserPlusKeys(t, client, c, uid1, name1, expectCall)
   283  
   284  	// Should be cached again.
   285  	testLoadUserPlusKeys(t, client, c, uid1, name1, expectCached)
   286  
   287  	// Should invalidate cache for uid2.
   288  	err = c.KeyfamilyChanged(context.Background(), uid2)
   289  	require.NoError(t, err)
   290  
   291  	// Should fill cache again.
   292  	testLoadUserPlusKeys(t, client, c, uid2, name2, expectCall)
   293  
   294  	// Should be cached again.
   295  	testLoadUserPlusKeys(t, client, c, uid2, name2, expectCached)
   296  
   297  	// Should invalidate cache for all users.
   298  	c.OnDisconnected(context.Background(), rpc.UsingExistingConnection)
   299  
   300  	// Should fill cache again.
   301  	testLoadUserPlusKeys(t, client, c, uid1, name1, expectCall)
   302  	testLoadUserPlusKeys(t, client, c, uid2, name2, expectCall)
   303  
   304  	// Test that CheckForRekey gets called only if the logged-in user
   305  	// changes.
   306  	session := idutil.SessionInfo{
   307  		UID: uid1,
   308  	}
   309  	c.setCachedCurrentSession(session)
   310  	ctr := NewSafeTestReporter(t)
   311  	mockCtrl := gomock.NewController(ctr)
   312  	config := NewConfigMock(mockCtrl, ctr)
   313  	c.config = config
   314  	defer func() {
   315  		config.ctr.CheckForFailures()
   316  		mockCtrl.Finish()
   317  	}()
   318  	errChan := make(chan error, 1)
   319  	config.mockMdserv.EXPECT().CheckForRekeys(gomock.Any()).Do(
   320  		func(ctx context.Context) {
   321  			errChan <- nil
   322  		}).Return(errChan)
   323  	err = c.KeyfamilyChanged(context.Background(), uid1)
   324  	require.NoError(t, err)
   325  	<-errChan
   326  	// This one shouldn't trigger CheckForRekeys; if it does, the mock
   327  	// controller will catch it during Finish.
   328  	err = c.KeyfamilyChanged(context.Background(), uid2)
   329  	require.NoError(t, err)
   330  }
   331  
   332  func TestKeybaseDaemonRPCEditList(t *testing.T) {
   333  	var userName1, userName2 kbname.NormalizedUsername = "u1", "u2"
   334  	config1, _, ctx, cancel := kbfsOpsConcurInit(t, userName1, userName2)
   335  	defer kbfsConcurTestShutdown(ctx, t, config1, cancel)
   336  	// kbfsOpsConcurInit turns off notifications, so turn them back on.
   337  	config1.SetMode(modeTest{NewInitModeFromType(InitDefault)})
   338  
   339  	clock, first := clocktest.NewTestClockAndTimeNow()
   340  	config1.SetClock(clock)
   341  
   342  	config2 := ConfigAsUser(config1, userName2)
   343  	defer CheckConfigAndShutdown(ctx, t, config2)
   344  
   345  	name := userName1.String() + "," + userName2.String()
   346  
   347  	rootNode1 := GetRootNodeOrBust(ctx, t, config1, name, tlf.Private)
   348  	rootNode2 := GetRootNodeOrBust(ctx, t, config2, name, tlf.Private)
   349  
   350  	// user 1 creates a file
   351  	kbfsOps1 := config1.KBFSOps()
   352  	_, _, err := kbfsOps1.CreateFile(
   353  		ctx, rootNode1, testPPS("a"), false, NoExcl)
   354  	require.NoError(t, err)
   355  	err = kbfsOps1.SyncAll(ctx, rootNode1.GetFolderBranch())
   356  	require.NoError(t, err)
   357  
   358  	kbfsOps2 := config2.KBFSOps()
   359  	err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch(), nil)
   360  	require.NoError(t, err)
   361  
   362  	clock.Add(1 * time.Minute)
   363  	second := clock.Now()
   364  
   365  	_, _, err = kbfsOps2.CreateFile(ctx, rootNode2, testPPS("b"), false, NoExcl)
   366  	require.NoError(t, err)
   367  	err = kbfsOps2.SyncAll(ctx, rootNode2.GetFolderBranch())
   368  	require.NoError(t, err)
   369  
   370  	err = kbfsOps2.SyncFromServer(ctx, rootNode2.GetFolderBranch(), nil)
   371  	require.NoError(t, err)
   372  	err = kbfsOps1.SyncFromServer(ctx, rootNode1.GetFolderBranch(), nil)
   373  	require.NoError(t, err)
   374  
   375  	session1, err := config1.KBPKI().GetCurrentSession(context.Background())
   376  	require.NoError(t, err)
   377  	uid1 := session1.UID
   378  	session2, err := config2.KBPKI().GetCurrentSession(context.Background())
   379  	require.NoError(t, err)
   380  	uid2 := session2.UID
   381  
   382  	// We should see 1 create edit for each user.
   383  	expectedHistory := keybase1.FSFolderEditHistory{
   384  		Folder: keybase1.Folder{
   385  			Name:       name,
   386  			FolderType: keybase1.FolderType_PRIVATE,
   387  			Private:    true,
   388  		},
   389  		ServerTime: keybase1.ToTime(second),
   390  		History: []keybase1.FSFolderWriterEditHistory{
   391  			{
   392  				WriterName: "u2",
   393  				Edits: []keybase1.FSFolderWriterEdit{{
   394  					Filename:         "/keybase/private/u1,u2/b",
   395  					NotificationType: keybase1.FSNotificationType_FILE_MODIFIED,
   396  					ServerTime:       keybase1.ToTime(second),
   397  				}},
   398  				Deletes: []keybase1.FSFolderWriterEdit{},
   399  			},
   400  			{
   401  				WriterName: "u1",
   402  				Edits: []keybase1.FSFolderWriterEdit{{
   403  					Filename:         "/keybase/private/u1,u2/a",
   404  					NotificationType: keybase1.FSNotificationType_FILE_MODIFIED,
   405  					ServerTime:       keybase1.ToTime(first),
   406  				}},
   407  				Deletes: []keybase1.FSFolderWriterEdit{},
   408  			},
   409  		},
   410  	}
   411  
   412  	users := map[keybase1.UID]idutil.UserInfo{
   413  		uid1: {Name: userName1},
   414  		uid2: {Name: userName2},
   415  	}
   416  	client1 := &fakeKeybaseClient{users: users}
   417  	c1 := newKeybaseDaemonRPCWithClient(
   418  		nil, client1, logger.NewTestLogger(t))
   419  	c1.config = config1
   420  
   421  	reqID := 10
   422  	err = c1.FSEditListRequest(ctx, keybase1.FSEditListRequest{
   423  		Folder:    keybase1.Folder{Name: name, Private: true},
   424  		RequestID: reqID,
   425  	})
   426  	require.NoError(t, err)
   427  	history := client1.editResponse.Edits
   428  	require.Equal(t, expectedHistory, history)
   429  }