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 }