github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/service/user.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 service 5 6 import ( 7 "fmt" 8 "sort" 9 "time" 10 11 "github.com/keybase/client/go/protocol/gregor1" 12 13 "github.com/keybase/client/go/uidmap" 14 15 "github.com/keybase/client/go/avatars" 16 "github.com/keybase/client/go/chat" 17 "github.com/keybase/client/go/chat/globals" 18 "github.com/keybase/client/go/chat/utils" 19 "github.com/keybase/client/go/engine" 20 "github.com/keybase/client/go/libkb" 21 "github.com/keybase/client/go/offline" 22 "github.com/keybase/client/go/phonenumbers" 23 "github.com/keybase/client/go/profiling" 24 keybase1 "github.com/keybase/client/go/protocol/keybase1" 25 "github.com/keybase/go-framed-msgpack-rpc/rpc" 26 "golang.org/x/net/context" 27 ) 28 29 // UserHandler is the RPC handler for the user interface. 30 type UserHandler struct { 31 *BaseHandler 32 libkb.Contextified 33 globals.ChatContextified 34 service *Service 35 } 36 37 // NewUserHandler creates a UserHandler for the xp transport. 38 func NewUserHandler(xp rpc.Transporter, g *libkb.GlobalContext, chatG *globals.ChatContext, s *Service) *UserHandler { 39 return &UserHandler{ 40 BaseHandler: NewBaseHandler(g, xp), 41 Contextified: libkb.NewContextified(g), 42 ChatContextified: globals.NewChatContextified(chatG), 43 service: s, 44 } 45 } 46 47 func (h *UserHandler) ListTracking(ctx context.Context, arg keybase1.ListTrackingArg) (ret keybase1.UserSummarySet, err error) { 48 eng := engine.NewListTrackingEngine(h.G(), &engine.ListTrackingEngineArg{ 49 Filter: arg.Filter, 50 Assertion: arg.Assertion, 51 // Verbose has no effect on this call. At the engine level, it only 52 // affects JSON output. 53 }) 54 m := libkb.NewMetaContext(ctx, h.G()) 55 err = engine.RunEngine2(m, eng) 56 if err != nil { 57 return ret, err 58 } 59 return eng.TableResult(), nil 60 } 61 62 func (h *UserHandler) ListTrackingJSON(ctx context.Context, arg keybase1.ListTrackingJSONArg) (res string, err error) { 63 eng := engine.NewListTrackingEngine(h.G(), &engine.ListTrackingEngineArg{ 64 JSON: true, 65 Filter: arg.Filter, 66 Verbose: arg.Verbose, 67 Assertion: arg.Assertion, 68 }) 69 m := libkb.NewMetaContext(ctx, h.G()) 70 err = engine.RunEngine2(m, eng) 71 if err != nil { 72 return res, err 73 } 74 return eng.JSONResult(), nil 75 } 76 77 func (h *UserHandler) ListTrackersUnverified(ctx context.Context, arg keybase1.ListTrackersUnverifiedArg) (res keybase1.UserSummarySet, err error) { 78 m := libkb.NewMetaContext(ctx, h.G()) 79 defer m.Trace(fmt.Sprintf("ListTrackersUnverified(assertion=%s)", arg.Assertion), &err)() 80 eng := engine.NewListTrackersUnverifiedEngine(h.G(), engine.ListTrackersUnverifiedEngineArg{Assertion: arg.Assertion}) 81 uis := libkb.UIs{ 82 LogUI: h.getLogUI(arg.SessionID), 83 SessionID: arg.SessionID, 84 } 85 m = m.WithUIs(uis) 86 err = engine.RunEngine2(m, eng) 87 if err == nil { 88 res = eng.GetResults() 89 } 90 return res, err 91 } 92 93 func (h *UserHandler) LoadUser(ctx context.Context, arg keybase1.LoadUserArg) (user keybase1.User, err error) { 94 loadUserArg := libkb.NewLoadUserByUIDArg(ctx, h.G(), arg.Uid).WithPublicKeyOptional() 95 u, err := libkb.LoadUser(loadUserArg) 96 if err != nil { 97 return 98 } 99 exportedUser := u.Export() 100 user = *exportedUser 101 return 102 } 103 104 func (h *UserHandler) LoadUserByName(_ context.Context, arg keybase1.LoadUserByNameArg) (user keybase1.User, err error) { 105 loadUserArg := libkb.NewLoadUserByNameArg(h.G(), arg.Username).WithPublicKeyOptional() 106 u, err := libkb.LoadUser(loadUserArg) 107 if err != nil { 108 return 109 } 110 exportedUser := u.Export() 111 user = *exportedUser 112 return 113 } 114 115 func (h *UserHandler) LoadUserPlusKeysV2(ctx context.Context, arg keybase1.LoadUserPlusKeysV2Arg) (ret keybase1.UserPlusKeysV2AllIncarnations, err error) { 116 mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("LUPK2") 117 defer mctx.Trace(fmt.Sprintf("UserHandler#LoadUserPlusKeysV2(%+v)", arg), &err)() 118 119 cacheArg := keybase1.LoadUserPlusKeysV2Arg{ 120 Uid: arg.Uid, 121 } 122 123 retp := &ret 124 servedRet, err := h.service.offlineRPCCache.Serve(mctx, arg.Oa, offline.Version(1), "user.loadUserPlusKeysV2", false, cacheArg, &retp, func(mctx libkb.MetaContext) (interface{}, error) { 125 return h.G().GetUPAKLoader().LoadV2WithKID(mctx.Ctx(), arg.Uid, arg.PollForKID) 126 }) 127 if s, ok := servedRet.(*keybase1.UserPlusKeysV2AllIncarnations); ok && s != nil { 128 // Even if err != nil, the caller might still be expecting 129 // data, so use the return value if there is one. 130 ret = *s 131 } else if err != nil { 132 ret = keybase1.UserPlusKeysV2AllIncarnations{} 133 } 134 return ret, err 135 } 136 137 func (h *UserHandler) LoadUserPlusKeys(netCtx context.Context, arg keybase1.LoadUserPlusKeysArg) (keybase1.UserPlusKeys, error) { 138 netCtx = libkb.WithLogTag(netCtx, "LUPK") 139 h.G().Log.CDebugf(netCtx, "+ UserHandler#LoadUserPlusKeys(%+v)", arg) 140 ret, err := libkb.LoadUserPlusKeys(netCtx, h.G(), arg.Uid, arg.PollForKID) 141 142 // for debugging purposes, output the returned KIDs (since this can be racy) 143 var kids []keybase1.KID 144 for _, key := range ret.DeviceKeys { 145 if !key.IsSibkey && key.PGPFingerprint == "" { 146 kids = append(kids, key.KID) 147 } 148 } 149 150 if err == nil { 151 // ret.Status might indicate an error we should return 152 // (like libkb.UserDeletedError, for example) 153 err = libkb.UserErrorFromStatus(ret.Status) 154 if err != nil { 155 h.G().Log.CDebugf(netCtx, "using error from StatusCode: %v => %s", ret.Status, err) 156 } 157 } 158 159 h.G().Log.CDebugf(netCtx, "- UserHandler#LoadUserPlusKeys(%+v) -> (UVV=%+v, KIDs=%v, err=%s)", arg, ret.Uvv, kids, libkb.ErrToOk(err)) 160 return ret, err 161 } 162 163 func (h *UserHandler) LoadMySettings(ctx context.Context, sessionID int) (res keybase1.UserSettings, err error) { 164 mctx := libkb.NewMetaContext(ctx, h.G()) 165 emails, err := libkb.LoadUserEmails(mctx) 166 if err != nil { 167 return res, err 168 } 169 phoneNumbers, err := phonenumbers.GetPhoneNumbers(mctx) 170 if err != nil { 171 switch err.(type) { 172 case libkb.FeatureFlagError: 173 mctx.Debug("PhoneNumbers feature not enabled - phone number list will be empty") 174 default: 175 return res, err 176 } 177 } 178 res.Emails = emails 179 res.PhoneNumbers = phoneNumbers 180 return res, nil 181 } 182 183 func (h *UserHandler) LoadPublicKeys(ctx context.Context, arg keybase1.LoadPublicKeysArg) (keys []keybase1.PublicKey, err error) { 184 larg := libkb.NewLoadUserArg(h.G()).WithUID(arg.Uid) 185 return h.loadPublicKeys(ctx, larg) 186 } 187 188 func (h *UserHandler) LoadMyPublicKeys(ctx context.Context, sessionID int) (keys []keybase1.PublicKey, err error) { 189 larg := libkb.NewLoadUserArg(h.G()).WithSelf(true) 190 return h.loadPublicKeys(ctx, larg) 191 } 192 193 func (h *UserHandler) loadPublicKeys(ctx context.Context, larg libkb.LoadUserArg) (keys []keybase1.PublicKey, err error) { 194 u, err := libkb.LoadUser(larg) 195 if err != nil { 196 return 197 } 198 var publicKeys []keybase1.PublicKey 199 if u.GetComputedKeyFamily() != nil { 200 publicKeys = u.GetComputedKeyFamily().Export() 201 } 202 return publicKeys, nil 203 } 204 205 func (h *UserHandler) LoadAllPublicKeysUnverified(ctx context.Context, 206 arg keybase1.LoadAllPublicKeysUnverifiedArg) (keys []keybase1.PublicKey, err error) { 207 208 u, err := libkb.LoadUserFromServer(libkb.NewMetaContext(ctx, h.G()), arg.Uid, nil) 209 if err != nil { 210 return 211 } 212 var publicKeys []keybase1.PublicKey 213 if u.GetKeyFamily() != nil { 214 publicKeys = u.GetKeyFamily().Export() 215 } 216 return publicKeys, nil 217 } 218 219 func (h *UserHandler) ProfileEdit(nctx context.Context, arg keybase1.ProfileEditArg) error { 220 eng := engine.NewProfileEdit(h.G(), arg) 221 m := libkb.NewMetaContext(nctx, h.G()) 222 return engine.RunEngine2(m, eng) 223 } 224 225 func (h *UserHandler) InterestingPeople(ctx context.Context, args keybase1.InterestingPeopleArg) (res []keybase1.InterestingPerson, err error) { 226 // In case someone comes from "GetInterestingPeople" command in standalone 227 // mode: 228 h.G().StartStandaloneChat() 229 230 // Chat source 231 chatFn := func(uid keybase1.UID) (kuids []keybase1.UID, err error) { 232 g := globals.NewContext(h.G(), h.ChatG()) 233 list, err := chat.RecentConversationParticipants(ctx, g, uid.ToBytes()) 234 if err != nil { 235 return nil, err 236 } 237 for _, guid := range list { 238 kuids = append(kuids, keybase1.UID(guid.String())) 239 } 240 return kuids, nil 241 } 242 243 // Following source 244 followingFn := func(uid keybase1.UID) (res []keybase1.UID, err error) { 245 var found bool 246 var tmp keybase1.UserSummarySet 247 // This is only informative, so unverified data is fine. 248 found, err = h.G().LocalDb.GetInto(&tmp, libkb.DbKeyUID(libkb.DBUnverifiedTrackersFollowing, uid)) 249 if err != nil { 250 return nil, err 251 } 252 if !found { 253 return nil, nil 254 } 255 for _, u := range tmp.Users { 256 res = append(res, u.Uid) 257 } 258 return res, nil 259 } 260 261 fallbackFn := func(uid keybase1.UID) (uids []keybase1.UID, err error) { 262 uids = []keybase1.UID{ 263 libkb.GetUIDByNormalizedUsername(h.G(), "hellobot"), 264 } 265 return uids, nil 266 } 267 268 ip := newInterestingPeople(h.G()) 269 270 // Add sources of interesting people 271 ip.AddSource(chatFn, 0.7) 272 ip.AddSource(followingFn, 0.2) 273 274 // We filter out the fallback recommendations when actually building a team 275 if args.Namespace != "teams" { 276 // The most interesting person of all... you 277 you := keybase1.InterestingPerson{ 278 Uid: h.G().GetEnv().GetUID(), 279 Username: h.G().GetEnv().GetUsername().String(), 280 } 281 res = append(res, you) 282 283 // add hellobot as a fallback recommendation if you don't have many others 284 ip.AddSource(fallbackFn, 0.1) 285 } 286 287 uids, err := ip.Get(ctx, args.MaxUsers) 288 if err != nil { 289 h.G().Log.Debug("InterestingPeople: failed to get list: %s", err.Error()) 290 return nil, err 291 } 292 293 if len(uids) == 0 { 294 h.G().Log.Debug("InterestingPeople: there are no interesting people for current user") 295 return []keybase1.InterestingPerson{}, nil 296 } 297 298 const fullnameFreshness = 0 // never stale 299 packages, err := h.G().UIDMapper.MapUIDsToUsernamePackagesOffline(ctx, h.G(), uids, fullnameFreshness) 300 if err != nil { 301 h.G().Log.Debug("InterestingPeople: failed in UIDMapper: %s, but continuing", err.Error()) 302 } 303 304 const serviceMapFreshness = 24 * time.Hour 305 serviceMaps := h.G().ServiceMapper.MapUIDsToServiceSummaries(ctx, h.G(), uids, 306 serviceMapFreshness, uidmap.DisallowNetworkBudget) 307 308 for i, uid := range uids { 309 if packages[i].NormalizedUsername.IsNil() { 310 // We asked UIDMapper for cached data only, this username was missing. 311 h.G().Log.Debug("InterestingPeople: failed to get username for: %s", uid) 312 continue 313 } 314 ret := keybase1.InterestingPerson{ 315 Uid: uid, 316 Username: packages[i].NormalizedUsername.String(), 317 } 318 if fn := packages[i].FullName; fn != nil { 319 ret.Fullname = fn.FullName.String() 320 } 321 if smap, found := serviceMaps[uid]; found { 322 ret.ServiceMap = smap.ServiceMap 323 } 324 res = append(res, ret) 325 } 326 return res, nil 327 } 328 329 func (h *UserHandler) MeUserVersion(ctx context.Context, arg keybase1.MeUserVersionArg) (res keybase1.UserVersion, err error) { 330 loadMeArg := libkb.NewLoadUserArg(h.G()). 331 WithNetContext(ctx). 332 WithUID(h.G().Env.GetUID()). 333 WithSelf(true). 334 WithForcePoll(arg.ForcePoll). 335 WithPublicKeyOptional() 336 upak, _, err := h.G().GetUPAKLoader().LoadV2(loadMeArg) 337 if err != nil { 338 return keybase1.UserVersion{}, err 339 } 340 if upak == nil { 341 return keybase1.UserVersion{}, fmt.Errorf("could not load self upak") 342 } 343 return upak.Current.ToUserVersion(), nil 344 } 345 346 func (h *UserHandler) GetUPAK(ctx context.Context, arg keybase1.GetUPAKArg) (ret keybase1.UPAKVersioned, err error) { 347 stubMode := libkb.StubModeFromUnstubbedBool(arg.Unstubbed) 348 larg := libkb.NewLoadUserArg(h.G()).WithNetContext(ctx).WithUID(arg.Uid).WithPublicKeyOptional().WithStubMode(stubMode) 349 350 upak, _, err := h.G().GetUPAKLoader().LoadV2(larg) 351 if err != nil { 352 return ret, err 353 } 354 if upak == nil { 355 return ret, libkb.UserNotFoundError{UID: arg.Uid, Msg: "upak load failed"} 356 } 357 ret = keybase1.NewUPAKVersionedWithV2(*upak) 358 return ret, err 359 } 360 361 func (h *UserHandler) GetUPAKLite(ctx context.Context, uid keybase1.UID) (ret keybase1.UPKLiteV1AllIncarnations, err error) { 362 arg := libkb.NewLoadUserArg(h.G()).WithNetContext(ctx).WithUID(uid).WithPublicKeyOptional().ForUPAKLite() 363 upakLite, err := h.G().GetUPAKLoader().LoadLite(arg) 364 if err != nil { 365 return ret, err 366 } 367 if upakLite == nil { 368 return ret, libkb.UserNotFoundError{UID: uid, Msg: "upak load failed"} 369 } 370 ret = *upakLite 371 return ret, nil 372 } 373 374 func (h *UserHandler) UploadUserAvatar(ctx context.Context, arg keybase1.UploadUserAvatarArg) (err error) { 375 ctx = libkb.WithLogTag(ctx, "US") 376 defer h.G().CTrace(ctx, fmt.Sprintf("UploadUserAvatar(%s)", arg.Filename), &err)() 377 378 mctx := libkb.NewMetaContext(ctx, h.G()) 379 if err := avatars.UploadImage(mctx, arg.Filename, nil /* teamname */, arg.Crop); err != nil { 380 return err 381 } 382 return h.G().GetAvatarLoader().ClearCacheForName(mctx, h.G().Env.GetUsername().String(), avatars.AllFormats) 383 } 384 385 func (h *UserHandler) ProofSuggestions(ctx context.Context, sessionID int) (ret keybase1.ProofSuggestionsRes, err error) { 386 mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("US") 387 defer mctx.Trace("ProofSuggestions", &err)() 388 tracer := mctx.G().CTimeTracer(mctx.Ctx(), "ProofSuggestions", libkb.ProfileProofSuggestions) 389 defer tracer.Finish() 390 suggestions, err := h.proofSuggestionsHelper(mctx, tracer) 391 if err != nil { 392 return ret, err 393 } 394 tracer.Stage("fold-pri") 395 foldPriority := mctx.G().GetProofServices().SuggestionFoldPriority(h.MetaContext(ctx)) 396 tracer.Stage("fold-loop") 397 for _, suggestion := range suggestions { 398 if foldPriority > 0 && suggestion.Priority >= foldPriority { 399 ret.ShowMore = true 400 suggestion.BelowFold = true 401 } 402 ret.Suggestions = append(ret.Suggestions, suggestion.ProofSuggestion) 403 } 404 return ret, nil 405 } 406 407 type ProofSuggestion struct { 408 keybase1.ProofSuggestion 409 LogoKey string 410 Priority int 411 } 412 413 var pgpProofSuggestion = ProofSuggestion{ 414 ProofSuggestion: keybase1.ProofSuggestion{ 415 Key: "pgp", 416 ProfileText: "Add a PGP key", 417 PickerText: "PGP key", 418 PickerSubtext: "", 419 }, 420 LogoKey: "pgp", 421 } 422 423 var webProofSuggestion = ProofSuggestion{ 424 ProofSuggestion: keybase1.ProofSuggestion{ 425 Key: "web", 426 ProfileText: "Prove your website", 427 PickerText: "Your own website", 428 PickerSubtext: "", 429 }, 430 LogoKey: "web", 431 } 432 433 var bitcoinProofSuggestion = ProofSuggestion{ 434 ProofSuggestion: keybase1.ProofSuggestion{ 435 Key: "btc", 436 ProfileText: "Set a Bitcoin address", 437 PickerText: "Bitcoin address", 438 PickerSubtext: "", 439 }, 440 LogoKey: "btc", 441 } 442 443 var zcashProofSuggestion = ProofSuggestion{ 444 ProofSuggestion: keybase1.ProofSuggestion{ 445 Key: "zcash", 446 ProfileText: "Set a Zcash address", 447 PickerText: "Zcash address", 448 PickerSubtext: "", 449 }, 450 LogoKey: "zcash", 451 } 452 453 func (h *UserHandler) proofSuggestionsHelper(mctx libkb.MetaContext, tracer profiling.TimeTracer) (ret []ProofSuggestion, err error) { 454 user, err := libkb.LoadMe(libkb.NewLoadUserArgWithMetaContext(mctx).WithPublicKeyOptional()) 455 if err != nil { 456 return ret, err 457 } 458 if user == nil || user.IDTable() == nil { 459 return ret, fmt.Errorf("could not load logged-in user") 460 } 461 462 tracer.Stage("get_list") 463 var suggestions []ProofSuggestion 464 serviceKeys := mctx.G().GetProofServices().ListServicesThatAcceptNewProofs(mctx) 465 tracer.Stage("loop_keys") 466 for _, service := range serviceKeys { 467 switch service { 468 case "web", "dns", "http", "https": 469 // These are under the "web" umbrella. 470 // "web" is added below. 471 continue 472 } 473 serviceType := mctx.G().GetProofServices().GetServiceType(mctx.Ctx(), service) 474 if serviceType == nil { 475 mctx.Debug("missing proof service type: %v", service) 476 continue 477 } 478 if len(user.IDTable().GetActiveProofsFor(serviceType)) > 0 { 479 mctx.Debug("user has an active proof: %v", serviceType.Key()) 480 continue 481 } 482 subtext := serviceType.DisplayGroup() 483 if len(subtext) == 0 { 484 subtext = serviceType.PickerSubtext() 485 } 486 var metas []keybase1.Identify3RowMeta 487 if serviceType.IsNew(mctx) { 488 metas = []keybase1.Identify3RowMeta{{Label: "new", Color: keybase1.Identify3RowColor_BLUE}} 489 } 490 suggestions = append(suggestions, ProofSuggestion{ 491 LogoKey: serviceType.GetLogoKey(), 492 ProofSuggestion: keybase1.ProofSuggestion{ 493 Key: service, 494 ProfileText: fmt.Sprintf("Prove your %v", serviceType.DisplayName()), 495 PickerText: serviceType.DisplayName(), 496 PickerSubtext: subtext, 497 Metas: metas, 498 }}) 499 } 500 tracer.Stage("misc") 501 hasPGP := len(user.GetActivePGPKeys(true)) > 0 502 if !hasPGP { 503 suggestions = append(suggestions, pgpProofSuggestion) 504 } 505 // Always show the option to create a new web proof. 506 suggestions = append(suggestions, webProofSuggestion) 507 if !user.IDTable().HasActiveCryptocurrencyFamily(libkb.CryptocurrencyFamilyBitcoin) { 508 suggestions = append(suggestions, bitcoinProofSuggestion) 509 } 510 if !user.IDTable().HasActiveCryptocurrencyFamily(libkb.CryptocurrencyFamilyZCash) { 511 suggestions = append(suggestions, zcashProofSuggestion) 512 } 513 514 // Attach icon urls 515 tracer.Stage("icons") 516 for i := range suggestions { 517 suggestion := &suggestions[i] 518 suggestion.ProfileIcon = libkb.MakeProofIcons(mctx, suggestion.LogoKey, libkb.ProofIconTypeSmall, 16) 519 suggestion.ProfileIconDarkmode = libkb.MakeProofIcons(mctx, suggestion.LogoKey, libkb.ProofIconTypeSmallDarkmode, 16) 520 suggestion.PickerIcon = libkb.MakeProofIcons(mctx, suggestion.LogoKey, libkb.ProofIconTypeFull, 32) 521 suggestion.PickerIconDarkmode = libkb.MakeProofIcons(mctx, suggestion.LogoKey, libkb.ProofIconTypeFullDarkmode, 32) 522 } 523 524 // Alphabetize so that ties later on in SliceStable are deterministic. 525 tracer.Stage("alphabetize") 526 sort.Slice(suggestions, func(i, j int) bool { 527 return suggestions[i].Key < suggestions[j].Key 528 }) 529 530 // Priorities from the server. 531 tracer.Stage("prioritize-server") 532 serverPriority := make(map[string]int) // key -> server priority 533 maxServerPriority := 0 534 for _, displayConfig := range mctx.G().GetProofServices().ListDisplayConfigs(mctx) { 535 if displayConfig.Priority <= 0 { 536 continue 537 } 538 var altKey string 539 switch displayConfig.Key { 540 case "zcash.t", "zcash.z", "zcash.s": 541 altKey = "zcash" 542 case "bitcoin": 543 altKey = "btc" 544 case "http", "https", "dns": 545 altKey = "web" 546 } 547 serverPriority[displayConfig.Key] = displayConfig.Priority 548 if len(altKey) > 0 { 549 if v, ok := serverPriority[altKey]; !ok || displayConfig.Priority < v { 550 serverPriority[altKey] = displayConfig.Priority 551 } 552 } 553 if displayConfig.Priority > maxServerPriority { 554 maxServerPriority = displayConfig.Priority 555 } 556 } 557 558 // Fallback priorities for rows the server missed. 559 // Fallback priorities are placed after server priorities. 560 tracer.Stage("fallback") 561 offlineOrder := []string{ 562 "twitter", 563 "github", 564 "reddit", 565 "hackernews", 566 "rooter", 567 "web", 568 "pgp", 569 "bitcoin", 570 "zcash", 571 } 572 offlineOrderMap := make(map[string]int) // key -> offline priority 573 for i, k := range offlineOrder { 574 offlineOrderMap[k] = i 575 } 576 577 tracer.Stage("prioritize-again") 578 priorityFn := func(key string) int { 579 if p, ok := serverPriority[key]; ok { 580 return p 581 } else if p, ok := offlineOrderMap[key]; ok { 582 return p + maxServerPriority + 1 583 } else { 584 return len(offlineOrderMap) + maxServerPriority 585 } 586 } 587 for i := range suggestions { 588 suggestions[i].Priority = priorityFn(suggestions[i].Key) 589 } 590 591 tracer.Stage("sort-final") 592 sort.SliceStable(suggestions, func(i, j int) bool { 593 return suggestions[i].Priority < suggestions[j].Priority 594 }) 595 return suggestions, nil 596 } 597 598 func (h *UserHandler) FindNextMerkleRootAfterRevoke(ctx context.Context, arg keybase1.FindNextMerkleRootAfterRevokeArg) (ret keybase1.NextMerkleRootRes, err error) { 599 m := libkb.NewMetaContext(ctx, h.G()) 600 m = m.WithLogTag("FNMR") 601 defer m.Trace("UserHandler#FindNextMerkleRootAfterRevoke", &err)() 602 return libkb.FindNextMerkleRootAfterRevoke(m, arg) 603 } 604 605 func (h *UserHandler) FindNextMerkleRootAfterReset(ctx context.Context, arg keybase1.FindNextMerkleRootAfterResetArg) (ret keybase1.NextMerkleRootRes, err error) { 606 m := libkb.NewMetaContext(ctx, h.G()) 607 m = m.WithLogTag("FNMR") 608 defer m.Trace("UserHandler#FindNextMerkleRootAfterReset", &err)() 609 return libkb.FindNextMerkleRootAfterReset(m, arg) 610 } 611 612 func (h *UserHandler) LoadPassphraseState(ctx context.Context, sessionID int) (res keybase1.PassphraseState, err error) { 613 m := libkb.NewMetaContext(ctx, h.G()) 614 return libkb.LoadPassphraseStateWithForceRepoll(m) 615 } 616 617 func (h *UserHandler) CanLogout(ctx context.Context, sessionID int) (res keybase1.CanLogoutRes, err error) { 618 m := libkb.NewMetaContext(ctx, h.G()) 619 res = libkb.CanLogout(m) 620 return res, nil 621 } 622 623 func (h *UserHandler) UserCard(ctx context.Context, arg keybase1.UserCardArg) (res *keybase1.UserCard, err error) { 624 mctx := libkb.NewMetaContext(ctx, h.G()) 625 defer mctx.Trace("UserHandler#UserCard", &err)() 626 627 uid := libkb.GetUIDByUsername(h.G(), arg.Username) 628 if res, err = libkb.UserCard(mctx, uid, arg.UseSession); err != nil { 629 return res, err 630 } 631 // decorate body for use in chat 632 if res != nil { 633 res.BioDecorated = utils.PresentDecoratedUserBio(ctx, res.Bio) 634 } 635 return res, nil 636 } 637 638 func (h *UserHandler) SetUserBlocks(ctx context.Context, arg keybase1.SetUserBlocksArg) (err error) { 639 mctx := libkb.NewMetaContext(ctx, h.G()) 640 eng := engine.NewUserBlocksSet(h.G(), arg) 641 uis := libkb.UIs{ 642 LogUI: h.getLogUI(arg.SessionID), 643 SessionID: arg.SessionID, 644 } 645 mctx = mctx.WithUIs(uis) 646 if err := engine.RunEngine2(mctx, eng); err != nil { 647 return err 648 } 649 h.cleanupAfterBlockChange(mctx, eng.UIDs()) 650 return nil 651 } 652 653 const blockButtonsGregorPrefix = "blockButtons." 654 655 func (h *UserHandler) DismissBlockButtons(ctx context.Context, tlfID keybase1.TLFID) (err error) { 656 mctx := libkb.NewMetaContext(ctx, h.G()) 657 defer mctx.Trace( 658 fmt.Sprintf("UserHandler#DismissBlockButtons(TLF=%s)", tlfID), 659 &err)() 660 661 return h.service.gregor.DismissCategory(ctx, gregor1.Category(fmt.Sprintf("%s%s", blockButtonsGregorPrefix, tlfID.String()))) 662 } 663 664 func (h *UserHandler) GetUserBlocks(ctx context.Context, arg keybase1.GetUserBlocksArg) (res []keybase1.UserBlock, err error) { 665 mctx := libkb.NewMetaContext(ctx, h.G()) 666 eng := engine.NewUserBlocksGet(h.G(), arg) 667 uis := libkb.UIs{ 668 LogUI: h.getLogUI(arg.SessionID), 669 SessionID: arg.SessionID, 670 } 671 mctx = mctx.WithUIs(uis) 672 err = engine.RunEngine2(mctx, eng) 673 if err == nil { 674 res = eng.Blocks() 675 } 676 return res, err 677 } 678 679 func (h *UserHandler) GetTeamBlocks(ctx context.Context, sessionID int) (res []keybase1.TeamBlock, err error) { 680 mctx := libkb.NewMetaContext(ctx, h.G()) 681 eng := engine.NewTeamBlocksGet(h.G()) 682 uis := libkb.UIs{ 683 LogUI: h.getLogUI(sessionID), 684 SessionID: sessionID, 685 } 686 mctx = mctx.WithUIs(uis) 687 err = engine.RunEngine2(mctx, eng) 688 if err == nil { 689 res = eng.Blocks() 690 } 691 return res, err 692 } 693 694 // Legacy RPC and API: 695 696 func (h *UserHandler) BlockUser(ctx context.Context, username string) (err error) { 697 mctx := libkb.NewMetaContext(ctx, h.G()) 698 defer mctx.Trace(fmt.Sprintf("UserHandler#BlockUser: %s", username), &err)() 699 return h.setUserBlock(mctx, username, true) 700 } 701 702 func (h *UserHandler) UnblockUser(ctx context.Context, username string) (err error) { 703 mctx := libkb.NewMetaContext(ctx, h.G()) 704 defer mctx.Trace(fmt.Sprintf("UserHandler#UnblockUser: %s", username), &err)() 705 return h.setUserBlock(mctx, username, false) 706 } 707 708 func (h *UserHandler) setUserBlock(mctx libkb.MetaContext, username string, block bool) error { 709 uid, err := mctx.G().GetUPAKLoader().LookupUID(mctx.Ctx(), libkb.NewNormalizedUsername(username)) 710 if err != nil { 711 return err 712 } 713 apiArg := libkb.APIArg{ 714 Endpoint: "user/block", 715 SessionType: libkb.APISessionTypeREQUIRED, 716 Args: libkb.HTTPArgs{ 717 "block_uid": libkb.S{Val: uid.String()}, 718 "unblock": libkb.B{Val: !block}, 719 }, 720 } 721 _, err = mctx.G().API.Post(mctx, apiArg) 722 723 if err == nil { 724 h.cleanupAfterBlockChange(mctx, []keybase1.UID{uid}) 725 } 726 727 return err 728 } 729 730 func (h *UserHandler) cleanupAfterBlockChange(mctx libkb.MetaContext, uids []keybase1.UID) { 731 mctx.Debug("clearing card cache after block change") 732 for _, uid := range uids { 733 if err := mctx.G().CardCache().Delete(uid); err != nil { 734 mctx.Debug("cleanupAfterBlockChange CardCache delete error for %s: %s", uid, err) 735 } 736 } 737 738 mctx.Debug("refreshing wallet state after block change") 739 mctx.G().GetStellar().Refresh(mctx, "user block change") 740 }