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  }