github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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)