github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/keybase_daemon_local.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  	"sync"
    10  
    11  	"golang.org/x/net/context"
    12  
    13  	"github.com/keybase/client/go/kbfs/idutil"
    14  	"github.com/keybase/client/go/kbfs/kbfscodec"
    15  	"github.com/keybase/client/go/kbfs/kbfscrypto"
    16  	"github.com/keybase/client/go/kbfs/ldbutils"
    17  	kbname "github.com/keybase/client/go/kbun"
    18  	"github.com/keybase/client/go/protocol/keybase1"
    19  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    20  	"github.com/syndtr/goleveldb/leveldb"
    21  	"github.com/syndtr/goleveldb/leveldb/util"
    22  )
    23  
    24  type favoriteStore interface {
    25  	FavoriteAdd(uid keybase1.UID, folder keybase1.FolderHandle) error
    26  	FavoriteDelete(uid keybase1.UID, folder keybase1.FolderHandle) error
    27  	FavoriteList(uid keybase1.UID) ([]keybase1.Folder, error)
    28  
    29  	Shutdown()
    30  }
    31  
    32  type diskFavoriteClient struct {
    33  	favoriteDb *leveldb.DB
    34  	codec      kbfscodec.Codec
    35  }
    36  
    37  var _ favoriteStore = diskFavoriteClient{}
    38  
    39  func (c diskFavoriteClient) favkey(
    40  	uid keybase1.UID, folder keybase1.FolderHandle) []byte {
    41  	return []byte(fmt.Sprintf("%s:%s", uid, folder.ToString()))
    42  }
    43  
    44  func (c diskFavoriteClient) FavoriteAdd(
    45  	uid keybase1.UID, folder keybase1.FolderHandle) error {
    46  	enc, err := c.codec.Encode(folder)
    47  	if err != nil {
    48  		return err
    49  	}
    50  
    51  	return c.favoriteDb.Put(c.favkey(uid, folder), enc, nil)
    52  }
    53  
    54  func (c diskFavoriteClient) FavoriteDelete(
    55  	uid keybase1.UID, folder keybase1.FolderHandle) error {
    56  	return c.favoriteDb.Delete(c.favkey(uid, folder), nil)
    57  }
    58  
    59  func (c diskFavoriteClient) FavoriteList(uid keybase1.UID) (
    60  	[]keybase1.Folder, error) {
    61  	iter := c.favoriteDb.NewIterator(util.BytesPrefix([]byte(uid+":")), nil)
    62  	defer iter.Release()
    63  	var folders []keybase1.Folder
    64  	for iter.Next() {
    65  		var folder keybase1.Folder
    66  		if err := c.codec.Decode(iter.Value(), &folder); err != nil {
    67  			return nil, err
    68  		}
    69  		folders = append(folders, folder)
    70  	}
    71  	if err := iter.Error(); err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	return folders, nil
    76  }
    77  
    78  func (c diskFavoriteClient) Shutdown() {
    79  	c.favoriteDb.Close()
    80  }
    81  
    82  type memoryFavoriteClient struct {
    83  	favorites map[keybase1.UID]map[string]keybase1.FolderHandle
    84  }
    85  
    86  var _ favoriteStore = memoryFavoriteClient{}
    87  
    88  func (c memoryFavoriteClient) FavoriteAdd(
    89  	uid keybase1.UID, folder keybase1.FolderHandle) error {
    90  	if c.favorites[uid] == nil {
    91  		c.favorites[uid] = make(map[string]keybase1.FolderHandle)
    92  	}
    93  	c.favorites[uid][folder.ToString()] = folder
    94  	return nil
    95  }
    96  
    97  func (c memoryFavoriteClient) FavoriteDelete(
    98  	uid keybase1.UID, folder keybase1.FolderHandle) error {
    99  	if c.favorites[uid] != nil {
   100  		delete(c.favorites[uid], folder.ToString())
   101  	}
   102  	return nil
   103  }
   104  
   105  func (c memoryFavoriteClient) FavoriteList(
   106  	uid keybase1.UID) ([]keybase1.Folder, error) {
   107  	folders := make([]keybase1.Folder, len(c.favorites[uid]))
   108  	i := 0
   109  	for _, v := range c.favorites[uid] {
   110  		folders[i] = keybase1.Folder{
   111  			Name:       v.Name,
   112  			FolderType: v.FolderType,
   113  		}
   114  		i++
   115  	}
   116  	return folders, nil
   117  }
   118  
   119  func (c memoryFavoriteClient) Shutdown() {}
   120  
   121  // KeybaseDaemonLocal implements KeybaseDaemon using an in-memory user
   122  // and session store, and a given favorite store.
   123  type KeybaseDaemonLocal struct {
   124  	*idutil.DaemonLocal
   125  
   126  	// lock protects everything below.
   127  	lock          sync.Mutex
   128  	favoriteStore favoriteStore
   129  }
   130  
   131  var _ KeybaseService = &KeybaseDaemonLocal{}
   132  
   133  type makeKeysFunc func(kbname.NormalizedUsername, int) (
   134  	kbfscrypto.CryptPublicKey, kbfscrypto.VerifyingKey)
   135  
   136  func (k *KeybaseDaemonLocal) addDeviceForTesting(uid keybase1.UID,
   137  	makeKeys makeKeysFunc) (int, error) {
   138  	k.lock.Lock()
   139  	defer k.lock.Unlock()
   140  
   141  	user, err := k.GetLocalUser(uid)
   142  	if err != nil {
   143  		return 0, fmt.Errorf("No such user %s: %v", uid, err)
   144  	}
   145  	user = user.DeepCopy()
   146  
   147  	index := len(user.VerifyingKeys)
   148  	newCryptPublicKey, newVerifyingKey := makeKeys(user.Name, index)
   149  	user.VerifyingKeys = append(user.VerifyingKeys, newVerifyingKey)
   150  	user.CryptPublicKeys = append(user.CryptPublicKeys, newCryptPublicKey)
   151  
   152  	k.SetLocalUser(uid, user)
   153  	return index, nil
   154  }
   155  
   156  func (k *KeybaseDaemonLocal) revokeDeviceForTesting(clock Clock,
   157  	uid keybase1.UID, index int) error {
   158  	k.lock.Lock()
   159  	defer k.lock.Unlock()
   160  
   161  	user, err := k.GetLocalUser(uid)
   162  	if err != nil {
   163  		return fmt.Errorf("No such user %s: %v", uid, err)
   164  	}
   165  	user = user.DeepCopy()
   166  
   167  	session, err := k.CurrentSession(context.TODO(), 0)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	if index >= len(user.VerifyingKeys) ||
   173  		(session.UID == uid && index == user.CurrentCryptPublicKeyIndex) {
   174  		return fmt.Errorf("Can't revoke index %d", index)
   175  	}
   176  
   177  	if user.RevokedVerifyingKeys == nil {
   178  		user.RevokedVerifyingKeys =
   179  			make(map[kbfscrypto.VerifyingKey]idutil.RevokedKeyInfo)
   180  	}
   181  	if user.RevokedCryptPublicKeys == nil {
   182  		user.RevokedCryptPublicKeys =
   183  			make(map[kbfscrypto.CryptPublicKey]idutil.RevokedKeyInfo)
   184  	}
   185  
   186  	kbtime := keybase1.ToTime(clock.Now())
   187  	info := idutil.RevokedKeyInfo{
   188  		Time: kbtime,
   189  		MerkleRoot: keybase1.MerkleRootV2{
   190  			Seqno: 1,
   191  		},
   192  	}
   193  	user.RevokedVerifyingKeys[user.VerifyingKeys[index]] = info
   194  	user.RevokedCryptPublicKeys[user.CryptPublicKeys[index]] = info
   195  
   196  	user.VerifyingKeys = append(user.VerifyingKeys[:index],
   197  		user.VerifyingKeys[index+1:]...)
   198  	user.CryptPublicKeys = append(user.CryptPublicKeys[:index],
   199  		user.CryptPublicKeys[index+1:]...)
   200  
   201  	if session.UID == uid && index < user.CurrentCryptPublicKeyIndex {
   202  		user.CurrentCryptPublicKeyIndex--
   203  	}
   204  	if session.UID == uid && index < user.CurrentVerifyingKeyIndex {
   205  		user.CurrentVerifyingKeyIndex--
   206  	}
   207  
   208  	k.SetLocalUser(uid, user)
   209  	return nil
   210  }
   211  
   212  func (k *KeybaseDaemonLocal) switchDeviceForTesting(uid keybase1.UID,
   213  	index int) error {
   214  	k.lock.Lock()
   215  	defer k.lock.Unlock()
   216  
   217  	user, err := k.GetLocalUser(uid)
   218  	if err != nil {
   219  		return fmt.Errorf("No such user %s: %v", uid, err)
   220  	}
   221  
   222  	if index >= len(user.CryptPublicKeys) {
   223  		return fmt.Errorf("Wrong crypt public key index: %d", index)
   224  	}
   225  	user.CurrentCryptPublicKeyIndex = index
   226  
   227  	if index >= len(user.VerifyingKeys) {
   228  		return fmt.Errorf("Wrong verifying key index: %d", index)
   229  	}
   230  	user.CurrentVerifyingKeyIndex = index
   231  
   232  	k.SetLocalUser(uid, user)
   233  	return nil
   234  }
   235  
   236  func (k *KeybaseDaemonLocal) addTeamWriterForTest(
   237  	tid keybase1.TeamID, uid keybase1.UID) error {
   238  	k.lock.Lock()
   239  	defer k.lock.Unlock()
   240  	teamName, isImplicit, err := k.DaemonLocal.AddTeamWriterForTest(tid, uid)
   241  	if err != nil {
   242  		return err
   243  	}
   244  	if !isImplicit {
   245  		f := keybase1.FolderHandle{
   246  			Name:       string(teamName),
   247  			FolderType: keybase1.FolderType_TEAM,
   248  		}
   249  		err := k.favoriteStore.FavoriteAdd(uid, f)
   250  		if err != nil {
   251  			return err
   252  		}
   253  	}
   254  	return nil
   255  }
   256  
   257  func (k *KeybaseDaemonLocal) removeTeamWriterForTest(
   258  	tid keybase1.TeamID, uid keybase1.UID) error {
   259  	k.lock.Lock()
   260  	defer k.lock.Unlock()
   261  	teamName, err := k.DaemonLocal.RemoveTeamWriterForTest(tid, uid)
   262  	if err != nil {
   263  		return err
   264  	}
   265  	f := keybase1.FolderHandle{
   266  		Name:       string(teamName),
   267  		FolderType: keybase1.FolderType_TEAM,
   268  	}
   269  	return k.favoriteStore.FavoriteDelete(uid, f)
   270  }
   271  
   272  func (k *KeybaseDaemonLocal) addTeamReaderForTest(
   273  	tid keybase1.TeamID, uid keybase1.UID) error {
   274  	k.lock.Lock()
   275  	defer k.lock.Unlock()
   276  	teamName, err := k.DaemonLocal.AddTeamReaderForTest(tid, uid)
   277  	if err != nil {
   278  		return err
   279  	}
   280  	f := keybase1.FolderHandle{
   281  		Name:       string(teamName),
   282  		FolderType: keybase1.FolderType_TEAM,
   283  	}
   284  	return k.favoriteStore.FavoriteAdd(uid, f)
   285  }
   286  
   287  func (k *KeybaseDaemonLocal) addTeamsForTestLocked(teams []idutil.TeamInfo) {
   288  	k.DaemonLocal.AddTeamsForTest(teams)
   289  	for _, t := range teams {
   290  		f := keybase1.FolderHandle{
   291  			Name:       string(t.Name),
   292  			FolderType: keybase1.FolderType_TEAM,
   293  		}
   294  		for u := range t.Writers {
   295  			_ = k.favoriteStore.FavoriteAdd(u, f)
   296  		}
   297  		for u := range t.Readers {
   298  			_ = k.favoriteStore.FavoriteAdd(u, f)
   299  		}
   300  	}
   301  }
   302  
   303  func (k *KeybaseDaemonLocal) addTeamsForTest(teams []idutil.TeamInfo) {
   304  	k.lock.Lock()
   305  	defer k.lock.Unlock()
   306  	k.addTeamsForTestLocked(teams)
   307  }
   308  
   309  // FavoriteAdd implements KeybaseDaemon for KeybaseDaemonLocal.
   310  func (k *KeybaseDaemonLocal) FavoriteAdd(
   311  	ctx context.Context, folder keybase1.FolderHandle) error {
   312  	if err := checkContext(ctx); err != nil {
   313  		return err
   314  	}
   315  
   316  	k.lock.Lock()
   317  	defer k.lock.Unlock()
   318  	session, err := k.CurrentSession(ctx, 0)
   319  	if err != nil {
   320  		return err
   321  	}
   322  	return k.favoriteStore.FavoriteAdd(session.UID, folder)
   323  }
   324  
   325  // FavoriteDelete implements KeybaseDaemon for KeybaseDaemonLocal.
   326  func (k *KeybaseDaemonLocal) FavoriteDelete(
   327  	ctx context.Context, folder keybase1.FolderHandle) error {
   328  	if err := checkContext(ctx); err != nil {
   329  		return err
   330  	}
   331  
   332  	k.lock.Lock()
   333  	defer k.lock.Unlock()
   334  	session, err := k.CurrentSession(ctx, 0)
   335  	if err != nil {
   336  		return err
   337  	}
   338  	return k.favoriteStore.FavoriteDelete(session.UID, folder)
   339  }
   340  
   341  // FavoriteList implements KeybaseDaemon for KeybaseDaemonLocal.
   342  func (k *KeybaseDaemonLocal) FavoriteList(
   343  	ctx context.Context, sessionID int) (keybase1.FavoritesResult, error) {
   344  	if err := checkContext(ctx); err != nil {
   345  		return keybase1.FavoritesResult{}, err
   346  	}
   347  
   348  	k.lock.Lock()
   349  	defer k.lock.Unlock()
   350  
   351  	session, err := k.CurrentSession(ctx, 0)
   352  	if err != nil {
   353  		return keybase1.FavoritesResult{}, err
   354  	}
   355  
   356  	// This is only used for testing, so it's okay to only have favorites here.
   357  	favs, err := k.favoriteStore.FavoriteList(session.UID)
   358  	if err != nil {
   359  		return keybase1.FavoritesResult{}, err
   360  	}
   361  	return keybase1.FavoritesResult{
   362  		FavoriteFolders: favs,
   363  		IgnoredFolders:  []keybase1.Folder{},
   364  		NewFolders:      []keybase1.Folder{},
   365  	}, nil
   366  }
   367  
   368  // EncryptFavorites implements KeybaseService for KeybaseDaemonLocal
   369  func (k *KeybaseDaemonLocal) EncryptFavorites(ctx context.Context,
   370  	dataToEncrypt []byte) ([]byte, error) {
   371  	return nil, checkContext(ctx)
   372  }
   373  
   374  // DecryptFavorites implements KeybaseService for KeybaseDaemonLocal
   375  func (k *KeybaseDaemonLocal) DecryptFavorites(ctx context.Context,
   376  	dataToDecrypt []byte) ([]byte, error) {
   377  	return nil, checkContext(ctx)
   378  }
   379  
   380  // NotifyOnlineStatusChanged implements KeybaseDaemon for KeybaseDeamonLocal.
   381  func (k *KeybaseDaemonLocal) NotifyOnlineStatusChanged(ctx context.Context, online bool) error {
   382  	return checkContext(ctx)
   383  }
   384  
   385  // NotifyFavoritesChanged implements KeybaseDaemon for KeybaseDeamonLocal.
   386  func (k *KeybaseDaemonLocal) NotifyFavoritesChanged(ctx context.Context) error {
   387  	return checkContext(ctx)
   388  }
   389  
   390  // Notify implements KeybaseDaemon for KeybaseDeamonLocal.
   391  func (k *KeybaseDaemonLocal) Notify(ctx context.Context, notification *keybase1.FSNotification) error {
   392  	return checkContext(ctx)
   393  }
   394  
   395  // NotifyPathUpdated implements KeybaseDaemon for KeybaseDeamonLocal.
   396  func (k *KeybaseDaemonLocal) NotifyPathUpdated(
   397  	ctx context.Context, _ string) error {
   398  	return checkContext(ctx)
   399  }
   400  
   401  // NotifySyncStatus implements KeybaseDaemon for KeybaseDeamonLocal.
   402  func (k *KeybaseDaemonLocal) NotifySyncStatus(ctx context.Context,
   403  	_ *keybase1.FSPathSyncStatus) error {
   404  	return checkContext(ctx)
   405  }
   406  
   407  // NotifyOverallSyncStatus implements KeybaseDaemon for KeybaseDeamonLocal.
   408  func (k *KeybaseDaemonLocal) NotifyOverallSyncStatus(
   409  	ctx context.Context, _ keybase1.FolderSyncStatus) error {
   410  	return checkContext(ctx)
   411  }
   412  
   413  // FlushUserFromLocalCache implements the KeybaseDaemon interface for
   414  // KeybaseDaemonLocal.
   415  func (k *KeybaseDaemonLocal) FlushUserFromLocalCache(ctx context.Context,
   416  	uid keybase1.UID) {
   417  	// Do nothing.
   418  }
   419  
   420  // ClearCaches implements the KeybaseDaemon interface for
   421  // KeybaseDaemonLocal.
   422  func (k *KeybaseDaemonLocal) ClearCaches(_ context.Context) {
   423  	// Do nothing.
   424  }
   425  
   426  // EstablishMountDir implements the KeybaseDaemon interface for KeybaseDaemonLocal.
   427  func (k *KeybaseDaemonLocal) EstablishMountDir(ctx context.Context) (string, error) {
   428  	return "", nil
   429  }
   430  
   431  // PutGitMetadata implements the KeybaseService interface for
   432  // KeybaseDaemonLocal.
   433  func (k *KeybaseDaemonLocal) PutGitMetadata(
   434  	ctx context.Context, folder keybase1.FolderHandle, repoID keybase1.RepoID,
   435  	metadata keybase1.GitLocalMetadata) error {
   436  	return nil
   437  }
   438  
   439  // OnPathChange implements the SubscriptionNotifier interface.
   440  func (k *KeybaseDaemonLocal) OnPathChange(
   441  	clientID SubscriptionManagerClientID,
   442  	subscriptionIDs []SubscriptionID, path string, topics []keybase1.PathSubscriptionTopic) {
   443  }
   444  
   445  // OnNonPathChange implements the SubscriptionNotifier interface.
   446  func (k *KeybaseDaemonLocal) OnNonPathChange(
   447  	clientID SubscriptionManagerClientID,
   448  	subscriptionIDs []SubscriptionID, topic keybase1.SubscriptionTopic) {
   449  }
   450  
   451  // GetKVStoreClient implements the KeybaseService interface.
   452  func (k *KeybaseDaemonLocal) GetKVStoreClient() keybase1.KvstoreInterface {
   453  	return nil
   454  }
   455  
   456  // GetKeybaseDaemonRawClient implements the KeybaseService interface for
   457  // KeybaseServiceBase.
   458  func (k *KeybaseDaemonLocal) GetKeybaseDaemonRawClient() rpc.GenericClient {
   459  	return nil
   460  }
   461  
   462  // Shutdown implements KeybaseDaemon for KeybaseDaemonLocal.
   463  func (k *KeybaseDaemonLocal) Shutdown() {
   464  	k.favoriteStore.Shutdown()
   465  }
   466  
   467  func newKeybaseDaemonLocal(
   468  	codec kbfscodec.Codec, currentUID keybase1.UID, users []idutil.LocalUser,
   469  	teams []idutil.TeamInfo, favoriteStore favoriteStore) *KeybaseDaemonLocal {
   470  	k := &KeybaseDaemonLocal{
   471  		DaemonLocal:   idutil.NewDaemonLocal(currentUID, users, teams, codec),
   472  		favoriteStore: favoriteStore,
   473  	}
   474  	return k
   475  }
   476  
   477  // NewKeybaseDaemonDisk constructs a KeybaseDaemonLocal object given a
   478  // set of possible users, and one user that should be "logged in".
   479  // Any storage (e.g. the favorites) persists to disk.
   480  func NewKeybaseDaemonDisk(currentUID keybase1.UID, users []idutil.LocalUser,
   481  	teams []idutil.TeamInfo, favDBFile string, codec kbfscodec.Codec) (
   482  	*KeybaseDaemonLocal, error) {
   483  	favoriteDb, err := leveldb.OpenFile(favDBFile, ldbutils.LeveldbOptions(nil))
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  	favoriteStore := diskFavoriteClient{favoriteDb, codec}
   488  	return newKeybaseDaemonLocal(
   489  		codec, currentUID, users, teams, favoriteStore), nil
   490  }
   491  
   492  // NewKeybaseDaemonMemory constructs a KeybaseDaemonLocal object given
   493  // a set of possible users, and one user that should be "logged in".
   494  // Any storage (e.g. the favorites) is kept in memory only.
   495  func NewKeybaseDaemonMemory(currentUID keybase1.UID,
   496  	users []idutil.LocalUser, teams []idutil.TeamInfo,
   497  	codec kbfscodec.Codec) *KeybaseDaemonLocal {
   498  	favoriteStore := memoryFavoriteClient{
   499  		favorites: make(map[keybase1.UID]map[string]keybase1.FolderHandle),
   500  	}
   501  	return newKeybaseDaemonLocal(codec, currentUID, users, teams, favoriteStore)
   502  }