github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/hidden/manager.go (about)

     1  package hidden
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	libkb "github.com/keybase/client/go/libkb"
     9  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    10  	storage "github.com/keybase/client/go/teams/storage"
    11  )
    12  
    13  const (
    14  	HiddenChainFlagCacheTime = 24 * time.Hour
    15  )
    16  
    17  // ChainManager manages a hidden team chain, and wraps put/gets to mem/disk storage.
    18  // Accesses are single-flighted by TeamID. Implements that libkb.HiddenTeamChainManager
    19  // interface.
    20  type ChainManager struct {
    21  	// single-flight lock on TeamID
    22  	locktab *libkb.LockTable
    23  
    24  	hiddenSupportStorage *storage.SupportsHiddenFlagStorage
    25  
    26  	// Hold onto FastTeamLoad by-products as long as we have room, and store
    27  	// them persistently to disk.
    28  	storage *storage.HiddenStorage
    29  }
    30  
    31  var _ libkb.HiddenTeamChainManager = (*ChainManager)(nil)
    32  
    33  type loadArg struct {
    34  	id     keybase1.TeamID
    35  	mutate func(libkb.MetaContext, *keybase1.HiddenTeamChain) (bool, error)
    36  }
    37  
    38  func (m *ChainManager) TeamSupportsHiddenChain(mctx libkb.MetaContext, id keybase1.TeamID) (state bool, err error) {
    39  	supportsHiddenState := m.hiddenSupportStorage.Get(mctx, id)
    40  	// if we never checked before or the chain was not supported but the cache
    41  	// expired, check again. Once enabled, hidden support cannot be revoked
    42  	// regardless of the cache staleness.
    43  	if supportsHiddenState != nil {
    44  		mctx.Debug("ChainManager#TeamSupportsHiddenChain(%v): current state is %+v", id, *supportsHiddenState)
    45  	} else {
    46  		mctx.Debug("ChainManager#TeamSupportsHiddenChain(%v): current state is nil", id)
    47  	}
    48  	if supportsHiddenState == nil || (!supportsHiddenState.State && mctx.G().Clock().Now().After(supportsHiddenState.CacheUntil)) {
    49  		mctx.Debug("ChainManager#TeamSupportsHiddenChain(%v): querying the server", id)
    50  		state, err = featureGateForTeamFromServer(mctx, id)
    51  		if err != nil {
    52  			mctx.Debug("ChainManager#TeamSupportsHiddenChain(%v): got error %v", id)
    53  			return false, err
    54  		}
    55  		supportsHiddenState = &storage.HiddenChainSupportState{TeamID: id, State: state, CacheUntil: mctx.G().Clock().Now().Add(HiddenChainFlagCacheTime)}
    56  		m.hiddenSupportStorage.Put(mctx, supportsHiddenState)
    57  	}
    58  	mctx.Debug("ChainManager#TeamSupportsHiddenChain(%s): returning %v", id, supportsHiddenState.State)
    59  	return supportsHiddenState.State, nil
    60  }
    61  
    62  func ShouldClearSupportFlagOnError(err error) bool {
    63  	if err == nil {
    64  		return false
    65  	}
    66  	return !strings.Contains(err.Error(), "API network error")
    67  }
    68  
    69  func (m *ChainManager) ClearSupportFlagIfFalse(mctx libkb.MetaContext, teamID keybase1.TeamID) {
    70  	mctx.Debug("ChainManager#ClearSupportFlagIfFalse(%v)", teamID)
    71  	m.hiddenSupportStorage.ClearEntryIfFalse(mctx, teamID)
    72  }
    73  
    74  // Tail returns the furthest known tail of the hidden team chain, as known to our local cache.
    75  // Needed when posting new main chain links that point back to the most recently known tail.
    76  func (m *ChainManager) Tail(mctx libkb.MetaContext, id keybase1.TeamID) (*keybase1.LinkTriple, error) {
    77  	mctx = withLogTag(mctx)
    78  	state, err := m.loadAndMutate(mctx, loadArg{id: id})
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	if state == nil {
    83  		return nil, nil
    84  	}
    85  	return state.MaxTriple(), nil
    86  }
    87  
    88  func (m *ChainManager) loadLocked(mctx libkb.MetaContext, arg loadArg) (ret *keybase1.HiddenTeamChain, frozen bool, err error) {
    89  	state, frozen, tombstoned := m.storage.Get(mctx, arg.id, arg.id.IsPublic())
    90  	if tombstoned {
    91  		return nil, false, NewTombstonedError("cannot load hidden chain for tombstoned team")
    92  	}
    93  	return state, frozen, nil
    94  }
    95  
    96  func withLogTag(mctx libkb.MetaContext) libkb.MetaContext {
    97  	return mctx.WithLogTag("HTCM")
    98  }
    99  
   100  // Load hidden team chain data from storage, either mem or disk. Will not hit the network.
   101  func (m *ChainManager) Load(mctx libkb.MetaContext, id keybase1.TeamID) (ret *keybase1.HiddenTeamChain, err error) {
   102  	mctx = withLogTag(mctx)
   103  	ret, err = m.loadAndMutate(mctx, loadArg{id: id})
   104  	return ret, err
   105  }
   106  
   107  func (m *ChainManager) checkFrozen(mctx libkb.MetaContext, newState *keybase1.HiddenTeamChain, frozenState *keybase1.HiddenTeamChain) (err error) {
   108  	if frozenState == nil || frozenState.Last == keybase1.Seqno(0) {
   109  		return nil
   110  	}
   111  	if newState == nil {
   112  		return NewManagerError("previously frozen state was non-nil, but this state is nil")
   113  	}
   114  	link, ok := frozenState.Outer[frozenState.Last]
   115  	if !ok {
   116  		return NewManagerError("bad frozen state, couldn't find link for %d", frozenState.Last)
   117  	}
   118  	newLink, ok := newState.Outer[frozenState.Last]
   119  	if !ok {
   120  		return NewManagerError("On thaw, new state is missing link at %d", frozenState.Last)
   121  	}
   122  	if !newLink.Eq(link) {
   123  		return NewManagerError("On thaw, hash mismatch at link %d (%s != %s)", frozenState.Last, newLink, link)
   124  	}
   125  	return nil
   126  }
   127  
   128  // Load hidden team chain data from storage, either mem or disk. Will not hit the network.
   129  func (m *ChainManager) HintLatestSeqno(mctx libkb.MetaContext, id keybase1.TeamID, q keybase1.Seqno) (err error) {
   130  	mctx = withLogTag(mctx)
   131  	defer mctx.Trace(fmt.Sprintf("hidden.ChainManager#HintLatestSeqno(%d)", q), &err)()
   132  	_, err = m.loadAndMutate(mctx, loadArg{
   133  		id: id,
   134  		mutate: func(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain) (bool, error) {
   135  			changed := false
   136  			if state.LatestSeqnoHint < q {
   137  				mctx.Debug("For %s: update LatestSeqnoHint from %d -> %d", id, state.LatestSeqnoHint, q)
   138  				state.LatestSeqnoHint = q
   139  				changed = true
   140  			} else if state.LatestSeqnoHint == q {
   141  				mctx.Debug("For %s: update LatestSeqnoHint dupe update at %d", id, q)
   142  			} else {
   143  				mctx.Debug("For %s: refusing to backtrack from %d -> %d", id, state.LatestSeqnoHint, q)
   144  			}
   145  			return changed, nil
   146  
   147  		},
   148  	})
   149  	return err
   150  }
   151  
   152  func (m *ChainManager) loadAndMutate(mctx libkb.MetaContext, arg loadArg) (state *keybase1.HiddenTeamChain, err error) {
   153  	defer mctx.Trace(fmt.Sprintf("ChainManager#load(%+v)", arg), &err)()
   154  	lock := m.locktab.AcquireOnName(mctx.Ctx(), mctx.G(), arg.id.String())
   155  	defer lock.Release(mctx.Ctx())
   156  
   157  	var frozenState *keybase1.HiddenTeamChain
   158  	var frozen bool
   159  	frozenState, frozen, err = m.loadLocked(mctx, arg)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	if !frozen {
   164  		state = frozenState
   165  	}
   166  
   167  	if arg.mutate == nil {
   168  		return state, nil
   169  	}
   170  
   171  	var changed bool
   172  	if state == nil {
   173  		state = keybase1.NewHiddenTeamChain(arg.id)
   174  	}
   175  	changed, err = arg.mutate(mctx, state)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	if !changed {
   180  		return state, nil
   181  	}
   182  	if frozen {
   183  		err = m.checkFrozen(mctx, state, frozenState)
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  	}
   188  	state.CachedAt = keybase1.ToTime(mctx.G().Clock().Now())
   189  	m.storage.Put(mctx, state)
   190  
   191  	return state, nil
   192  }
   193  
   194  func (m *ChainManager) ratchet(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain, ratchet keybase1.HiddenTeamChainRatchetSet) (ret bool, err error) {
   195  	err = checkRatchets(mctx, state, ratchet)
   196  	if err != nil {
   197  		return false, err
   198  	}
   199  	updated := state.RatchetSet.Merge(ratchet)
   200  	return updated, nil
   201  }
   202  
   203  // Ratchet should be called when we know about advances in this chain but don't necessarily have the links to back the
   204  // ratchet up. We'll check them later when next we refresh. But we do check that the ratchet is consistent with the known
   205  // data (and ratchets) that we have.
   206  func (m *ChainManager) Ratchet(mctx libkb.MetaContext, id keybase1.TeamID, ratchets keybase1.HiddenTeamChainRatchetSet) (err error) {
   207  	mctx = withLogTag(mctx)
   208  	defer mctx.Trace(fmt.Sprintf("hidden.ChainManager#Ratchet(%s, %+v)", id, ratchets), &err)()
   209  	arg := loadArg{
   210  		id: id,
   211  		mutate: func(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain) (bool, error) {
   212  			return m.ratchet(mctx, state, ratchets)
   213  		},
   214  	}
   215  	_, err = m.loadAndMutate(mctx, arg)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	return nil
   220  }
   221  
   222  func (m *ChainManager) checkPrev(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain, newData keybase1.HiddenTeamChain, expectedPrev *keybase1.LinkTriple) (err error) {
   223  
   224  	// nothing to check if no new links
   225  	if len(newData.Outer) == 0 {
   226  		return nil
   227  	}
   228  
   229  	if expectedPrev == nil {
   230  		_, ok := newData.Outer[keybase1.Seqno(1)]
   231  		if !ok {
   232  			return NewManagerError("if no prev given, a head link is required")
   233  		}
   234  		return nil
   235  	}
   236  	link, ok := state.Outer[expectedPrev.Seqno]
   237  	if !ok {
   238  		return NewManagerError("update at %v left a chain gap", *expectedPrev)
   239  	}
   240  	if !link.Eq(expectedPrev.LinkID) {
   241  		return NewManagerError("prev mismatch at %v", *expectedPrev)
   242  	}
   243  	return nil
   244  }
   245  
   246  func (m *ChainManager) advance(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain, newData keybase1.HiddenTeamChain, expectedPrev *keybase1.LinkTriple) (update bool, err error) {
   247  	err = m.checkRatchetsOnAdvance(mctx, state.RatchetSet, newData)
   248  	if err != nil {
   249  		return false, err
   250  	}
   251  	err = m.checkPrev(mctx, state, newData, expectedPrev)
   252  	if err != nil {
   253  		return false, err
   254  	}
   255  	update, err = state.Merge(newData)
   256  	if err != nil {
   257  		return false, err
   258  	}
   259  	return update, nil
   260  }
   261  
   262  func (m *ChainManager) checkRatchetOnAdvance(mctx libkb.MetaContext, r keybase1.LinkTripleAndTime, newData keybase1.HiddenTeamChain) (err error) {
   263  	q := r.Triple.Seqno
   264  	link, ok := newData.Outer[q]
   265  	if ok && !link.Eq(r.Triple.LinkID) {
   266  		return NewManagerError("update data failed to match ratchet %+v", r)
   267  	}
   268  	return nil
   269  }
   270  
   271  func (m *ChainManager) checkRatchetsOnAdvance(mctx libkb.MetaContext, ratchets keybase1.HiddenTeamChainRatchetSet, newData keybase1.HiddenTeamChain) (err error) {
   272  	for _, r := range ratchets.Flat() {
   273  		err = m.checkRatchetOnAdvance(mctx, r, newData)
   274  		if err != nil {
   275  			return err
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  // Advance the stored hidden team storage by the given update. Before this function is called, we should
   282  // have checked many things:
   283  //   - that the PTKs match the unverified seeds sent down by the server.
   284  //   - that the postImages of the seedChecks are continuous, given a consistent set of seeds
   285  //   - that all full (unstubbed links) have valid reverse signatures
   286  //   - that all prevs are self consistent, and consistent with any preloaded data
   287  //   - that if the update starts in the middle of the chain, that its head has a prev, and that prev is consistent.
   288  //   - that the updates are consistent with any known ratchets
   289  //
   290  // See hidden.go for and the caller of this function for where that happens.
   291  func (m *ChainManager) Advance(mctx libkb.MetaContext, dat keybase1.HiddenTeamChain, expectedPrev *keybase1.LinkTriple) (err error) {
   292  	mctx = withLogTag(mctx)
   293  	defer mctx.Trace(fmt.Sprintf("hidden.ChainManager#Advance(%s)", dat.ID()), &err)()
   294  	arg := loadArg{
   295  		id: dat.ID(),
   296  		mutate: func(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain) (bool, error) {
   297  			return m.advance(mctx, state, dat, expectedPrev)
   298  		},
   299  	}
   300  	_, err = m.loadAndMutate(mctx, arg)
   301  	if err != nil {
   302  		return err
   303  	}
   304  	return nil
   305  }
   306  
   307  func NewChainManager(g *libkb.GlobalContext) *ChainManager {
   308  	return &ChainManager{
   309  		storage:              storage.NewHiddenStorage(g),
   310  		hiddenSupportStorage: storage.NewSupportsHiddenFlagStorage(g),
   311  		locktab:              libkb.NewLockTable(),
   312  	}
   313  }
   314  
   315  func NewChainManagerAndInstall(g *libkb.GlobalContext) *ChainManager {
   316  	ret := NewChainManager(g)
   317  	g.SetHiddenTeamChainManager(ret)
   318  	g.AddLogoutHook(ret, "HiddenTeamChainManager")
   319  	g.AddDbNukeHook(ret, "HiddenTeamChainManager")
   320  	return ret
   321  }
   322  
   323  func (m *ChainManager) Shutdown(mctx libkb.MetaContext) {
   324  	m.storage.Shutdown()
   325  	m.hiddenSupportStorage.Shutdown()
   326  }
   327  
   328  // OnLogout is called when the user logs out, which purges the LRU.
   329  func (m *ChainManager) OnLogout(mctx libkb.MetaContext) error {
   330  	m.storage.ClearMem()
   331  	m.hiddenSupportStorage.ClearMem()
   332  	return nil
   333  }
   334  
   335  // OnDbNuke is called when the disk cache is cleared, which purges the LRU.
   336  func (m *ChainManager) OnDbNuke(mctx libkb.MetaContext) error {
   337  	m.storage.ClearMem()
   338  	m.hiddenSupportStorage.ClearMem()
   339  	return nil
   340  }
   341  
   342  func (m *ChainManager) Tombstone(mctx libkb.MetaContext, id keybase1.TeamID) (err error) {
   343  	mctx = withLogTag(mctx)
   344  	defer mctx.Trace(fmt.Sprintf("hidden.ChainManager#Tombstone(%s)", id), &err)()
   345  	arg := loadArg{
   346  		id: id,
   347  		mutate: func(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain) (bool, error) {
   348  			return state.Tombstone(), nil
   349  		},
   350  	}
   351  	_, err = m.loadAndMutate(mctx, arg)
   352  	return err
   353  }
   354  
   355  func (m *ChainManager) Freeze(mctx libkb.MetaContext, id keybase1.TeamID) (err error) {
   356  	mctx = withLogTag(mctx)
   357  	defer mctx.Trace(fmt.Sprintf("hidden.ChainManager#Freeze(%s)", id), &err)()
   358  	arg := loadArg{
   359  		id: id,
   360  		mutate: func(mctx libkb.MetaContext, state *keybase1.HiddenTeamChain) (bool, error) {
   361  			return state.Freeze(), nil
   362  		},
   363  	}
   364  	_, err = m.loadAndMutate(mctx, arg)
   365  	return err
   366  }