forked from keybase/client
/
identify.go
336 lines (287 loc) · 9.27 KB
/
identify.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
package chat
import (
"context"
"errors"
"fmt"
"strings"
"sync"
"golang.org/x/sync/errgroup"
"github.com/keybase/client/go/chat/globals"
"github.com/keybase/client/go/chat/storage"
"github.com/keybase/client/go/chat/utils"
"github.com/keybase/client/go/engine"
"github.com/keybase/client/go/libkb"
"github.com/keybase/client/go/protocol/chat1"
"github.com/keybase/client/go/protocol/gregor1"
"github.com/keybase/client/go/protocol/keybase1"
)
type IdentifyNotifier struct {
globals.Contextified
utils.DebugLabeler
sync.RWMutex
storage *storage.Storage
identCache map[string]keybase1.CanonicalTLFNameAndIDWithBreaks
}
func NewIdentifyNotifier(g *globals.Context) *IdentifyNotifier {
return &IdentifyNotifier{
Contextified: globals.NewContextified(g),
DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "IdentifyNotifier", false),
identCache: make(map[string]keybase1.CanonicalTLFNameAndIDWithBreaks),
storage: storage.New(g),
}
}
func (i *IdentifyNotifier) Send(update keybase1.CanonicalTLFNameAndIDWithBreaks) {
// Send to storage as well (charge forward on error)
if err := i.storage.UpdateTLFIdentifyBreak(context.Background(), update.TlfID.ToBytes(), update.Breaks.Breaks); err != nil {
i.Debug(context.Background(), "failed to update storage with TLF identify info: %s", err.Error())
}
// Send notification to GUI about identify status
i.RLock()
tlfName := update.CanonicalName.String()
stored, ok := i.identCache[tlfName]
i.RUnlock()
if ok {
// We have the exact update stored, don't send it again
if stored.Eq(update) {
i.Debug(context.Background(), "hit cache, not sending notify: %s", tlfName)
return
}
}
i.Lock()
i.identCache[tlfName] = update
i.Unlock()
i.Debug(context.Background(), "cache miss, sending notify: %s dat: %v", tlfName, update)
i.G().NotifyRouter.HandleChatIdentifyUpdate(context.Background(), update)
}
type IdentifyChangedHandler struct {
globals.Contextified
utils.DebugLabeler
}
func NewIdentifyChangedHandler(g *globals.Context) *IdentifyChangedHandler {
return &IdentifyChangedHandler{
Contextified: globals.NewContextified(g),
DebugLabeler: utils.NewDebugLabeler(g.GetLog(), "IdentifyChangedHandler", false),
}
}
var errNoConvForUser = errors.New("user not found in inbox")
func (h *IdentifyChangedHandler) getUsername(ctx context.Context, uid keybase1.UID) (string, error) {
u, err := h.G().GetUPAKLoader().LookupUsername(ctx, uid)
return u.String(), err
}
func (h *IdentifyChangedHandler) getTLFtoCrypt(ctx context.Context, uid gregor1.UID) (string, chat1.TLFID, error) {
me := h.G().Env.GetUID()
inbox := storage.NewInbox(h.G(), me.ToBytes())
_, allConvs, err := inbox.ReadAll(ctx)
if err != nil {
return "", nil, err
}
for _, conv := range allConvs {
if conv.Conv.Includes(uid) {
maxText, err := conv.Conv.GetMaxMessage(chat1.MessageType_TEXT)
if err != nil {
h.Debug(ctx, "failed to get a max message from conv: uid: %s convID: %s err: %s",
uid, conv.GetConvID(), err.Error())
continue
}
return maxText.TLFNameExpanded(conv.Conv.Metadata.FinalizeInfo),
conv.Conv.Metadata.IdTriple.Tlfid, nil
}
}
h.Debug(ctx, "no conversation found for update for uid: %s", uid)
return "", nil, errNoConvForUser
}
func (h *IdentifyChangedHandler) BackgroundIdentifyChanged(ctx context.Context, job engine.IdentifyJob) {
notifier := NewIdentifyNotifier(h.G())
// Get username
uid := job.UID()
username, err := h.getUsername(ctx, uid)
if err != nil {
h.Debug(ctx, "BackgroundIdentifyChanged: failed to load username: uid: %s err: %s", uid, err)
return
}
// Get TLF info out of inbox
tlfName, tlfID, err := h.getTLFtoCrypt(ctx, uid.ToBytes())
if err != nil {
if err != errNoConvForUser {
h.Debug(ctx, "BackgroundIdentifyChanged: error finding TLF name for update: err: %s",
err.Error())
}
return
}
// Form payload
h.Debug(ctx, "BackgroundIdentifyChanged: using TLF name: %s", tlfName)
notifyPayload := keybase1.CanonicalTLFNameAndIDWithBreaks{
TlfID: keybase1.TLFID(tlfID.String()),
CanonicalName: keybase1.CanonicalTlfName(tlfName),
}
if job.ThisError() != nil {
// Handle error case by transmitting a break
idbreak := keybase1.TLFIdentifyFailure{
User: keybase1.User{
Uid: uid,
Username: username,
},
}
notifyPayload.Breaks = keybase1.TLFBreak{
Breaks: []keybase1.TLFIdentifyFailure{idbreak},
}
h.Debug(ctx, "BackgroundIdentifyChanged: transmitting a break")
}
// Fire away!
notifier.Send(notifyPayload)
}
func (h *IdentifyChangedHandler) HandleUserChanged(uid keybase1.UID) (err error) {
defer h.Trace(context.Background(), func() error { return err },
fmt.Sprintf("HandleUserChanged(uid=%s)", uid))()
// If this is about us we don't care
me := h.G().Env.GetUID()
if me.Equal(uid) {
return nil
}
// Make a new chat context
var breaks []keybase1.TLFIdentifyFailure
ident := keybase1.TLFIdentifyBehavior_CHAT_GUI
notifier := NewIdentifyNotifier(h.G())
ctx := Context(context.Background(), h.G(), ident, &breaks, notifier)
// Find a TLF name from the local inbox that includes the user sent to us
tlfName, _, err := h.getTLFtoCrypt(ctx, uid.ToBytes())
if err != nil {
if err != errNoConvForUser {
h.Debug(ctx, "HandleUserChanged: error finding TLF name for update: err: %s", err.Error())
return err
}
return nil
}
h.Debug(ctx, "HandleUserChanged: using TLF name: %s", tlfName)
// Take this guy out of the cache, we want this to run fresh
if err = h.G().Identify2Cache.Delete(uid); err != nil {
// Charge through this error, probably doesn't matter
h.Debug(ctx, "HandleUserChanged: unable to delete cache entry: uid: %s: err: %s", uid,
err.Error())
}
// Run against CryptKeys to generate notifications if necessary
_, err = CtxKeyFinder(ctx, h.G()).Find(ctx, tlfName, chat1.ConversationMembersType_KBFS, false)
if err != nil {
h.Debug(ctx, "HandleUserChanged: failed to run CryptKeys: %s", err.Error())
}
return nil
}
type NameIdentifier struct {
globals.Contextified
}
func NewNameIdentifier(g *globals.Context) *NameIdentifier {
return &NameIdentifier{
Contextified: globals.NewContextified(g),
}
}
func (t *NameIdentifier) Identify(ctx context.Context, arg keybase1.TLFQuery, private bool) ([]keybase1.TLFIdentifyFailure, error) {
// need new context as errgroup will cancel it.
group, ectx := errgroup.WithContext(BackgroundContext(ctx, t.G()))
assertions := make(chan string)
group.Go(func() error {
defer close(assertions)
pieces := strings.Split(strings.Fields(arg.TlfName)[0], ",")
for _, p := range pieces {
select {
case assertions <- p:
case <-ectx.Done():
return ectx.Err()
}
}
return nil
})
fails := make(chan keybase1.TLFIdentifyFailure)
const numIdentifiers = 3
for i := 0; i < numIdentifiers; i++ {
group.Go(func() error {
for assertion := range assertions {
f, err := t.identifyUser(ectx, assertion, private, arg.IdentifyBehavior)
if err != nil {
return err
}
if f.Breaks == nil {
continue
}
select {
case fails <- f:
case <-ectx.Done():
return ectx.Err()
}
}
return nil
})
}
go func() {
group.Wait()
close(fails)
}()
var res []keybase1.TLFIdentifyFailure
for f := range fails {
res = append(res, f)
}
if err := group.Wait(); err != nil {
return nil, err
}
return res, nil
}
func (t *NameIdentifier) identifyUser(ctx context.Context, assertion string, private bool, idBehavior keybase1.TLFIdentifyBehavior) (keybase1.TLFIdentifyFailure, error) {
reason := "You accessed a public conversation."
if private {
reason = fmt.Sprintf("You accessed a private conversation with %s.", assertion)
}
arg := keybase1.Identify2Arg{
UserAssertion: assertion,
UseDelegateUI: false,
Reason: keybase1.IdentifyReason{Reason: reason},
CanSuppressUI: true,
IdentifyBehavior: idBehavior,
}
ectx := engine.Context{
IdentifyUI: chatNullIdentifyUI{},
NetContext: ctx,
}
eng := engine.NewResolveThenIdentify2(t.G().ExternalG(), &arg)
err := engine.RunEngine(eng, &ectx)
if err != nil {
// Ignore these errors
if _, ok := err.(libkb.NotFoundError); ok {
return keybase1.TLFIdentifyFailure{}, nil
}
if _, ok := err.(libkb.ResolutionError); ok {
return keybase1.TLFIdentifyFailure{}, nil
}
// Special treatment is needed for GUI strict mode, since we need to
// simultaneously plumb identify breaks up to the UI, and make sure the
// overall process returns an error. Swallow the error here so the rest of
// the identify can proceed, but we will check later (in GetTLFCryptKeys) for breaks with this
// mode and return an error there.
if !(libkb.IsIdentifyProofError(err) &&
idBehavior == keybase1.TLFIdentifyBehavior_CHAT_GUI_STRICT) {
return keybase1.TLFIdentifyFailure{}, err
}
}
resp := eng.Result()
var frep keybase1.TLFIdentifyFailure
if resp != nil && resp.TrackBreaks != nil {
frep.User = keybase1.User{
Uid: resp.Upk.Uid,
Username: resp.Upk.Username,
}
frep.Breaks = resp.TrackBreaks
}
return frep, nil
}
func appendBreaks(l []keybase1.TLFIdentifyFailure, r []keybase1.TLFIdentifyFailure) []keybase1.TLFIdentifyFailure {
m := make(map[string]bool)
var res []keybase1.TLFIdentifyFailure
for _, f := range l {
m[f.User.Username] = true
res = append(res, f)
}
for _, f := range r {
if !m[f.User.Username] {
res = append(res, f)
}
}
return res
}