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  }