github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/chat/identify.go (about) 1 package chat 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "sync" 8 9 "golang.org/x/sync/errgroup" 10 11 "github.com/keybase/client/go/chat/globals" 12 "github.com/keybase/client/go/chat/storage" 13 "github.com/keybase/client/go/chat/utils" 14 "github.com/keybase/client/go/engine" 15 "github.com/keybase/client/go/libkb" 16 "github.com/keybase/client/go/protocol/keybase1" 17 ) 18 19 type DummyIdentifyNotifier struct{} 20 21 func (d DummyIdentifyNotifier) Reset() {} 22 func (d DummyIdentifyNotifier) ResetOnGUIConnect() {} 23 func (d DummyIdentifyNotifier) Send(ctx context.Context, update keybase1.CanonicalTLFNameAndIDWithBreaks) { 24 } 25 26 type SimpleIdentifyNotifier struct { 27 globals.Contextified 28 utils.DebugLabeler 29 storage *storage.Storage 30 } 31 32 func NewSimpleIdentifyNotifier(g *globals.Context) *SimpleIdentifyNotifier { 33 return &SimpleIdentifyNotifier{ 34 Contextified: globals.NewContextified(g), 35 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "SimpleIdentifyNotifier", false), 36 storage: storage.New(g, g.ConvSource), 37 } 38 } 39 40 func (d *SimpleIdentifyNotifier) Reset() {} 41 func (d *SimpleIdentifyNotifier) ResetOnGUIConnect() {} 42 43 func (d *SimpleIdentifyNotifier) Send(ctx context.Context, update keybase1.CanonicalTLFNameAndIDWithBreaks) { 44 // Send to storage as well (charge forward on error) 45 if err := d.storage.UpdateTLFIdentifyBreak(ctx, update.TlfID.ToBytes(), update.Breaks.Breaks); err != nil { 46 d.Debug(ctx, "failed to update storage with TLF identify info: %s", err.Error()) 47 } 48 d.G().NotifyRouter.HandleChatIdentifyUpdate(ctx, update) 49 } 50 51 type CachingIdentifyNotifier struct { 52 globals.Contextified 53 utils.DebugLabeler 54 55 sync.RWMutex 56 storage *storage.Storage 57 identCache map[string]keybase1.CanonicalTLFNameAndIDWithBreaks 58 } 59 60 func NewCachingIdentifyNotifier(g *globals.Context) *CachingIdentifyNotifier { 61 return &CachingIdentifyNotifier{ 62 Contextified: globals.NewContextified(g), 63 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "CachingIdentifyNotifier", false), 64 identCache: make(map[string]keybase1.CanonicalTLFNameAndIDWithBreaks), 65 storage: storage.New(g, g.ConvSource), 66 } 67 } 68 69 func (i *CachingIdentifyNotifier) ResetOnGUIConnect() { 70 i.G().ConnectionManager.RegisterLabelCallback(func(typ keybase1.ClientType) { 71 switch typ { 72 case keybase1.ClientType_GUI_HELPER, keybase1.ClientType_GUI_MAIN: 73 i.Reset() 74 } 75 }) 76 } 77 78 func (i *CachingIdentifyNotifier) Reset() { 79 i.identCache = make(map[string]keybase1.CanonicalTLFNameAndIDWithBreaks) 80 } 81 82 func (i *CachingIdentifyNotifier) Send(ctx context.Context, update keybase1.CanonicalTLFNameAndIDWithBreaks) { 83 84 // Send to storage as well (charge forward on error) 85 if err := i.storage.UpdateTLFIdentifyBreak(ctx, update.TlfID.ToBytes(), update.Breaks.Breaks); err != nil { 86 i.Debug(ctx, "failed to update storage with TLF identify info: %s", err.Error()) 87 } 88 89 // Send notification to GUI about identify status 90 tlfName := update.CanonicalName.String() 91 i.RLock() 92 stored, ok := i.identCache[tlfName] 93 i.RUnlock() 94 if ok { 95 // We have the exact update stored, don't send it again 96 if stored.Eq(update) { 97 i.Debug(ctx, "hit cache, not sending notify: %s", tlfName) 98 return 99 } 100 } 101 i.Lock() 102 i.identCache[tlfName] = update 103 i.Unlock() 104 105 i.Debug(ctx, "cache miss, sending notify: %s dat: %v", tlfName, update) 106 i.G().NotifyRouter.HandleChatIdentifyUpdate(ctx, update) 107 } 108 109 type IdentifyChangedHandler struct { 110 globals.Contextified 111 utils.DebugLabeler 112 } 113 114 func NewIdentifyChangedHandler(g *globals.Context) *IdentifyChangedHandler { 115 return &IdentifyChangedHandler{ 116 Contextified: globals.NewContextified(g), 117 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "IdentifyChangedHandler", false), 118 } 119 } 120 121 func (h *IdentifyChangedHandler) getUsername(ctx context.Context, uid keybase1.UID) (string, error) { 122 u, err := h.G().GetUPAKLoader().LookupUsername(ctx, uid) 123 return u.String(), err 124 } 125 126 func (h *IdentifyChangedHandler) HandleUserChanged(uid keybase1.UID) (err error) { 127 defer h.Trace(context.Background(), &err, 128 fmt.Sprintf("HandleUserChanged(uid=%s)", uid))() 129 // If this is about us we don't care 130 me := h.G().Env.GetUID() 131 if me.Equal(uid) { 132 return nil 133 } 134 // Make a new chat context 135 var breaks []keybase1.TLFIdentifyFailure 136 ident := keybase1.TLFIdentifyBehavior_CHAT_GUI 137 notifier := NewCachingIdentifyNotifier(h.G()) 138 ctx := globals.ChatCtx(context.Background(), h.G(), ident, &breaks, notifier) 139 username, err := h.getUsername(ctx, uid) 140 if err != nil { 141 return err 142 } 143 if _, err := NewNameIdentifier(h.G()).Identify(ctx, []string{username}, true, func() keybase1.TLFID { 144 return "" 145 }, func() keybase1.CanonicalTlfName { 146 return keybase1.CanonicalTlfName(username) 147 }); err != nil { 148 h.Debug(ctx, "HandleUserChanged: failed to identify: %s", err) 149 } 150 return nil 151 } 152 153 type NameIdentifier struct { 154 globals.Contextified 155 utils.DebugLabeler 156 } 157 158 func NewNameIdentifier(g *globals.Context) *NameIdentifier { 159 return &NameIdentifier{ 160 Contextified: globals.NewContextified(g), 161 DebugLabeler: utils.NewDebugLabeler(g.ExternalG(), "NameIdentifier", false), 162 } 163 } 164 165 func (t *NameIdentifier) Identify(ctx context.Context, names []string, private bool, 166 getTLFID func() keybase1.TLFID, getCanonicalName func() keybase1.CanonicalTlfName) (res []keybase1.TLFIdentifyFailure, err error) { 167 idNotifier := globals.CtxIdentifyNotifier(ctx) 168 identBehavior, breaks, ok := globals.CtxIdentifyMode(ctx) 169 if !ok { 170 return res, fmt.Errorf("invalid context with no chat metadata") 171 } 172 defer t.Trace(ctx, &err, 173 fmt.Sprintf("Identify(names=%s,mode=%v,uid=%s)", strings.Join(names, ","), identBehavior, 174 t.G().GetEnv().GetUsername()))() 175 176 if identBehavior == keybase1.TLFIdentifyBehavior_CHAT_SKIP { 177 t.Debug(ctx, "SKIP behavior found, not running identify") 178 return nil, nil 179 } 180 181 // need new context as errgroup will cancel it. 182 group, ectx := errgroup.WithContext(globals.BackgroundChatCtx(ctx, t.G())) 183 assertions := make(chan string) 184 185 group.Go(func() error { 186 defer close(assertions) 187 for _, p := range names { 188 select { 189 case assertions <- p: 190 case <-ectx.Done(): 191 return ectx.Err() 192 } 193 } 194 return nil 195 }) 196 197 fails := make(chan keybase1.TLFIdentifyFailure) 198 const numIdentifiers = 3 199 for i := 0; i < numIdentifiers; i++ { 200 group.Go(func() error { 201 for assertion := range assertions { 202 f, err := t.identifyUser(ectx, assertion, private, identBehavior) 203 if err != nil { 204 return err 205 } 206 if f.Breaks == nil { 207 continue 208 } 209 select { 210 case fails <- f: 211 case <-ectx.Done(): 212 return ectx.Err() 213 } 214 } 215 return nil 216 }) 217 } 218 219 go func() { 220 _ = group.Wait() 221 close(fails) 222 }() 223 for f := range fails { 224 res = append(res, f) 225 } 226 if err := group.Wait(); err != nil { 227 return nil, err 228 } 229 230 // Run the updates through the identify notifier 231 if idNotifier != nil { 232 t.Debug(ctx, "sending update through ident notifier: %d breaks", len(res)) 233 idNotifier.Send(ctx, keybase1.CanonicalTLFNameAndIDWithBreaks{ 234 Breaks: keybase1.TLFBreak{ 235 Breaks: res, 236 }, 237 TlfID: getTLFID(), 238 CanonicalName: getCanonicalName(), 239 }) 240 } 241 *breaks = appendBreaks(*breaks, res) 242 243 return res, nil 244 } 245 246 func (t *NameIdentifier) identifyUser(ctx context.Context, assertion string, private bool, idBehavior keybase1.TLFIdentifyBehavior) (keybase1.TLFIdentifyFailure, error) { 247 reason := "You accessed a public conversation." 248 if private { 249 reason = fmt.Sprintf("You accessed a private conversation with %s.", assertion) 250 } 251 252 arg := keybase1.Identify2Arg{ 253 UserAssertion: assertion, 254 UseDelegateUI: false, 255 Reason: keybase1.IdentifyReason{Reason: reason}, 256 CanSuppressUI: true, 257 IdentifyBehavior: idBehavior, 258 } 259 260 uis := libkb.UIs{ 261 IdentifyUI: chatNullIdentifyUI{}, 262 } 263 eng := engine.NewResolveThenIdentify2(t.G().ExternalG(), &arg) 264 m := libkb.NewMetaContext(ctx, t.G().ExternalG()).WithUIs(uis) 265 if err := engine.RunEngine2(m, eng); err != nil { 266 switch err.(type) { 267 // Ignore these errors 268 // NOTE: Even though we ignore a `libkb.UserDeletedError` here, if we have 269 // previously chatted with the user we will still validate the sigchain 270 // when identifying the user and then return this error. 271 case libkb.NotFoundError, libkb.ResolutionError, libkb.UserDeletedError: 272 return keybase1.TLFIdentifyFailure{}, nil 273 } 274 return keybase1.TLFIdentifyFailure{}, err 275 } 276 resp, err := eng.Result(m) 277 if err != nil { 278 return keybase1.TLFIdentifyFailure{}, err 279 } 280 var frep keybase1.TLFIdentifyFailure 281 if resp != nil && resp.TrackBreaks != nil { 282 frep.User = resp.Upk.ExportToSimpleUser() 283 frep.Breaks = resp.TrackBreaks 284 } 285 286 return frep, nil 287 } 288 289 func appendBreaks(l []keybase1.TLFIdentifyFailure, r []keybase1.TLFIdentifyFailure) []keybase1.TLFIdentifyFailure { 290 m := make(map[string]bool) 291 var res []keybase1.TLFIdentifyFailure 292 for _, f := range l { 293 m[f.User.Username] = true 294 res = append(res, f) 295 } 296 for _, f := range r { 297 if !m[f.User.Username] { 298 res = append(res, f) 299 } 300 } 301 return res 302 }