github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/libkb/loaduser.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package libkb
     5  
     6  import (
     7  	"fmt"
     8  	"runtime/debug"
     9  	"time"
    10  
    11  	"golang.org/x/net/context"
    12  
    13  	"github.com/keybase/client/go/jsonhelpers"
    14  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    15  	jsonw "github.com/keybase/go-jsonw"
    16  )
    17  
    18  type UIDer interface {
    19  	GetUID() keybase1.UID
    20  }
    21  
    22  type StubMode int
    23  
    24  const (
    25  	StubModeStubbed   StubMode = 0
    26  	StubModeUnstubbed StubMode = 1
    27  )
    28  
    29  func StubModeFromUnstubbedBool(unstubbed bool) StubMode {
    30  	if unstubbed {
    31  		return StubModeUnstubbed
    32  	}
    33  	return StubModeStubbed
    34  }
    35  
    36  func (s StubMode) String() string {
    37  	if s == StubModeUnstubbed {
    38  		return "unstubbed"
    39  	}
    40  	return "stubbed"
    41  }
    42  
    43  type LoadUserArg struct {
    44  	uid                      keybase1.UID
    45  	name                     string // Can also be an assertion like foo@twitter
    46  	publicKeyOptional        bool
    47  	noCacheResult            bool // currently ignore
    48  	self                     bool
    49  	forceReload              bool // ignore the cache entirely, don't even bother polling if it is out of date; just fetch new data.
    50  	forcePoll                bool // for cached user load, force a repoll. If this and StaleOK are both set, we try a network call and if it fails return the cached data.
    51  	staleOK                  bool // if stale cached versions are OK (for immutable fields)
    52  	cachedOnly               bool // only return cached data (staleOK should be true and forcePoll must be false)
    53  	uider                    UIDer
    54  	abortIfSigchainUnchanged bool
    55  	resolveBody              *jsonw.Wrapper // some load paths plumb this through
    56  	upakLite                 bool
    57  	stubMode                 StubMode // by default, this is StubModeStubbed, meaning, stubbed links are OK
    58  	forceMerkleServerPolling bool     // can be used to force or suppress server merkle polling, if set
    59  
    60  	// NOTE: We used to have these feature flags, but we got rid of them, to
    61  	// avoid problems where a yes-features load doesn't accidentally get served
    62  	// the result of an earlier no-features load from cache. We shouldn't add
    63  	// any more flags like this unless we also add machinery to avoid that
    64  	// mistake.
    65  	// AllKeys      bool
    66  	// AllSubchains bool
    67  
    68  	// We might have already loaded these if we're falling back from a
    69  	// failed LoadUserPlusKeys load
    70  	merkleLeaf *MerkleUserLeaf
    71  	sigHints   *SigHints
    72  	m          MetaContext
    73  }
    74  
    75  func (arg LoadUserArg) String() string {
    76  	leaf := "nil"
    77  	if arg.merkleLeaf != nil {
    78  		leaf = fmt.Sprintf("%v", arg.merkleLeaf.idVersion)
    79  	}
    80  	return fmt.Sprintf("{UID:%s Name:%q PublicKeyOptional:%v NoCacheResult:%v Self:%v ForceReload:%v ForcePoll:%v StaleOK:%v AbortIfSigchainUnchanged:%v CachedOnly:%v Leaf:%v %v}",
    81  		arg.uid, arg.name, arg.publicKeyOptional, arg.noCacheResult, arg.self, arg.forceReload,
    82  		arg.forcePoll, arg.staleOK, arg.abortIfSigchainUnchanged, arg.cachedOnly, leaf, arg.stubMode)
    83  }
    84  
    85  func (arg LoadUserArg) MetaContext() MetaContext {
    86  	return arg.m
    87  }
    88  
    89  func NewLoadUserArg(g *GlobalContext) LoadUserArg {
    90  	return LoadUserArg{m: NewMetaContextBackground(g)}
    91  }
    92  
    93  func NewLoadUserArgWithMetaContext(m MetaContext) LoadUserArg {
    94  	return LoadUserArg{
    95  		m:     m,
    96  		uider: m.LoginContext(),
    97  	}
    98  }
    99  
   100  func NewLoadUserArgWithContext(ctx context.Context, g *GlobalContext) LoadUserArg {
   101  	return LoadUserArg{
   102  		m: NewMetaContext(ctx, g),
   103  	}
   104  }
   105  
   106  func NewLoadUserSelfArg(g *GlobalContext) LoadUserArg {
   107  	ret := NewLoadUserArg(g)
   108  	ret.self = true
   109  	return ret
   110  }
   111  
   112  func NewLoadUserSelfAndUIDArg(g *GlobalContext) LoadUserArg {
   113  	ret := NewLoadUserArg(g)
   114  	ret.self = true
   115  	ret.uid = g.GetMyUID()
   116  	return ret
   117  }
   118  
   119  func NewLoadUserForceArg(g *GlobalContext) LoadUserArg {
   120  	arg := NewLoadUserPubOptionalArg(g)
   121  	arg.forceReload = true
   122  	return arg
   123  }
   124  
   125  func NewLoadUserByNameArg(g *GlobalContext, name string) LoadUserArg {
   126  	arg := NewLoadUserArg(g)
   127  	arg.name = name
   128  	return arg
   129  }
   130  
   131  func NewLoadUserByUIDArg(ctx context.Context, g *GlobalContext, uid keybase1.UID) LoadUserArg {
   132  	arg := NewLoadUserArgWithMetaContext(NewMetaContext(ctx, g))
   133  	arg.uid = uid
   134  	return arg
   135  }
   136  
   137  func NewLoadUserByUIDForceArg(g *GlobalContext, uid keybase1.UID) LoadUserArg {
   138  	arg := NewLoadUserArg(g)
   139  	arg.uid = uid
   140  	arg.forceReload = true
   141  	return arg
   142  }
   143  
   144  func NewLoadUserPubOptionalArg(g *GlobalContext) LoadUserArg {
   145  	arg := NewLoadUserArg(g)
   146  	arg.publicKeyOptional = true
   147  	return arg
   148  }
   149  
   150  func (arg LoadUserArg) WithSelf(self bool) LoadUserArg {
   151  	arg.self = self
   152  	return arg
   153  }
   154  
   155  func (arg LoadUserArg) EnsureCtxAndLogTag() LoadUserArg {
   156  	arg.m = arg.m.EnsureCtx().WithLogTag("LU")
   157  	return arg
   158  }
   159  
   160  func (arg LoadUserArg) WithCachedOnly(b bool) LoadUserArg {
   161  	arg.cachedOnly = b
   162  	return arg
   163  }
   164  
   165  func (arg LoadUserArg) WithResolveBody(r *jsonw.Wrapper) LoadUserArg {
   166  	arg.resolveBody = r
   167  	return arg
   168  }
   169  
   170  func (arg LoadUserArg) WithName(n string) LoadUserArg {
   171  	arg.name = n
   172  	return arg
   173  }
   174  
   175  func (arg LoadUserArg) WithForceMerkleServerPolling(b bool) LoadUserArg {
   176  	arg.forceMerkleServerPolling = b
   177  	return arg
   178  }
   179  
   180  func (arg LoadUserArg) WithNetContext(ctx context.Context) LoadUserArg {
   181  	arg.m = arg.m.WithCtx(ctx)
   182  	return arg
   183  }
   184  
   185  func (arg LoadUserArg) WithUID(uid keybase1.UID) LoadUserArg {
   186  	arg.uid = uid
   187  	return arg
   188  }
   189  
   190  func (arg LoadUserArg) WithPublicKeyOptional() LoadUserArg {
   191  	arg.publicKeyOptional = true
   192  	return arg
   193  }
   194  
   195  func (arg LoadUserArg) ForUPAKLite() LoadUserArg {
   196  	arg.upakLite = true
   197  	return arg
   198  }
   199  
   200  func (arg LoadUserArg) WithForcePoll(fp bool) LoadUserArg {
   201  	arg.forcePoll = fp
   202  	return arg
   203  }
   204  
   205  func (arg LoadUserArg) WithStaleOK(b bool) LoadUserArg {
   206  	arg.staleOK = b
   207  	return arg
   208  }
   209  
   210  func (arg LoadUserArg) GetNetContext() context.Context {
   211  	if ctx := arg.m.Ctx(); ctx != nil {
   212  		return ctx
   213  	}
   214  	return context.Background()
   215  }
   216  
   217  func (arg LoadUserArg) WithLoginContext(l LoginContext) LoadUserArg {
   218  	arg.uider = l
   219  	return arg
   220  }
   221  
   222  func (arg LoadUserArg) WithAbortIfSigchainUnchanged() LoadUserArg {
   223  	arg.abortIfSigchainUnchanged = true
   224  	return arg
   225  }
   226  
   227  func (arg LoadUserArg) WithForceReload() LoadUserArg {
   228  	arg.forceReload = true
   229  	return arg
   230  }
   231  
   232  func (arg LoadUserArg) WithStubMode(sm StubMode) LoadUserArg {
   233  	arg.stubMode = sm
   234  	return arg
   235  }
   236  
   237  func (arg LoadUserArg) ToMerkleOpts() MerkleOpts {
   238  	ret := MerkleOpts{}
   239  	if !arg.forceReload && !arg.forcePoll && !arg.forceMerkleServerPolling {
   240  		ret.NoServerPolling = true
   241  	}
   242  	return ret
   243  }
   244  
   245  func (arg *LoadUserArg) checkUIDName() error {
   246  	if arg.uid.Exists() {
   247  		return nil
   248  	}
   249  
   250  	if len(arg.name) == 0 && !arg.self {
   251  		return fmt.Errorf("no username given to LoadUser")
   252  	}
   253  
   254  	if len(arg.name) > 0 && arg.self {
   255  		return fmt.Errorf("If loading self, can't provide a username")
   256  	}
   257  
   258  	if !arg.self {
   259  		return nil
   260  	}
   261  
   262  	if arg.uid = myUID(arg.m.G(), arg.uider); arg.uid.IsNil() {
   263  		arg.name = arg.m.G().Env.GetUsername().String()
   264  		if len(arg.name) == 0 {
   265  			return SelfNotFoundError{msg: "could not find UID or username for self"}
   266  		}
   267  	}
   268  	return nil
   269  }
   270  
   271  func (arg *LoadUserArg) resolveUID() (ResolveResult, error) {
   272  	var rres ResolveResult
   273  	if arg.uid.Exists() {
   274  		return rres, nil
   275  	}
   276  	if len(arg.name) == 0 {
   277  		// this won't happen anymore because check moved to
   278  		// checkUIDName() func, but just in case
   279  		return rres, fmt.Errorf("resolveUID: no uid or name")
   280  	}
   281  
   282  	if rres = arg.m.G().Resolver.ResolveWithBody(arg.m, arg.name).FailOnDeleted(); rres.err != nil {
   283  		return rres, rres.err
   284  	}
   285  
   286  	if rres.uid.IsNil() {
   287  		return rres, fmt.Errorf("No resolution for name=%s", arg.name)
   288  	}
   289  
   290  	arg.uid = rres.uid
   291  	return rres, nil
   292  }
   293  
   294  // after resolution, check if this is a self load
   295  func (arg *LoadUserArg) checkSelf() {
   296  	if arg.self {
   297  		return
   298  	}
   299  
   300  	myuid := myUID(arg.m.G(), arg.uider)
   301  	if myuid.Exists() && arg.uid.Exists() && myuid.Equal(arg.uid) {
   302  		arg.self = true
   303  	}
   304  }
   305  
   306  func LoadMe(arg LoadUserArg) (*User, error) {
   307  	arg.self = true
   308  	return LoadUser(arg)
   309  }
   310  
   311  func LoadMeByUID(ctx context.Context, g *GlobalContext, uid keybase1.UID) (*User, error) {
   312  	return LoadMe(NewLoadUserByUIDArg(ctx, g, uid))
   313  }
   314  func LoadMeByMetaContextAndUID(m MetaContext, uid keybase1.UID) (*User, error) {
   315  	return LoadMe(NewLoadUserArgWithMetaContext(m).WithUID(uid))
   316  }
   317  
   318  func LoadUser(arg LoadUserArg) (ret *User, err error) {
   319  	m := arg.MetaContext().WithLogTag("LU")
   320  	defer m.Trace(fmt.Sprintf("LoadUser(%s)", arg), &err)()
   321  
   322  	var refresh bool
   323  
   324  	if m.G().VDL.DumpSiteLoadUser() {
   325  		debug.PrintStack()
   326  	}
   327  
   328  	// Whatever the reply is, pass along our desired global context
   329  	var refreshReason string
   330  	defer func() {
   331  		if ret != nil {
   332  			ret.SetGlobalContext(m.G())
   333  			if refresh {
   334  				m.G().NotifyRouter.HandleUserChanged(m, ret.GetUID(), fmt.Sprintf("libkb.LoadUser refresh '%v'", refreshReason))
   335  			}
   336  		}
   337  	}()
   338  
   339  	// make sure we have a uid or a name to load
   340  	if err = arg.checkUIDName(); err != nil {
   341  		return nil, err
   342  	}
   343  
   344  	m.Debug("LoadUser(uid=%v, name=%v)", arg.uid, arg.name)
   345  
   346  	// resolve the uid from the name, if necessary
   347  	rres, err := arg.resolveUID()
   348  	if err != nil {
   349  		return nil, err
   350  	}
   351  
   352  	// check to see if this is a self load
   353  	arg.checkSelf()
   354  
   355  	m.Debug("| resolved to %s", arg.uid)
   356  
   357  	if arg.uid.Exists() {
   358  		lock, err := m.G().loadUserLockTab.AcquireOnNameWithContextAndTimeout(m.Ctx(), m.G(), arg.uid.String(), 30*time.Second)
   359  		if err != nil {
   360  			m.Debug("| error acquiring singleflight lock for %s: %v", arg.uid, err)
   361  			return nil, err
   362  		}
   363  		defer lock.Release(m.Ctx())
   364  	}
   365  
   366  	// We can get the user object's body from either the resolution result or
   367  	// if it was plumbed through as a parameter.
   368  	resolveBody := rres.body
   369  	if resolveBody == nil {
   370  		resolveBody = arg.resolveBody
   371  	}
   372  
   373  	// get sig hints from local db in order to populate during merkle leaf lookup
   374  	// They might have already been loaded in.
   375  	var sigHints *SigHints
   376  	if sigHints = arg.sigHints; sigHints == nil {
   377  		sigHints, err = LoadSigHints(m, arg.uid)
   378  		if err != nil {
   379  			return nil, err
   380  		}
   381  	}
   382  
   383  	// load user from local, remote
   384  	ret, refresh, refreshReason, err = loadUser(m, arg.uid, resolveBody, sigHints, arg.forceReload, arg.merkleLeaf, arg.WithForceMerkleServerPolling(true).ToMerkleOpts())
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	ret.sigHints = sigHints
   390  
   391  	// Match the returned User object to the Merkle tree. Also make sure
   392  	// that the username queried for matches the User returned (if it
   393  	// was indeed queried for)
   394  	if err = ret.leaf.MatchUser(ret, arg.uid, rres.GetNormalizedQueriedUsername()); err != nil {
   395  		return ret, err
   396  	}
   397  
   398  	if err = ret.LoadSigChains(m, &ret.leaf, arg.self, arg.stubMode); err != nil {
   399  		return ret, err
   400  	}
   401  
   402  	if arg.abortIfSigchainUnchanged && ret.sigChain().wasFullyCached {
   403  		return nil, nil
   404  	}
   405  
   406  	if ret.sigHints != nil && ret.sigHints.dirty {
   407  		refresh = true
   408  	}
   409  
   410  	// Proactively cache fetches from remote server to local storage
   411  	if e2 := ret.Store(m); e2 != nil {
   412  		m.Warning("Problem storing user %s: %s", ret.GetName(), e2)
   413  	}
   414  
   415  	if ret.HasActiveKey() {
   416  		if err = ret.MakeIDTable(m); err != nil {
   417  			return ret, err
   418  		}
   419  
   420  		// Check that the user has self-signed only after we
   421  		// consider revocations. See: https://github.com/keybase/go/issues/43
   422  		if err = ret.VerifySelfSig(); err != nil {
   423  			return ret, err
   424  		}
   425  
   426  		cacheUserServiceSummary(m, ret)
   427  	} else if !arg.publicKeyOptional {
   428  		m.Debug("No active key for user: %s", ret.GetUID())
   429  		return ret, NoKeyError{}
   430  	}
   431  
   432  	return ret, err
   433  }
   434  
   435  func loadUser(m MetaContext, uid keybase1.UID, resolveBody *jsonw.Wrapper, sigHints *SigHints, force bool, leaf *MerkleUserLeaf, merkleOpts MerkleOpts) (*User, bool, string, error) {
   436  	local, err := LoadUserFromLocalStorage(m, uid)
   437  	var refresh bool
   438  	var refreshReason string
   439  	if err != nil {
   440  		m.Warning("Failed to load %s from storage: %s", uid, err)
   441  	}
   442  
   443  	if leaf == nil {
   444  		leaf, err = lookupMerkleLeaf(m, uid, (local != nil), sigHints, merkleOpts)
   445  		if err != nil {
   446  			return nil, refresh, refreshReason, err
   447  		}
   448  	}
   449  
   450  	var f1, loadRemote bool
   451  
   452  	if local == nil {
   453  		m.Debug("| No local user stored for %s", uid)
   454  		loadRemote = true
   455  	} else if f1, refreshReason, err = local.CheckBasicsFreshness(leaf.idVersion); err != nil {
   456  		return nil, refresh, refreshReason, err
   457  	} else {
   458  		loadRemote = !f1
   459  		refresh = loadRemote
   460  	}
   461  
   462  	m.Debug("| Freshness: basics=%v; for %s", f1, uid)
   463  
   464  	var ret *User
   465  	if !loadRemote && !force {
   466  		ret = local
   467  	} else if ret, err = LoadUserFromServer(m, uid, resolveBody); err != nil {
   468  		return nil, refresh, refreshReason, err
   469  	}
   470  
   471  	if ret == nil {
   472  		return nil, refresh, refreshReason, nil
   473  	}
   474  
   475  	if leaf != nil {
   476  		ret.leaf = *leaf
   477  	}
   478  	return ret, refresh, refreshReason, nil
   479  }
   480  
   481  func LoadUserFromLocalStorage(m MetaContext, uid keybase1.UID) (u *User, err error) {
   482  	m.Debug("+ LoadUserFromLocalStorage(%s)", uid)
   483  	jw, err := m.G().LocalDb.Get(DbKeyUID(DBUser, uid))
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  
   488  	if jw == nil {
   489  		m.Debug("- loadUserFromLocalStorage(%s): Not found", uid)
   490  		return nil, nil
   491  	}
   492  
   493  	m.Debug("| Loaded successfully")
   494  
   495  	if u, err = NewUserFromLocalStorage(m.G(), jw); err != nil {
   496  		return nil, err
   497  	}
   498  
   499  	if u.id.NotEqual(uid) {
   500  		err = fmt.Errorf("Bad lookup; uid mismatch: %s != %s", uid, u.id)
   501  	}
   502  
   503  	m.Debug("| Loaded username %s (uid=%s)", u.name, uid)
   504  	m.Debug("- LoadUserFromLocalStorage(%s,%s)", u.name, uid)
   505  
   506  	return
   507  }
   508  
   509  // LoadUserEmails returns emails for logged in user
   510  func LoadUserEmails(m MetaContext) (emails []keybase1.Email, err error) {
   511  	uid := m.G().GetMyUID()
   512  	res, err := m.G().API.Get(m, APIArg{
   513  		Endpoint:    "user/private",
   514  		SessionType: APISessionTypeREQUIRED,
   515  		Args: HTTPArgs{
   516  			"uid": UIDArg(uid),
   517  		},
   518  	})
   519  	if err != nil {
   520  		return
   521  	}
   522  
   523  	emailPayloads, err := jsonhelpers.JSONGetChildren(res.Body.AtKey("them").AtKey("emails").AtKey("emails"))
   524  	if err != nil {
   525  		return nil, err
   526  	}
   527  	for _, emailPayload := range emailPayloads {
   528  		email, err := emailPayload.AtKey("email").GetString()
   529  		if err != nil {
   530  			return nil, err
   531  		}
   532  		isPrimary, err := emailPayload.AtKey("is_primary").GetInt()
   533  		if err != nil {
   534  			return nil, err
   535  		}
   536  		isVerified, err := emailPayload.AtKey("is_verified").GetInt()
   537  		if err != nil {
   538  			return nil, err
   539  		}
   540  		visibilityCode, err := emailPayload.AtKey("visibility").GetInt()
   541  		if err != nil {
   542  			return nil, err
   543  		}
   544  
   545  		var lastVerifyEmailDate int64
   546  		lastVerifyEmailDateNode := emailPayload.AtKey("last_verify_email_date")
   547  		if lastVerifyEmailDateNode.IsOk() && !lastVerifyEmailDateNode.IsNil() {
   548  			lastVerifyEmailDate, err = lastVerifyEmailDateNode.GetInt64()
   549  			if err != nil {
   550  				return nil, err
   551  			}
   552  		}
   553  
   554  		emails = append(emails, keybase1.Email{
   555  			Email:               keybase1.EmailAddress(email),
   556  			IsVerified:          isVerified == 1,
   557  			IsPrimary:           isPrimary == 1,
   558  			Visibility:          keybase1.IdentityVisibility(visibilityCode),
   559  			LastVerifyEmailDate: keybase1.UnixTime(lastVerifyEmailDate),
   560  		})
   561  	}
   562  
   563  	return
   564  }
   565  
   566  func LoadUserFromServer(m MetaContext, uid keybase1.UID, body *jsonw.Wrapper) (u *User, err error) {
   567  	m.Debug("Load User from server: %s", uid)
   568  
   569  	// Res.body might already have been preloaded as a result of a Resolve call earlier.
   570  	if body == nil {
   571  		res, err := m.G().API.Get(m, APIArg{
   572  			Endpoint:    "user/lookup",
   573  			SessionType: APISessionTypeNONE,
   574  			Args: HTTPArgs{
   575  				"uid":          UIDArg(uid),
   576  				"load_deleted": B{true},
   577  			},
   578  		})
   579  
   580  		if err != nil {
   581  			return nil, err
   582  		}
   583  		body = res.Body.AtKey("them")
   584  	} else {
   585  		m.Debug("| Skipped load; got user object previously")
   586  	}
   587  
   588  	if u, err = NewUserFromServer(m.G(), body); err != nil {
   589  		return u, err
   590  	}
   591  	m.Debug("- Load user from server: %s -> %s", uid, ErrToOk(err))
   592  
   593  	return u, err
   594  }
   595  
   596  func myUID(g *GlobalContext, uider UIDer) keybase1.UID {
   597  	if uider != nil {
   598  		return uider.GetUID()
   599  	}
   600  	return g.GetMyUID()
   601  }
   602  
   603  func lookupMerkleLeaf(m MetaContext, uid keybase1.UID, localExists bool, sigHints *SigHints, merkleOpts MerkleOpts) (f *MerkleUserLeaf, err error) {
   604  	if uid.IsNil() {
   605  		err = fmt.Errorf("uid parameter for lookupMerkleLeaf empty")
   606  		return
   607  	}
   608  
   609  	q := NewHTTPArgs()
   610  	q.Add("uid", UIDArg(uid))
   611  
   612  	f, err = m.G().MerkleClient.LookupUser(m, q, sigHints, merkleOpts)
   613  	if err == nil && f == nil && localExists {
   614  		err = fmt.Errorf("User not found in server Merkle tree")
   615  	}
   616  
   617  	return
   618  }
   619  
   620  func lookupSigHintsAndMerkleLeaf(m MetaContext, uid keybase1.UID, localExists bool, merkleOpts MerkleOpts) (sigHints *SigHints, leaf *MerkleUserLeaf, err error) {
   621  	defer m.Trace("lookupSigHintsAndMerkleLeaf", &err)()
   622  	sigHints, err = LoadSigHints(m, uid)
   623  	if err != nil {
   624  		return nil, nil, err
   625  	}
   626  
   627  	leaf, err = lookupMerkleLeaf(m, uid, true, sigHints, merkleOpts)
   628  	if err != nil {
   629  		return nil, nil, err
   630  	}
   631  
   632  	return sigHints, leaf, nil
   633  }
   634  
   635  // LoadUserPlusKeys loads user and keys for the given UID.  If `pollForKID` is provided, we'll request
   636  // this user potentially twice: the first time can hit the cache for the UID, but will force a repoll
   637  // unless the pollForKID is found for the user.  If pollForKID is empty, then just access the cache as
   638  // normal.
   639  func LoadUserPlusKeys(ctx context.Context, g *GlobalContext, uid keybase1.UID, pollForKID keybase1.KID) (keybase1.UserPlusKeys, error) {
   640  	return g.GetUPAKLoader().LoadUserPlusKeys(ctx, uid, pollForKID)
   641  }
   642  
   643  // IsUserByUsernameOffline checks to see if the given username is a legit Keybase username,
   644  // using only our offline cache and materials. Useful if you don't mean to share info
   645  // with the server, as in chat @-mentions. Will return true if it's known to be a legit
   646  // user, and false if it can't say for sure. "Legit" users in this context might
   647  // be deleted or reset; they just once existing as a user.
   648  func IsUserByUsernameOffline(m MetaContext, un NormalizedUsername) bool {
   649  	if m.G().UIDMapper.MapHardcodedUsernameToUID(un).Exists() {
   650  		return true
   651  	}
   652  
   653  	// We already took care of the bad username casing in the harcoded exception list above,
   654  	// so it's ok to treat the NormalizedUsername as a cased string.
   655  	uid := usernameToUIDPreserveCase(un.String())
   656  
   657  	// use the UPAKLoader with StaleOK, CachedOnly in order to get cached upak
   658  	arg := NewLoadUserArgWithMetaContext(m).WithUID(uid).WithPublicKeyOptional().WithStaleOK(true).WithCachedOnly(true)
   659  	_, _, err := m.G().GetUPAKLoader().LoadV2(arg)
   660  
   661  	if err == nil {
   662  		return true
   663  	}
   664  
   665  	if _, ok := err.(UserNotFoundError); !ok {
   666  		m.Debug("IsUserByUsernameOffline(%s) squashing error: %s", un.String(), err)
   667  	}
   668  
   669  	return false
   670  }
   671  
   672  func cacheUserServiceSummary(mctx MetaContext, user *User) {
   673  	serviceMapper := mctx.G().ServiceMapper
   674  	if serviceMapper == nil {
   675  		// no service summary mapper in current context - e.g. in tests.
   676  		return
   677  	}
   678  
   679  	remoteProofs := user.idTable.remoteProofLinks
   680  	if remoteProofs != nil {
   681  		summary := remoteProofs.toServiceSummary()
   682  		err := serviceMapper.InformOfServiceSummary(mctx.Ctx(), mctx.G(), user.id, summary)
   683  		if err != nil {
   684  			mctx.Debug("cacheUserServiceSummary for %q uid: %q: error: %s", user.name, user.id, err)
   685  		}
   686  	}
   687  }