github.com/keybase/client/go@v0.0.0-20240520164431-4f512a4c85a3/teams/loader_ctx.go (about)

     1  package teams
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	"github.com/keybase/client/go/sig3"
    14  	"github.com/keybase/client/go/teams/hidden"
    15  )
    16  
    17  // Things TeamLoader uses that are mocked out for tests.
    18  type LoaderContext interface {
    19  	// Get new links from the server.
    20  	getNewLinksFromServer(ctx context.Context,
    21  		teamID keybase1.TeamID, lows getLinksLows,
    22  		readSubteamID *keybase1.TeamID) (*rawTeam, error)
    23  	// Get full links from the server.
    24  	// Does not guarantee that the server returned the correct links, nor that they are unstubbed.
    25  	getLinksFromServer(ctx context.Context,
    26  		teamID keybase1.TeamID, requestSeqnos []keybase1.Seqno,
    27  		readSubteamID *keybase1.TeamID) (*rawTeam, error)
    28  	getMe(context.Context) (keybase1.UserVersion, error)
    29  	// Lookup the eldest seqno of a user. Can use the cache.
    30  	lookupEldestSeqno(context.Context, keybase1.UID) (keybase1.Seqno, error)
    31  	// Get the current user's per-user-key's derived encryption key (full).
    32  	perUserEncryptionKey(ctx context.Context, userSeqno keybase1.Seqno) (*libkb.NaclDHKeyPair, error)
    33  	merkleLookup(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, err error)
    34  	merkleLookupWithHidden(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error)
    35  	merkleLookupTripleInPast(ctx context.Context, isPublic bool, leafID keybase1.UserOrTeamID, root keybase1.MerkleRootV2) (triple *libkb.MerkleTriple, err error)
    36  	forceLinkMapRefreshForUser(ctx context.Context, uid keybase1.UID) (linkMap linkMapT, err error)
    37  	loadKeyV2(ctx context.Context, uid keybase1.UID, kid keybase1.KID, lkc *loadKeyCache) (keybase1.UserVersion, *keybase1.PublicKeyV2NaCl, linkMapT, error)
    38  }
    39  
    40  // The main LoaderContext is G.
    41  type LoaderContextG struct {
    42  	libkb.Contextified
    43  
    44  	// Cache of size=1 for caching merkle leaf lookups at the checkpoint, since in practice
    45  	// we hit this is rapid succession.
    46  	cacheMu     sync.RWMutex
    47  	cachedSeqno keybase1.Seqno
    48  	cachedLeaf  *libkb.MerkleGenericLeaf
    49  }
    50  
    51  var _ LoaderContext = (*LoaderContextG)(nil)
    52  
    53  func NewLoaderContextFromG(g *libkb.GlobalContext) LoaderContext {
    54  	return &LoaderContextG{
    55  		Contextified: libkb.NewContextified(g),
    56  	}
    57  }
    58  
    59  type rawTeam struct {
    60  	ID             keybase1.TeamID                                        `json:"id"`
    61  	Name           keybase1.TeamName                                      `json:"name"`
    62  	Status         libkb.AppStatus                                        `json:"status"`
    63  	Chain          []json.RawMessage                                      `json:"chain"`
    64  	Box            *TeamBox                                               `json:"box"`
    65  	Prevs          map[keybase1.PerTeamKeyGeneration]prevKeySealedEncoded `json:"prevs"`
    66  	ReaderKeyMasks []keybase1.ReaderKeyMask                               `json:"reader_key_masks"`
    67  	// Whether the user is only being allowed to view the chain
    68  	// because they are a member of a descendent team.
    69  	SubteamReader         bool                               `json:"subteam_reader"`
    70  	Showcase              keybase1.TeamShowcase              `json:"showcase"`
    71  	LegacyTLFUpgrade      []keybase1.TeamGetLegacyTLFUpgrade `json:"legacy_tlf_upgrade"`
    72  	HiddenChain           []sig3.ExportJSON                  `json:"hidden"`
    73  	RatchetBlindingKeySet *hidden.RatchetBlindingKeySet      `json:"ratchet_blinding_keys"`
    74  }
    75  
    76  func (r *rawTeam) GetAppStatus() *libkb.AppStatus {
    77  	return &r.Status
    78  }
    79  
    80  func (r *rawTeam) GetHiddenChain() []sig3.ExportJSON {
    81  	if r == nil {
    82  		return nil
    83  	}
    84  	return r.HiddenChain
    85  }
    86  
    87  func (r *rawTeam) unpackLinks(mctx libkb.MetaContext) (links []*ChainLinkUnpacked, err error) {
    88  	if r == nil {
    89  		return nil, nil
    90  	}
    91  	defer mctx.PerfTrace(fmt.Sprintf("TeamLoad: unpackLinks(%v, %d)", r.ID, len(r.Chain)), &err)()
    92  	start := time.Now()
    93  	defer func() {
    94  		if len(links) == 0 {
    95  			return
    96  		}
    97  		var message string
    98  		if err == nil {
    99  			message = fmt.Sprintf("Unpacking links %d for %s", len(r.Chain), r.ID)
   100  		} else {
   101  			message = fmt.Sprintf("Failed to unpack links for %s", r.ID)
   102  		}
   103  		mctx.G().RuntimeStats.PushPerfEvent(keybase1.PerfEvent{
   104  			EventType: keybase1.PerfEventType_TEAMCHAIN,
   105  			Message:   message,
   106  			Ctime:     keybase1.ToTime(start),
   107  		})
   108  	}()
   109  	parsedLinks, err := r.parseLinks(mctx.Ctx())
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  	for _, pLink := range parsedLinks {
   114  		pLink2 := pLink
   115  		link, err := unpackChainLink(&pLink2)
   116  		if err != nil {
   117  			return nil, err
   118  		}
   119  		if !link.isStubbed() {
   120  			if !link.innerTeamID.Eq(r.ID) {
   121  				return nil, fmt.Errorf("link has wrong team ID in response: %v != %v", link.innerTeamID, r.ID)
   122  			}
   123  		}
   124  		links = append(links, link)
   125  	}
   126  	return links, nil
   127  }
   128  
   129  func (r *rawTeam) parseLinks(ctx context.Context) ([]SCChainLink, error) {
   130  	var links []SCChainLink
   131  	for _, raw := range r.Chain {
   132  		link, err := ParseTeamChainLink(string(raw))
   133  		if err != nil {
   134  			return nil, err
   135  		}
   136  		links = append(links, link)
   137  	}
   138  	return links, nil
   139  }
   140  
   141  // Get new links from the server.
   142  func (l *LoaderContextG) getNewLinksFromServer(ctx context.Context,
   143  	teamID keybase1.TeamID, lows getLinksLows,
   144  	readSubteamID *keybase1.TeamID) (*rawTeam, error) {
   145  	return l.getLinksFromServerCommon(ctx, teamID, &lows, nil, readSubteamID)
   146  }
   147  
   148  // Get full links from the server.
   149  // Does not guarantee that the server returned the correct links, nor that they are unstubbed.
   150  func (l *LoaderContextG) getLinksFromServer(ctx context.Context,
   151  	teamID keybase1.TeamID, requestSeqnos []keybase1.Seqno, readSubteamID *keybase1.TeamID) (*rawTeam, error) {
   152  	return l.getLinksFromServerCommon(ctx, teamID, nil, requestSeqnos, readSubteamID)
   153  }
   154  
   155  func (l *LoaderContextG) getLinksFromServerCommon(ctx context.Context,
   156  	teamID keybase1.TeamID, lows *getLinksLows, requestSeqnos []keybase1.Seqno, readSubteamID *keybase1.TeamID) (*rawTeam, error) {
   157  
   158  	mctx := libkb.NewMetaContext(ctx, l.G())
   159  	arg := libkb.NewAPIArg("team/get")
   160  	arg.SessionType = libkb.APISessionTypeREQUIRED
   161  	if teamID.IsPublic() {
   162  		arg.SessionType = libkb.APISessionTypeOPTIONAL
   163  	}
   164  
   165  	arg.Args = libkb.HTTPArgs{
   166  		"id":     libkb.S{Val: teamID.String()},
   167  		"public": libkb.B{Val: teamID.IsPublic()},
   168  	}
   169  	if lows != nil {
   170  		arg.Args["low"] = libkb.I{Val: int(lows.Seqno)}
   171  		arg.Args["per_team_key_low"] = libkb.I{Val: int(lows.PerTeamKey)}
   172  		arg.Args["hidden_low"] = libkb.I{Val: int(lows.HiddenChainSeqno)}
   173  	}
   174  	if len(requestSeqnos) > 0 {
   175  		arg.Args["seqnos"] = libkb.S{Val: seqnosToString(requestSeqnos)}
   176  	}
   177  	if readSubteamID != nil {
   178  		arg.Args["read_subteam_id"] = libkb.S{Val: readSubteamID.String()}
   179  	}
   180  
   181  	var rt rawTeam
   182  	if err := mctx.G().API.GetDecode(mctx, arg, &rt); err != nil {
   183  		return nil, err
   184  	}
   185  	if !rt.ID.Eq(teamID) {
   186  		return nil, fmt.Errorf("server returned wrong team ID: %v != %v", rt.ID, teamID)
   187  	}
   188  	return &rt, nil
   189  }
   190  
   191  func seqnosToString(v []keybase1.Seqno) string {
   192  	var s []string
   193  	for _, e := range v {
   194  		s = append(s, fmt.Sprintf("%d", int(e)))
   195  	}
   196  	return strings.Join(s, ",")
   197  }
   198  
   199  func (l *LoaderContextG) getMe(ctx context.Context) (res keybase1.UserVersion, err error) {
   200  	uid := l.G().ActiveDevice.UID()
   201  	// If we're logged out, we still should be able to access the team loader
   202  	// for public teams. So we'll just return a nil UID here, and it should just work.
   203  	if uid.IsNil() {
   204  		return res, nil
   205  	}
   206  	return l.G().GetMeUV(ctx)
   207  }
   208  
   209  func (l *LoaderContextG) lookupEldestSeqno(ctx context.Context, uid keybase1.UID) (keybase1.Seqno, error) {
   210  	// Lookup the latest eldest seqno for that uid.
   211  	// This value may come from a cache.
   212  	upak, err := loadUPAK2(ctx, l.G(), uid, false /*forcePoll */)
   213  	if err != nil {
   214  		return keybase1.Seqno(1), err
   215  	}
   216  	return upak.Current.EldestSeqno, nil
   217  }
   218  
   219  func (l *LoaderContextG) perUserEncryptionKey(ctx context.Context, userSeqno keybase1.Seqno) (*libkb.NaclDHKeyPair, error) {
   220  	return perUserEncryptionKey(l.MetaContext(ctx), userSeqno)
   221  }
   222  
   223  func perUserEncryptionKey(m libkb.MetaContext, userSeqno keybase1.Seqno) (*libkb.NaclDHKeyPair, error) {
   224  	kr, err := m.G().GetPerUserKeyring(m.Ctx())
   225  	if err != nil {
   226  		return nil, err
   227  	}
   228  	return kr.GetEncryptionKeyBySeqnoOrSync(m, userSeqno)
   229  }
   230  
   231  func (l *LoaderContextG) merkleLookupWithHidden(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, hiddenResp *libkb.MerkleHiddenResponse, lastMerkleRoot *libkb.MerkleRoot, err error) {
   232  	leaf, hiddenResp, lastMerkleRoot, err := l.G().GetMerkleClient().LookupTeamWithHidden(l.MetaContext(ctx), teamID, hidden.ProcessHiddenResponseFunc)
   233  	if err != nil {
   234  		return r1, r2, nil, nil, err
   235  	}
   236  	r1, r2, err = l.processMerkleReply(ctx, teamID, public, leaf)
   237  	if err != nil {
   238  		return r1, r2, nil, nil, err
   239  	}
   240  
   241  	return r1, r2, hiddenResp, lastMerkleRoot, err
   242  }
   243  
   244  func (l *LoaderContextG) merkleLookup(ctx context.Context, teamID keybase1.TeamID, public bool) (r1 keybase1.Seqno, r2 keybase1.LinkID, err error) {
   245  	leaf, err := l.G().GetMerkleClient().LookupTeam(l.MetaContext(ctx), teamID)
   246  	if err != nil {
   247  		return r1, r2, err
   248  	}
   249  	r1, r2, err = l.processMerkleReply(ctx, teamID, public, leaf)
   250  	return r1, r2, err
   251  }
   252  
   253  func (l *LoaderContextG) processMerkleReply(ctx context.Context, teamID keybase1.TeamID, public bool, leaf *libkb.MerkleTeamLeaf) (r1 keybase1.Seqno, r2 keybase1.LinkID, err error) {
   254  
   255  	if !leaf.TeamID.Eq(teamID) {
   256  		return r1, r2, fmt.Errorf("merkle returned wrong leaf: %v != %v", leaf.TeamID.String(), teamID.String())
   257  	}
   258  
   259  	if public {
   260  		if leaf.Public == nil {
   261  			l.G().Log.CDebugf(ctx, "TeamLoader hidden error: merkle returned nil leaf")
   262  			return r1, r2, NewTeamDoesNotExistError(public, teamID.String())
   263  		}
   264  		return leaf.Public.Seqno, leaf.Public.LinkID.Export(), nil
   265  	}
   266  	if leaf.Private == nil {
   267  		l.G().Log.CDebugf(ctx, "TeamLoader hidden error: merkle returned nil leaf")
   268  		return r1, r2, NewTeamDoesNotExistError(public, teamID.String())
   269  	}
   270  	return leaf.Private.Seqno, leaf.Private.LinkID.Export(), nil
   271  }
   272  
   273  func (l *LoaderContextG) getCachedCheckpointLookup(leafID keybase1.UserOrTeamID, seqno keybase1.Seqno) *libkb.MerkleGenericLeaf {
   274  	l.cacheMu.RLock()
   275  	defer l.cacheMu.RUnlock()
   276  	if l.cachedLeaf == nil || !l.cachedLeaf.LeafID.Equal(leafID) || !l.cachedSeqno.Eq(seqno) {
   277  		return nil
   278  	}
   279  	ret := l.cachedLeaf.PartialClone()
   280  	return &ret
   281  }
   282  
   283  func (l *LoaderContextG) putCachedCheckpoint(seqno keybase1.Seqno, leaf *libkb.MerkleGenericLeaf) {
   284  	l.cacheMu.Lock()
   285  	defer l.cacheMu.Unlock()
   286  	tmp := leaf.PartialClone()
   287  	l.cachedLeaf = &tmp
   288  	l.cachedSeqno = seqno
   289  }
   290  
   291  func (l *LoaderContextG) merkleLookupTripleAtCheckpoint(mctx libkb.MetaContext, leafID keybase1.UserOrTeamID, seqno keybase1.Seqno) (leaf *libkb.MerkleGenericLeaf, err error) {
   292  
   293  	ret := l.getCachedCheckpointLookup(leafID, seqno)
   294  	if ret != nil {
   295  		mctx.VLogf(libkb.VLog0, "hit checkpoint cache")
   296  		return ret, nil
   297  	}
   298  
   299  	mc := l.G().MerkleClient
   300  	leaf, _, err = mc.LookupLeafAtSeqno(mctx, leafID, seqno)
   301  	if leaf != nil && err == nil {
   302  		l.putCachedCheckpoint(seqno, leaf)
   303  	}
   304  	return leaf, err
   305  }
   306  
   307  func (l *LoaderContextG) merkleLookupTripleInPast(ctx context.Context, isPublic bool, leafID keybase1.UserOrTeamID, root keybase1.MerkleRootV2) (triple *libkb.MerkleTriple, err error) {
   308  	mctx := l.MetaContext(ctx)
   309  
   310  	mc := l.G().MerkleClient
   311  	checkpoint := mc.FirstExaminableHistoricalRoot(mctx)
   312  	var leaf *libkb.MerkleGenericLeaf
   313  
   314  	// If we're trying to lookup a leaf from before the checkpoint, just bump forward to the checkpoint.
   315  	// The checkpoint is consindered to be a legitimate version of Tree.
   316  	if checkpoint != nil && root.Seqno < *checkpoint {
   317  		mctx.Debug("Bumping up pre-checkpoint merkle fetch to checkpoint at %d for %s", *checkpoint, leafID)
   318  		leaf, err = l.merkleLookupTripleAtCheckpoint(mctx, leafID, *checkpoint)
   319  	} else {
   320  		leaf, err = mc.LookupLeafAtHashMeta(mctx, leafID, root.HashMeta)
   321  	}
   322  
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  	if isPublic {
   327  		triple = leaf.Public
   328  	} else {
   329  		triple = leaf.Private
   330  	}
   331  	if triple == nil {
   332  		return nil, fmt.Errorf("unexpected nil leaf for %v", leafID)
   333  	}
   334  	return triple, nil
   335  }
   336  
   337  func (l *LoaderContextG) forceLinkMapRefreshForUser(ctx context.Context, uid keybase1.UID) (linkMap linkMapT, err error) {
   338  	arg := libkb.NewLoadUserArg(l.G()).WithNetContext(ctx).WithUID(uid).WithForcePoll(true)
   339  	upak, _, err := l.G().GetUPAKLoader().LoadV2(arg)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	return upak.SeqnoLinkIDs, nil
   344  }
   345  
   346  func (l *LoaderContextG) loadKeyV2(ctx context.Context, uid keybase1.UID, kid keybase1.KID, lkc *loadKeyCache) (
   347  	uv keybase1.UserVersion, pubKey *keybase1.PublicKeyV2NaCl, linkMap linkMapT, err error) {
   348  	ctx, tbs := l.G().CTimeBuckets(ctx)
   349  	defer tbs.Record("LoaderContextG.loadKeyV2")()
   350  
   351  	return lkc.loadKeyV2(l.MetaContext(ctx), uid, kid)
   352  }