github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/libkb/resolve.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  	"sync"
     9  	"time"
    10  
    11  	"runtime/debug"
    12  
    13  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    14  	jsonw "github.com/keybase/go-jsonw"
    15  	"stathat.com/c/ramcache"
    16  )
    17  
    18  type ResolveResult struct {
    19  	uid                keybase1.UID
    20  	teamID             keybase1.TeamID
    21  	body               *jsonw.Wrapper
    22  	err                error
    23  	queriedKbUsername  string
    24  	queriedByUID       bool
    25  	resolvedKbUsername string
    26  	queriedByTeamID    bool
    27  	resolvedTeamName   keybase1.TeamName
    28  	cachedAt           time.Time
    29  	mutable            bool
    30  	deleted            bool
    31  	isCompound         bool
    32  	isServerTrust      bool
    33  }
    34  
    35  func (res ResolveResult) HasPrimaryKey() bool {
    36  	return res.uid.Exists() || res.teamID.Exists()
    37  }
    38  
    39  func (res ResolveResult) String() string {
    40  	return fmt.Sprintf("{uid:%s teamID:%s err:%s mutable:%v}", res.uid, res.teamID, ErrToOk(res.err), res.mutable)
    41  }
    42  
    43  const (
    44  	resolveCacheTTL           = 12 * time.Hour
    45  	ResolveCacheMaxAge        = 12 * time.Hour
    46  	ResolveCacheMaxAgeMutable = 20 * time.Minute
    47  	resolveCacheMaxAgeErrored = 5 * time.Second
    48  )
    49  
    50  func (res *ResolveResult) GetUID() keybase1.UID {
    51  	return res.uid
    52  }
    53  
    54  func (res *ResolveResult) SetUIDForTesting(u keybase1.UID) {
    55  	res.uid = u
    56  }
    57  
    58  func (res *ResolveResult) User() keybase1.User {
    59  	return keybase1.User{
    60  		Uid:      res.GetUID(),
    61  		Username: res.GetNormalizedUsername().String(),
    62  	}
    63  }
    64  
    65  func (res *ResolveResult) UserOrTeam() keybase1.UserOrTeamLite {
    66  	var u keybase1.UserOrTeamLite
    67  	if res.GetUID().Exists() {
    68  		u.Id, u.Name = res.GetUID().AsUserOrTeam(), res.GetNormalizedUsername().String()
    69  	} else if res.GetTeamID().Exists() {
    70  		u.Id, u.Name = res.GetTeamID().AsUserOrTeam(), res.GetTeamName().String()
    71  	}
    72  	return u
    73  }
    74  
    75  func (res *ResolveResult) GetUsername() string {
    76  	return res.resolvedKbUsername
    77  }
    78  func (res *ResolveResult) GetNormalizedUsername() NormalizedUsername {
    79  	return NewNormalizedUsername(res.GetUsername())
    80  }
    81  func (res *ResolveResult) GetNormalizedQueriedUsername() NormalizedUsername {
    82  	return NewNormalizedUsername(res.queriedKbUsername)
    83  }
    84  
    85  func (res *ResolveResult) WasTeamIDAssertion() bool {
    86  	return res.queriedByTeamID
    87  }
    88  
    89  func (res *ResolveResult) GetTeamID() keybase1.TeamID {
    90  	return res.teamID
    91  }
    92  func (res *ResolveResult) GetTeamName() keybase1.TeamName {
    93  	return res.resolvedTeamName
    94  }
    95  
    96  func (res *ResolveResult) WasKBAssertion() bool {
    97  	return (res.queriedKbUsername != "" && !res.isCompound) || res.queriedByUID
    98  }
    99  
   100  func (res *ResolveResult) GetError() error {
   101  	return res.err
   102  }
   103  
   104  func (res *ResolveResult) GetBody() *jsonw.Wrapper {
   105  	return res.body
   106  }
   107  
   108  func (res *ResolveResult) GetDeleted() bool {
   109  	return res.deleted
   110  }
   111  
   112  func (res ResolveResult) FailOnDeleted() ResolveResult {
   113  	if res.deleted {
   114  		label := res.uid.String()
   115  		if res.resolvedKbUsername != "" {
   116  			label = res.resolvedKbUsername
   117  		}
   118  		res.err = UserDeletedError{Msg: fmt.Sprintf("user %q deleted", label)}
   119  	}
   120  	return res
   121  }
   122  
   123  func (res ResolveResult) IsServerTrust() bool {
   124  	return res.isServerTrust
   125  }
   126  
   127  func (r *ResolverImpl) ResolveWithBody(m MetaContext, input string) ResolveResult {
   128  	return r.resolve(m, input, true)
   129  }
   130  
   131  func (r *ResolverImpl) Resolve(m MetaContext, input string) ResolveResult {
   132  	return r.resolve(m, input, false)
   133  }
   134  
   135  func (r *ResolverImpl) resolve(m MetaContext, input string, withBody bool) (res ResolveResult) {
   136  	defer m.Trace(fmt.Sprintf("Resolving username %q", input), &res.err)()
   137  
   138  	var au AssertionURL
   139  	if au, res.err = ParseAssertionURL(m.G().MakeAssertionContext(m), input, false); res.err != nil {
   140  		return res
   141  	}
   142  	res = r.resolveURL(m, au, input, withBody, false)
   143  	return res
   144  }
   145  
   146  func (r *ResolverImpl) ResolveFullExpression(m MetaContext, input string) (res ResolveResult) {
   147  	return r.resolveFullExpression(m, input, false, false)
   148  }
   149  
   150  func (r *ResolverImpl) ResolveFullExpressionNeedUsername(m MetaContext, input string) (res ResolveResult) {
   151  	return r.resolveFullExpression(m, input, false, true)
   152  }
   153  
   154  func (r *ResolverImpl) ResolveFullExpressionWithBody(m MetaContext, input string) (res ResolveResult) {
   155  	return r.resolveFullExpression(m, input, true, false)
   156  }
   157  
   158  func (r *ResolverImpl) ResolveUser(m MetaContext, assertion string) (u keybase1.User, res ResolveResult, err error) {
   159  	res = r.ResolveFullExpressionNeedUsername(m, assertion)
   160  	err = res.GetError()
   161  	if err != nil {
   162  		return u, res, err
   163  	}
   164  	u = res.User()
   165  	if !u.Uid.Exists() {
   166  		return u, res, fmt.Errorf("no resolution for: %v", assertion)
   167  	}
   168  	return u, res, nil
   169  }
   170  
   171  func (r *ResolverImpl) resolveFullExpression(m MetaContext, input string, withBody bool, needUsername bool) (res ResolveResult) {
   172  	defer m.VTrace(VLog1, fmt.Sprintf("Resolver#resolveFullExpression(%q)", input), &res.err)()
   173  
   174  	var expr AssertionExpression
   175  	expr, res.err = AssertionParseAndOnly(m.G().MakeAssertionContext(m), input)
   176  	if res.err != nil {
   177  		return res
   178  	}
   179  	u := FindBestIdentifyComponentURL(expr)
   180  	if u == nil {
   181  		res.err = ResolutionError{Input: input, Msg: "Cannot find a resolvable factor"}
   182  		return res
   183  	}
   184  	ret := r.resolveURL(m, u, input, withBody, needUsername)
   185  	ret.isCompound = len(expr.CollectUrls(nil)) > 1
   186  	return ret
   187  }
   188  
   189  func (res *ResolveResult) addKeybaseNameIfKnown(au AssertionURL) {
   190  	if au.IsKeybase() && len(res.resolvedKbUsername) == 0 {
   191  		res.resolvedKbUsername = au.GetValue()
   192  	}
   193  }
   194  
   195  func (r *ResolverImpl) getFromDiskCache(m MetaContext, key string, au AssertionURL) (ret *ResolveResult) {
   196  	defer m.VTrace(VLog1, fmt.Sprintf("Resolver#getFromDiskCache(%q)", key), nil)()
   197  	var uid keybase1.UID
   198  	found, err := m.G().LocalDb.GetInto(&uid, resolveDbKey(key))
   199  	r.Stats.IncDiskGets()
   200  	if err != nil {
   201  		m.Warning("Problem fetching resolve result from local DB: %s", err)
   202  		return nil
   203  	}
   204  	if !found {
   205  		r.Stats.IncDiskGetMisses()
   206  		return nil
   207  	}
   208  	if uid.IsNil() {
   209  		m.Warning("nil UID found in disk cache")
   210  		return nil
   211  	}
   212  	r.Stats.IncDiskGetHits()
   213  	return &ResolveResult{uid: uid}
   214  }
   215  
   216  func isMutable(au AssertionURL) bool {
   217  	isStatic := au.IsUID() ||
   218  		au.IsKeybase() ||
   219  		(au.IsTeamID() && !au.ToTeamID().IsSubTeam()) ||
   220  		(au.IsTeamName() && au.ToTeamName().IsRootTeam())
   221  	return !isStatic
   222  }
   223  
   224  func (r *ResolverImpl) getFromUPAKLoader(m MetaContext, uid keybase1.UID) (ret *ResolveResult) {
   225  	nun, err := m.G().GetUPAKLoader().LookupUsername(m.Ctx(), uid)
   226  	if err != nil {
   227  		return nil
   228  	}
   229  	return &ResolveResult{uid: uid, queriedByUID: true, resolvedKbUsername: nun.String(), mutable: false}
   230  }
   231  
   232  func (r *ResolverImpl) resolveURL(m MetaContext, au AssertionURL, input string, withBody bool, needUsername bool) (res ResolveResult) {
   233  	ck := au.CacheKey()
   234  
   235  	lock := r.locktab.AcquireOnName(m.Ctx(), m.G(), ck)
   236  	defer lock.Release(m.Ctx())
   237  
   238  	// Debug succinctly what happened in the resolution
   239  	var trace string
   240  	defer func() {
   241  		m.Debug("| Resolver#resolveURL(%s) -> %s [trace:%s]", ck, res, trace)
   242  	}()
   243  
   244  	// A standard keybase UID, so it's already resolved... unless we explicitly
   245  	// need it!
   246  	if !needUsername {
   247  		if tmp := au.ToUID(); tmp.Exists() {
   248  			trace += "u"
   249  			return ResolveResult{uid: tmp}
   250  		}
   251  	}
   252  
   253  	if p := r.getFromMemCache(m, ck, au); p != nil && (!needUsername || len(p.resolvedKbUsername) > 0 || !p.resolvedTeamName.IsNil()) {
   254  		trace += "m"
   255  		ret := *p
   256  		ret.decorate(au)
   257  		return ret
   258  	}
   259  
   260  	if p := r.getFromDiskCache(m, ck, au); p != nil && (!needUsername || len(p.resolvedKbUsername) > 0 || !p.resolvedTeamName.IsNil()) {
   261  		p.mutable = isMutable(au)
   262  		r.putToMemCache(m, ck, *p)
   263  		trace += "d"
   264  		ret := *p
   265  		ret.decorate(au)
   266  		return ret
   267  	}
   268  
   269  	// We can check the UPAK loader for the username if we're just mapping a UID to a username.
   270  	if tmp := au.ToUID(); !withBody && tmp.Exists() {
   271  		if p := r.getFromUPAKLoader(m, tmp); p != nil {
   272  			trace += "l"
   273  			r.putToMemCache(m, ck, *p)
   274  			return *p
   275  		}
   276  	}
   277  
   278  	trace += "s"
   279  	res = r.resolveURLViaServerLookup(m, au, input, withBody)
   280  
   281  	// Cache for a shorter period of time if it's not a Keybase identity
   282  	res.mutable = isMutable(au)
   283  	r.putToMemCache(m, ck, res)
   284  
   285  	// We only put to disk cache if it's a Keybase-type assertion. In
   286  	// particular, UIDs are **not** stored to disk.
   287  	if au.IsKeybase() {
   288  		trace += "p"
   289  		r.putToDiskCache(m, ck, res)
   290  	}
   291  
   292  	return res
   293  }
   294  
   295  func (res *ResolveResult) decorate(au AssertionURL) {
   296  	if au.IsKeybase() {
   297  		res.queriedKbUsername = au.GetValue()
   298  	} else if au.IsUID() {
   299  		res.queriedByUID = true
   300  	}
   301  }
   302  
   303  func (r *ResolverImpl) resolveURLViaServerLookup(m MetaContext, au AssertionURL, input string, withBody bool) (res ResolveResult) {
   304  	defer m.VTrace(VLog1, fmt.Sprintf("Resolver#resolveURLViaServerLookup(input = %q)", input), &res.err)()
   305  
   306  	if au.IsTeamID() || au.IsTeamName() {
   307  		return r.resolveTeamViaServerLookup(m, au)
   308  	}
   309  
   310  	if au.IsServerTrust() {
   311  		return r.resolveServerTrustAssertion(m, au, input)
   312  	}
   313  
   314  	var key, val string
   315  	var ares *APIRes
   316  	var l int
   317  
   318  	res.decorate(au)
   319  
   320  	if key, val, res.err = au.ToLookup(); res.err != nil {
   321  		return
   322  	}
   323  
   324  	ha := HTTPArgsFromKeyValuePair(key, S{val})
   325  	ha.Add("multi", I{1})
   326  	ha.Add("load_deleted_v2", B{true})
   327  	fields := "basics"
   328  	if withBody {
   329  		fields += ",public_keys,pictures"
   330  	}
   331  	ha.Add("fields", S{fields})
   332  	ares, res.err = m.G().API.Get(m, APIArg{
   333  		Endpoint:        "user/lookup",
   334  		SessionType:     APISessionTypeNONE,
   335  		Args:            ha,
   336  		AppStatusCodes:  []int{SCOk, SCNotFound, SCDeleted},
   337  		RetryCount:      3,
   338  		InitialTimeout:  4 * time.Second,
   339  		RetryMultiplier: 1.5,
   340  	})
   341  
   342  	if res.err != nil {
   343  		m.Debug("API user/lookup %q error: %s", input, res.err)
   344  		return
   345  	}
   346  	switch ares.AppStatus.Code {
   347  	case SCNotFound:
   348  		m.Debug("API user/lookup %q not found", input)
   349  		res.err = NotFoundError{}
   350  		return
   351  	default:
   352  		// Nothing to do for other codes.
   353  	}
   354  
   355  	var them *jsonw.Wrapper
   356  	if them, res.err = ares.Body.AtKey("them").ToArray(); res.err != nil {
   357  		return res
   358  	}
   359  
   360  	if l, res.err = them.Len(); res.err != nil {
   361  		return res
   362  	}
   363  
   364  	if l == 0 {
   365  		res.err = ResolutionError{Input: input, Msg: "No resolution found", Kind: ResolutionErrorNotFound}
   366  		return res
   367  	}
   368  	if l > 1 {
   369  		res.err = ResolutionError{Input: input, Msg: "Identify is ambiguous", Kind: ResolutionErrorAmbiguous}
   370  		return res
   371  	}
   372  	res.body = them.AtIndex(0)
   373  	res.uid, res.err = GetUID(res.body.AtKey("id"))
   374  	if res.err != nil {
   375  		return res
   376  	}
   377  	res.resolvedKbUsername, res.err = res.body.AtPath("basics.username").GetString()
   378  	if res.err != nil {
   379  		return res
   380  	}
   381  	var status int
   382  	status, res.err = res.body.AtPath("basics.status").GetInt()
   383  	if res.err != nil {
   384  		return res
   385  	}
   386  	if status == SCDeleted {
   387  		res.deleted = true
   388  	}
   389  
   390  	return res
   391  }
   392  
   393  type teamLookup struct {
   394  	ID     keybase1.TeamID   `json:"id"`
   395  	Name   keybase1.TeamName `json:"name"`
   396  	Status AppStatus         `json:"status"`
   397  }
   398  
   399  func (t *teamLookup) GetAppStatus() *AppStatus {
   400  	return &t.Status
   401  }
   402  
   403  func (r *ResolverImpl) resolveTeamViaServerLookup(m MetaContext, au AssertionURL) (res ResolveResult) {
   404  	m.Debug("resolveTeamViaServerLookup")
   405  
   406  	res.queriedByTeamID = au.IsTeamID()
   407  	key, val, err := au.ToLookup()
   408  	if err != nil {
   409  		res.err = err
   410  		return res
   411  	}
   412  
   413  	arg := NewAPIArg("team/get")
   414  	arg.SessionType = APISessionTypeREQUIRED
   415  	arg.Args = make(HTTPArgs)
   416  	arg.Args[key] = S{Val: val}
   417  	arg.Args["lookup_only"] = B{Val: true}
   418  	if res.queriedByTeamID && au.ToTeamID().IsPublic() {
   419  		arg.Args["public"] = B{Val: true}
   420  	}
   421  
   422  	var lookup teamLookup
   423  	if err := m.G().API.GetDecode(m, arg, &lookup); err != nil {
   424  		res.err = err
   425  		return res
   426  	}
   427  
   428  	res.resolvedTeamName = lookup.Name
   429  	res.teamID = lookup.ID
   430  
   431  	return res
   432  }
   433  
   434  type serverTrustUserLookup struct {
   435  	AppStatusEmbed
   436  	User *keybase1.PhoneLookupResult `json:"user"`
   437  }
   438  
   439  func (r *ResolverImpl) resolveServerTrustAssertion(m MetaContext, au AssertionURL, input string) (res ResolveResult) {
   440  	defer m.Trace(fmt.Sprintf("Resolver#resolveServerTrustAssertion(%q, %q)", au.String(), input), &res.err)()
   441  
   442  	key, val, err := au.ToLookup()
   443  	if err != nil {
   444  		res.err = err
   445  		return res
   446  	}
   447  
   448  	var arg APIArg
   449  	switch key {
   450  	case "phone":
   451  		arg = NewAPIArg("user/phone_numbers_search")
   452  		arg.Args = map[string]HTTPValue{"phone_number": S{Val: val}}
   453  	case "email":
   454  		arg = NewAPIArg("email/search")
   455  		arg.Args = map[string]HTTPValue{"email": S{Val: val}}
   456  	default:
   457  		res.err = ResolutionError{Input: input, Msg: fmt.Sprintf("Unexpected assertion: %q for server trust lookup", key), Kind: ResolutionErrorInvalidInput}
   458  		return res
   459  	}
   460  
   461  	arg.SessionType = APISessionTypeREQUIRED
   462  	arg.AppStatusCodes = []int{SCOk}
   463  
   464  	var lookup serverTrustUserLookup
   465  	if err := m.G().API.GetDecode(m, arg, &lookup); err != nil {
   466  		if appErr, ok := err.(AppStatusError); ok {
   467  			switch appErr.Code {
   468  			case SCInputError:
   469  				res.err = ResolutionError{Input: input, Msg: err.Error(), Kind: ResolutionErrorInvalidInput}
   470  				return res
   471  			case SCRateLimit:
   472  				res.err = ResolutionError{Input: input, Msg: err.Error(), Kind: ResolutionErrorRateLimited}
   473  				return res
   474  			}
   475  		}
   476  		// When the call fails because of timeout or other reason, stop
   477  		// the process as well. Same reason as other errors - we don't
   478  		// want to create dead SBS team when there was a resolvable user
   479  		// but we weren't able to resolve.
   480  		res.err = ResolutionError{Input: input, Msg: err.Error(), Kind: ResolutionErrorRequestFailed}
   481  		return res
   482  	}
   483  
   484  	if lookup.User == nil {
   485  		res.err = ResolutionError{Input: input, Msg: "No resolution found", Kind: ResolutionErrorNotFound}
   486  		return res
   487  	}
   488  
   489  	user := *lookup.User
   490  	res.resolvedKbUsername = user.Username
   491  	res.uid = user.Uid
   492  	res.isServerTrust = true
   493  	// Mutable resolutions are not cached to disk. We can't be aggressive when
   494  	// caching server-trust resolutions, because when client pulls out one from
   495  	// cache, they have no way to verify it's still valid. From the server-side
   496  	// we have no way to invalidate that cache.
   497  	res.mutable = true
   498  
   499  	return res
   500  }
   501  
   502  type ResolveCacheStats struct {
   503  	sync.Mutex
   504  	misses          int
   505  	timeouts        int
   506  	mutableTimeouts int
   507  	errorTimeouts   int
   508  	hits            int
   509  	diskGets        int
   510  	diskGetHits     int
   511  	diskGetMisses   int
   512  	diskPuts        int
   513  }
   514  
   515  type ResolverImpl struct {
   516  	cache   *ramcache.Ramcache
   517  	Stats   *ResolveCacheStats
   518  	locktab *LockTable
   519  }
   520  
   521  func (s *ResolveCacheStats) Eq(m, t, mt, et, h int) bool {
   522  	s.Lock()
   523  	defer s.Unlock()
   524  	return (s.misses == m) && (s.timeouts == t) && (s.mutableTimeouts == mt) && (s.errorTimeouts == et) && (s.hits == h)
   525  }
   526  
   527  func (s *ResolveCacheStats) EqWithDiskHits(m, t, mt, et, h, dh int) bool {
   528  	s.Lock()
   529  	defer s.Unlock()
   530  	return (s.misses == m) && (s.timeouts == t) && (s.mutableTimeouts == mt) && (s.errorTimeouts == et) && (s.hits == h) && (s.diskGetHits == dh)
   531  }
   532  
   533  func (s *ResolveCacheStats) IncMisses() {
   534  	s.Lock()
   535  	s.misses++
   536  	s.Unlock()
   537  }
   538  
   539  func (s *ResolveCacheStats) IncTimeouts() {
   540  	s.Lock()
   541  	s.timeouts++
   542  	s.Unlock()
   543  }
   544  
   545  func (s *ResolveCacheStats) IncMutableTimeouts() {
   546  	s.Lock()
   547  	s.mutableTimeouts++
   548  	s.Unlock()
   549  }
   550  
   551  func (s *ResolveCacheStats) IncErrorTimeouts() {
   552  	s.Lock()
   553  	s.errorTimeouts++
   554  	s.Unlock()
   555  }
   556  
   557  func (s *ResolveCacheStats) IncHits() {
   558  	s.Lock()
   559  	s.hits++
   560  	s.Unlock()
   561  }
   562  
   563  func (s *ResolveCacheStats) IncDiskGets() {
   564  	s.Lock()
   565  	s.diskGets++
   566  	s.Unlock()
   567  }
   568  
   569  func (s *ResolveCacheStats) IncDiskGetHits() {
   570  	s.Lock()
   571  	s.diskGetHits++
   572  	s.Unlock()
   573  }
   574  
   575  func (s *ResolveCacheStats) IncDiskGetMisses() {
   576  	s.Lock()
   577  	s.diskGetMisses++
   578  	s.Unlock()
   579  }
   580  
   581  func (s *ResolveCacheStats) IncDiskPuts() {
   582  	s.Lock()
   583  	s.diskPuts++
   584  	s.Unlock()
   585  }
   586  
   587  func NewResolverImpl() *ResolverImpl {
   588  	return &ResolverImpl{
   589  		cache:   nil,
   590  		locktab: NewLockTable(),
   591  		Stats:   &ResolveCacheStats{},
   592  	}
   593  }
   594  
   595  func (r *ResolverImpl) EnableCaching(m MetaContext) {
   596  	cache := ramcache.New()
   597  	cache.MaxAge = ResolveCacheMaxAge
   598  	cache.TTL = resolveCacheTTL
   599  	r.cache = cache
   600  }
   601  
   602  func (r *ResolverImpl) Shutdown(m MetaContext) {
   603  	if r.cache == nil {
   604  		return
   605  	}
   606  	r.cache.Shutdown()
   607  }
   608  
   609  func (r *ResolverImpl) getFromMemCache(m MetaContext, key string, au AssertionURL) (ret *ResolveResult) {
   610  	defer m.VTrace(VLog1, fmt.Sprintf("Resolver#getFromMemCache(%q)", key), nil)()
   611  	if r.cache == nil {
   612  		return nil
   613  	}
   614  	res, _ := r.cache.Get(key)
   615  	if res == nil {
   616  		r.Stats.IncMisses()
   617  		return nil
   618  	}
   619  	rres, ok := res.(*ResolveResult)
   620  	if !ok {
   621  		r.Stats.IncMisses()
   622  		return nil
   623  	}
   624  	// Should never happen, but don't corrupt application state if it does
   625  	if !rres.HasPrimaryKey() {
   626  		m.Info("Resolver#getFromMemCache: nil UID/teamID in cache")
   627  		return nil
   628  	}
   629  	now := m.G().Clock().Now()
   630  	if now.Sub(rres.cachedAt) > ResolveCacheMaxAge {
   631  		r.Stats.IncTimeouts()
   632  		return nil
   633  	}
   634  	if rres.mutable && now.Sub(rres.cachedAt) > ResolveCacheMaxAgeMutable {
   635  		r.Stats.IncMutableTimeouts()
   636  		return nil
   637  	}
   638  	if rres.err != nil && now.Sub(rres.cachedAt) > resolveCacheMaxAgeErrored {
   639  		r.Stats.IncErrorTimeouts()
   640  		return nil
   641  	}
   642  	r.Stats.IncHits()
   643  	rres.addKeybaseNameIfKnown(au)
   644  	return rres
   645  }
   646  
   647  func resolveDbKey(key string) DbKey {
   648  	return DbKey{
   649  		Typ: DBResolveUsernameToUID,
   650  		Key: NewNormalizedUsername(key).String(),
   651  	}
   652  }
   653  
   654  func (r *ResolverImpl) putToDiskCache(m MetaContext, key string, res ResolveResult) {
   655  	m.VLogf(VLog1, "| Resolver#putToDiskCache (attempt) %+v", res)
   656  	// Only cache immutable resolutions to disk
   657  	if res.mutable {
   658  		return
   659  	}
   660  	// Don't cache errors or deleted users
   661  	if res.err != nil || res.deleted {
   662  		return
   663  	}
   664  	if res.uid.IsNil() {
   665  		m.Warning("Mistaken UID put to disk cache")
   666  		if m.G().Env.GetDebug() {
   667  			debug.PrintStack()
   668  		}
   669  		return
   670  	}
   671  	r.Stats.IncDiskPuts()
   672  	if err := m.G().LocalDb.PutObj(resolveDbKey(key), nil, res.uid); err != nil {
   673  		m.Warning("Cannot put resolve result to disk: %s", err)
   674  		return
   675  	}
   676  	m.Debug("| Resolver#putToDiskCache(%s) -> %v", key, res)
   677  }
   678  
   679  // Put receives a copy of a ResolveResult, clears out the body
   680  // to avoid caching data that can go stale, and stores the result.
   681  func (r *ResolverImpl) putToMemCache(m MetaContext, key string, res ResolveResult) {
   682  	if r.cache == nil {
   683  		return
   684  	}
   685  	// Don't cache errors or deleted users
   686  	if res.err != nil || res.deleted {
   687  		return
   688  	}
   689  	if !res.HasPrimaryKey() {
   690  		m.Warning("Mistaken UID put to mem cache")
   691  		if m.G().Env.GetDebug() {
   692  			debug.PrintStack()
   693  		}
   694  		return
   695  	}
   696  	res.cachedAt = m.G().Clock().Now()
   697  	res.body = nil // Don't cache body
   698  	_ = r.cache.Set(key, &res)
   699  }
   700  
   701  func (r *ResolverImpl) CacheTeamResolution(m MetaContext, id keybase1.TeamID, name keybase1.TeamName) {
   702  	m.VLogf(VLog0, "ResolverImpl#CacheTeamResolution: %s <-> %s", id, name)
   703  	res := ResolveResult{
   704  		teamID:           id,
   705  		queriedByTeamID:  true,
   706  		resolvedTeamName: name,
   707  		mutable:          id.IsSubTeam(),
   708  	}
   709  	r.putToMemCache(m, fmt.Sprintf("tid:%s", id), res)
   710  	res.queriedByTeamID = false
   711  	r.putToMemCache(m, fmt.Sprintf("team:%s", name.String()), res)
   712  }
   713  
   714  func (r *ResolverImpl) PurgeResolveCache(m MetaContext, input string) (err error) {
   715  	defer m.Trace(fmt.Sprintf("Resolver#PurgeResolveCache(input = %q)", input), &err)()
   716  	expr, err := AssertionParseAndOnly(m.G().MakeAssertionContext(m), input)
   717  	if err != nil {
   718  		return err
   719  	}
   720  	u := FindBestIdentifyComponentURL(expr)
   721  	if u == nil {
   722  		return ResolutionError{Input: input, Msg: "Cannot find a resolvable factor"}
   723  	}
   724  
   725  	key := u.CacheKey()
   726  	err = r.cache.Delete(key)
   727  	if err != nil {
   728  		return err
   729  	}
   730  	// Since we only put to disk cache if it's a Keybase-type assertion, we
   731  	// only remove it in this case as well.
   732  	if u.IsKeybase() {
   733  		if err := m.G().LocalDb.Delete(resolveDbKey(key)); err != nil {
   734  			m.Warning("Cannot remove resolve result from disk: %s", err)
   735  			return err
   736  		}
   737  	}
   738  	return nil
   739  }
   740  
   741  var _ Resolver = (*ResolverImpl)(nil)