github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/engine/list_tracking.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 engine
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"regexp"
    10  	"sort"
    11  	"time"
    12  
    13  	"github.com/keybase/client/go/libkb"
    14  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    15  	jsonw "github.com/keybase/go-jsonw"
    16  )
    17  
    18  type ListTrackingEngineArg struct {
    19  	Assertion  string
    20  	UID        keybase1.UID
    21  	CachedOnly bool
    22  
    23  	// If CachedOnly is set and StalenessWindow is non-nil, will load with
    24  	// StaleOK and use the relaxed CachedOnlyStalenessWindow instead.
    25  	CachedOnlyStalenessWindow *time.Duration
    26  
    27  	JSON    bool
    28  	Verbose bool
    29  	Filter  string
    30  }
    31  
    32  // ListTrackingEngine loads the follows of the given user using their sigchain,
    33  // but relies on the server to filter out users who have reset after the follow
    34  // statement.
    35  type ListTrackingEngine struct {
    36  	arg         *ListTrackingEngineArg
    37  	tableResult keybase1.UserSummarySet
    38  	jsonResult  string
    39  	libkb.Contextified
    40  
    41  	disableTrackerSyncerForTest bool
    42  }
    43  
    44  func NewListTrackingEngine(g *libkb.GlobalContext, arg *ListTrackingEngineArg) *ListTrackingEngine {
    45  	return &ListTrackingEngine{
    46  		arg:          arg,
    47  		Contextified: libkb.NewContextified(g),
    48  	}
    49  }
    50  
    51  func (e *ListTrackingEngine) Name() string {
    52  	return "ListTracking"
    53  }
    54  
    55  func (e *ListTrackingEngine) Prereqs() Prereqs { return Prereqs{} }
    56  
    57  func (e *ListTrackingEngine) RequiredUIs() []libkb.UIKind { return []libkb.UIKind{} }
    58  
    59  func (e *ListTrackingEngine) SubConsumers() []libkb.UIConsumer { return nil }
    60  
    61  func (e *ListTrackingEngine) Run(m libkb.MetaContext) (err error) {
    62  	uid, err := lookupUID(m, e.arg.UID, e.arg.Assertion, e.arg.CachedOnly)
    63  	if err != nil {
    64  		return err
    65  	}
    66  
    67  	// Get version according to server so we can filter out reset users later
    68  	ts := libkb.NewServertrustTrackerSyncer(m.G(), m.G().GetMyUID(), libkb.FollowDirectionFollowing)
    69  	var tsErr error
    70  	if e.disableTrackerSyncerForTest {
    71  		tsErr = errors.New("tracker syncer disabled for test")
    72  	} else if e.arg.CachedOnly {
    73  		tsErr = libkb.RunSyncerCached(m, ts, uid)
    74  	} else {
    75  		tsErr = libkb.RunSyncer(m, ts, uid, false /* loggedIn */, false /* forceReload */)
    76  	}
    77  	useServerLookup := false
    78  	serverLookup := make(map[keybase1.UID]struct{})
    79  	fullNames := make(map[keybase1.UID]string)
    80  	if tsErr != nil {
    81  		m.Warning("failed to load following list from server (cachedOnly=%t); continuing: %s", e.arg.CachedOnly, tsErr)
    82  	} else {
    83  		useServerLookup = true
    84  		m.Debug("got following list from server (len=%d, cachedOnly=%t); using it to filter sigchain list", len(ts.Result().Users), e.arg.CachedOnly)
    85  		for _, user := range ts.Result().Users {
    86  			serverLookup[user.Uid] = struct{}{}
    87  			fullNames[user.Uid] = user.FullName
    88  		}
    89  	}
    90  
    91  	// Load unstubbed so we get track links
    92  	larg := libkb.NewLoadUserArgWithMetaContext(m).
    93  		WithUID(uid).
    94  		WithStubMode(libkb.StubModeUnstubbed).
    95  		WithCachedOnly(e.arg.CachedOnly).
    96  		WithSelf(uid.Exists() && uid.Equal(m.G().GetMyUID()))
    97  	if e.arg.CachedOnly && e.arg.CachedOnlyStalenessWindow != nil {
    98  		larg = larg.WithStaleOK(true)
    99  	}
   100  	upak, _, err := m.G().GetUPAKLoader().LoadV2(larg)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	if upak == nil {
   105  		return libkb.UserNotFoundError{}
   106  	}
   107  
   108  	if e.arg.CachedOnly && e.arg.CachedOnlyStalenessWindow != nil {
   109  		if m.G().Clock().Since(keybase1.FromTime(upak.Uvv.CachedAt)) > *e.arg.CachedOnlyStalenessWindow {
   110  			msg := fmt.Sprintf("upak was cached but exceeded custom staleness window %v", *e.arg.CachedOnlyStalenessWindow)
   111  			return libkb.UserNotFoundError{UID: uid, Msg: msg}
   112  		}
   113  	}
   114  
   115  	unfilteredTracks := upak.Current.RemoteTracks
   116  
   117  	var rxx *regexp.Regexp
   118  	if e.arg.Filter != "" {
   119  		rxx, err = regexp.Compile(e.arg.Filter)
   120  		if err != nil {
   121  			return err
   122  		}
   123  	}
   124  
   125  	// Filter out any marked reset by server, or due to Filter argument
   126  	var filteredTracks []keybase1.RemoteTrack
   127  	for _, track := range unfilteredTracks {
   128  		trackedUID := track.Uid
   129  		if useServerLookup {
   130  			if _, ok := serverLookup[trackedUID]; !ok {
   131  				m.Debug("filtering out uid %s in sigchain list but not provided by server", trackedUID)
   132  				continue
   133  			}
   134  		}
   135  		if rxx != nil && !rxx.MatchString(track.Username) {
   136  			continue
   137  		}
   138  		filteredTracks = append(filteredTracks, track)
   139  	}
   140  
   141  	sort.Slice(filteredTracks, func(i, j int) bool {
   142  		return filteredTracks[i].Username < filteredTracks[j].Username
   143  	})
   144  
   145  	if e.arg.JSON {
   146  		return e.runJSON(m, filteredTracks, e.arg.Verbose)
   147  	}
   148  	return e.runTable(m, filteredTracks, fullNames)
   149  }
   150  
   151  func (e *ListTrackingEngine) runTable(m libkb.MetaContext, filteredTracks []keybase1.RemoteTrack, fullNames map[keybase1.UID]string) error {
   152  	e.tableResult = keybase1.UserSummarySet{}
   153  	for _, track := range filteredTracks {
   154  		linkID := track.LinkID
   155  		entry := keybase1.UserSummary{
   156  			Uid:      track.Uid,
   157  			Username: track.Username,
   158  			LinkID:   &linkID,
   159  			FullName: fullNames[track.Uid],
   160  		}
   161  		e.tableResult.Users = append(e.tableResult.Users, entry)
   162  	}
   163  	return nil
   164  }
   165  
   166  func condenseRecord(t keybase1.RemoteTrack) (*jsonw.Wrapper, error) {
   167  	out := jsonw.NewDictionary()
   168  	err := out.SetKey("uid", libkb.UIDWrapper(t.Uid))
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	err = out.SetKey("username", jsonw.NewString(t.Username))
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	err = out.SetKey("link_id", jsonw.NewString(t.LinkID.String()))
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  
   181  	return out, nil
   182  }
   183  
   184  func (e *ListTrackingEngine) runJSON(m libkb.MetaContext, filteredTracks []keybase1.RemoteTrack, verbose bool) error {
   185  	var tmp []*jsonw.Wrapper
   186  	for _, track := range filteredTracks {
   187  		var rec *jsonw.Wrapper
   188  		var e2 error
   189  		if rec, e2 = condenseRecord(track); e2 != nil {
   190  			m.Warning("In conversion to JSON: %s", e2)
   191  		}
   192  		if e2 == nil {
   193  			tmp = append(tmp, rec)
   194  		}
   195  	}
   196  
   197  	ret := jsonw.NewArray(len(tmp))
   198  	for i, r := range tmp {
   199  		if err := ret.SetIndex(i, r); err != nil {
   200  			return err
   201  		}
   202  	}
   203  
   204  	e.jsonResult = ret.MarshalPretty()
   205  	return nil
   206  }
   207  
   208  func (e *ListTrackingEngine) TableResult() keybase1.UserSummarySet {
   209  	return e.tableResult
   210  }
   211  
   212  func (e *ListTrackingEngine) JSONResult() string {
   213  	return e.jsonResult
   214  }