1// Copyright 2017 Keybase, Inc. All rights reserved. Use of
2// this source code is governed by the included BSD license.
3
4// Handlers for team-related gregor messages
5
6package service
7
8import (
9	"encoding/json"
10	"fmt"
11	"strings"
12	"sync"
13
14	"golang.org/x/net/context"
15
16	"github.com/keybase/client/go/badges"
17	"github.com/keybase/client/go/gregor"
18	"github.com/keybase/client/go/libkb"
19	"github.com/keybase/client/go/protocol/gregor1"
20	"github.com/keybase/client/go/protocol/keybase1"
21	"github.com/keybase/client/go/teams"
22)
23
24const teamHandlerName = "teamHandler"
25
26type teamHandler struct {
27	libkb.Contextified
28	badger *badges.Badger
29
30	// Some work that comes from team gregor messages is done in
31	// background goroutine: rotateTeam and reset user badge
32	// dismissing, for now. Use a mutex to limit this to only one
33	// job at time.
34	teamHandlerBackgroundJob sync.Mutex
35}
36
37var _ libkb.GregorInBandMessageHandler = (*teamHandler)(nil)
38
39func newTeamHandler(g *libkb.GlobalContext, badger *badges.Badger) *teamHandler {
40	return &teamHandler{
41		Contextified: libkb.NewContextified(g),
42		badger:       badger,
43	}
44}
45
46func (r *teamHandler) Create(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) {
47	switch category {
48	case "team.clkr":
49		return true, r.rotateTeam(ctx, cli, item)
50	case "team.sbs":
51		return true, r.sharingBeforeSignup(ctx, cli, item)
52	case "team.openreq":
53		return true, r.openTeamAccessRequest(ctx, cli, item)
54	case "team.opensweep":
55		return true, r.openTeamSweepResetUsersRequest(ctx, cli, item)
56	case "team.change":
57		return true, r.changeTeam(ctx, cli, category, item, keybase1.TeamChangeSet{})
58	case "team.force_repoll":
59		return true, r.gotForceRepoll(ctx, cli, item)
60	case "team.rename":
61		return true, r.changeTeam(ctx, cli, category, item, keybase1.TeamChangeSet{Renamed: true})
62	case "team.delete":
63		return true, r.deleteTeam(ctx, cli, item)
64	case "team.exit":
65		return true, r.exitTeam(ctx, cli, item)
66	case "team.seitan":
67		return true, r.seitanCompletion(ctx, cli, item)
68	case "team.member_out_from_reset":
69		return true, r.memberOutFromReset(ctx, cli, item)
70	case "team.abandoned":
71		return true, r.abandonTeam(ctx, cli, item)
72	case "team.newly_added_to_team":
73		return true, r.newlyAddedToTeam(ctx, cli, item)
74	case "team.user_team_version":
75		return true, r.userTeamVersion(ctx, cli, item)
76	case "team.member_showcase_change":
77		return true, r.memberShowcaseChange(ctx, cli, item)
78	default:
79		if strings.HasPrefix(category, "team.") {
80			return false, fmt.Errorf("unknown teamHandler category: %q", category)
81		}
82		return false, nil
83	}
84}
85
86func (r *teamHandler) rotateTeam(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
87	r.G().Log.CDebugf(ctx, "teamHandler: team.clkr received")
88	var msg keybase1.TeamCLKRMsg
89	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
90		r.G().Log.CDebugf(ctx, "error unmarshaling team.clkr item: %s", err)
91		return err
92	}
93	r.G().Log.CDebugf(ctx, "team.clkr unmarshaled: %+v", msg)
94
95	for _, uv := range msg.ResetUsersUntrusted {
96		// We don't use UIDMapper in HandleRotateRequest, but since
97		// server just told us that these users have reset, we might
98		// as well use that knowledge to refresh cache.
99
100		// Use ClearUIDAtEldestSeqno instead of InformOfEldestSeqno
101		// because usually uv.UserEldestSeqno (the "new EldestSeqno")
102		// will be 0, because user has just reset and hasn't
103		// reprovisioned yet
104
105		err := r.G().UIDMapper.ClearUIDAtEldestSeqno(ctx, r.G(), uv.Uid, uv.MemberEldestSeqno)
106		if err != nil {
107			return err
108		}
109	}
110
111	go func() {
112		r.teamHandlerBackgroundJob.Lock()
113		defer r.teamHandlerBackgroundJob.Unlock()
114
115		if err := teams.HandleRotateRequest(ctx, r.G(), msg); err != nil {
116			r.G().Log.CDebugf(ctx, "HandleRotateRequest failed with error: %s", err)
117			return
118		}
119
120		r.G().Log.CDebugf(ctx, "dismissing team.clkr item since rotate succeeded")
121		err := r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
122		if err != nil {
123			r.G().Log.CDebugf(ctx, "error dismissing team.clkr item: %+v", err)
124		}
125	}()
126
127	return nil
128}
129
130func (r *teamHandler) memberOutFromReset(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
131	nm := "team.member_out_from_reset"
132	r.G().Log.CDebugf(ctx, "teamHandler: %s received", nm)
133	var msg keybase1.TeamMemberOutFromReset
134	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
135		r.G().Log.CDebugf(ctx, "error unmarshaling %s item: %s", nm, err)
136		return err
137	}
138	r.G().Log.CDebugf(ctx, "%s unmarshaled: %+v", nm, msg)
139
140	if err := r.G().UIDMapper.ClearUIDAtEldestSeqno(ctx, r.G(), msg.ResetUser.Uid, msg.ResetUser.EldestSeqno); err != nil {
141		return err
142	}
143	// Favorites is misused to let people know when there are reset team
144	// members. This busts the relevant cache.
145	r.G().NotifyRouter.HandleFavoritesChanged(r.G().GetMyUID())
146	r.G().Log.CDebugf(ctx, "%s: cleared UIDMap cache for %s%%%d", nm, msg.ResetUser.Uid, msg.ResetUser.EldestSeqno)
147	return nil
148}
149
150type abandonMsg struct {
151	TeamID keybase1.TeamID `json:"team_id"`
152}
153
154func (r *teamHandler) abandonTeam(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
155	nm := "team.abandoned"
156	r.G().Log.CDebugf(ctx, "teamHandler.abandonTeam: %s received", nm)
157	var msg abandonMsg
158	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
159		r.G().Log.CDebugf(ctx, "error unmarshaling %s item: %s", nm, err)
160		return err
161	}
162	r.G().Log.CDebugf(ctx, "teamHandler.abandonTeam: %s unmarshaled: %+v", nm, msg)
163
164	r.G().NotifyRouter.HandleTeamAbandoned(ctx, msg.TeamID)
165
166	r.G().Log.CDebugf(ctx, "teamHandler.abandonTeam: locally dismissing %s", nm)
167	if err := r.G().GregorState.LocalDismissItem(ctx, item.Metadata().MsgID()); err != nil {
168		r.G().Log.CDebugf(ctx, "teamHandler.abandonTeam: failed to locally dismiss msg %v", item.Metadata().MsgID())
169	}
170
171	return nil
172}
173
174func (r *teamHandler) gotForceRepoll(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
175	r.G().Log.CDebugf(ctx, "teamHandler: gotForceRepoll received")
176	return teams.HandleForceRepollNotification(ctx, r.G(), item.DTime())
177}
178
179func (r *teamHandler) changeTeam(ctx context.Context, cli gregor1.IncomingInterface, category string,
180	item gregor.Item, changes keybase1.TeamChangeSet) error {
181	var rows []keybase1.TeamChangeRow
182	r.G().Log.CDebugf(ctx, "teamHandler: changeTeam received")
183	if err := json.Unmarshal(item.Body().Bytes(), &rows); err != nil {
184		r.G().Log.CDebugf(ctx, "error unmarshaling %s item: %s", category, err)
185		return err
186	}
187	r.G().Log.CDebugf(ctx, "%s unmarshaled: %+v", category, rows)
188	if err := teams.HandleChangeNotification(ctx, r.G(), rows, changes); err != nil {
189		return err
190	}
191
192	// Locally dismiss this now that we have processed it so we can
193	// avoid replaying it over and over.
194	if err := r.G().GregorState.LocalDismissItem(ctx, item.Metadata().MsgID()); err != nil {
195		r.G().Log.CDebugf(ctx, "failed to local dismiss team change: %s", err)
196	}
197	return nil
198}
199
200func (r *teamHandler) deleteTeam(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
201	var rows []keybase1.TeamChangeRow
202	if err := json.Unmarshal(item.Body().Bytes(), &rows); err != nil {
203		r.G().Log.CDebugf(ctx, "error unmarshaling team.(change|rename) item: %s", err)
204		return err
205	}
206	r.G().Log.CDebugf(ctx, "teamHandler: team.delete unmarshaled: %+v", rows)
207
208	return teams.HandleDeleteNotification(ctx, r.G(), rows)
209}
210
211func (r *teamHandler) exitTeam(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
212	var rows []keybase1.TeamExitRow
213	if err := json.Unmarshal(item.Body().Bytes(), &rows); err != nil {
214		r.G().Log.CDebugf(ctx, "error unmarshaling team.exit item: %s", err)
215		return err
216	}
217	r.G().Log.CDebugf(ctx, "teamHandler: team.exit unmarshaled: %+v", rows)
218	if err := teams.HandleExitNotification(ctx, r.G(), rows); err != nil {
219		return err
220	}
221
222	r.G().Log.Debug("dismissing team.exit: %v", item.Metadata().MsgID().String())
223	return r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
224}
225
226func (r *teamHandler) userTeamVersion(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) (err error) {
227	mctx := libkb.NewMetaContext(ctx, r.G())
228	nm := "team.user_team_version"
229	defer mctx.Trace("teamHandler#userTeamVersion", &err)()
230	var obj keybase1.UserTeamVersionUpdate
231	err = json.Unmarshal(item.Body().Bytes(), &obj)
232	if err != nil {
233		mctx.Debug("Error unmarshaling %s item: %s", nm, err)
234		return err
235	}
236	return r.G().GetTeamRoleMapManager().Update(mctx, obj.Version)
237
238}
239
240func (r *teamHandler) newlyAddedToTeam(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
241	nm := "team.newly_added_to_team"
242	r.G().Log.CDebugf(ctx, "teamHandler.newlyAddedToTeam: %s received", nm)
243	var rows []keybase1.TeamNewlyAddedRow
244	if err := json.Unmarshal(item.Body().Bytes(), &rows); err != nil {
245		r.G().Log.CDebugf(ctx, "error unmarshaling %s item: %s", nm, err)
246		return err
247	}
248	r.G().Log.CDebugf(ctx, "teamHandler.newlyAddedToTeam: %s unmarshaled: %+v", nm, rows)
249	if err := teams.HandleNewlyAddedToTeamNotification(ctx, r.G(), rows); err != nil {
250		return err
251	}
252
253	// Note there used to be a local dismissal here, but the newly_added_to_team needs
254	// to stay in the gregor state for badging to work.
255
256	return nil
257}
258
259func (r *teamHandler) sharingBeforeSignup(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
260	r.G().Log.CDebugf(ctx, "teamHandler: team.sbs received")
261	var msg keybase1.TeamSBSMsg
262	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
263		r.G().Log.CDebugf(ctx, "error unmarshaling team.sbs item: %s", err)
264		return err
265	}
266	r.G().Log.CDebugf(ctx, "team.sbs unmarshaled: %+v", msg)
267
268	if err := teams.HandleSBSRequest(ctx, r.G(), msg); err != nil {
269		return err
270	}
271
272	r.G().Log.Debug("dismissing team.sbs item since it succeeded")
273	return r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
274}
275
276func (r *teamHandler) openTeamAccessRequest(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
277	r.G().Log.CDebugf(ctx, "teamHandler: team.openreq received")
278	var msg keybase1.TeamOpenReqMsg
279	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
280		r.G().Log.CDebugf(ctx, "error unmarshaling team.openreq item: %s", err)
281		return err
282	}
283	r.G().Log.CDebugf(ctx, "team.openreq unmarshaled: %+v", msg)
284
285	if err := teams.HandleOpenTeamAccessRequest(ctx, r.G(), msg); err != nil {
286		return err
287	}
288
289	r.G().Log.CDebugf(ctx, "dismissing team.openreq item since it succeeded")
290	return r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
291}
292
293func (r *teamHandler) openTeamSweepResetUsersRequest(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
294	r.G().Log.CDebugf(ctx, "teamHandler: team.opensweep received")
295	var msg keybase1.TeamOpenSweepMsg
296	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
297		r.G().Log.CDebugf(ctx, "error unmarshaling team.opensweep item: %s", err)
298		return err
299	}
300	r.G().Log.CDebugf(ctx, "team.opensweep unmarshaled: %+v", msg)
301
302	if err := teams.HandleOpenTeamSweepRequest(ctx, r.G(), msg); err != nil {
303		return err
304	}
305
306	r.G().Log.CDebugf(ctx, "dismissing team.opensweep item since it succeeded")
307	return r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
308}
309
310func (r *teamHandler) memberShowcaseChange(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
311	r.G().Log.CDebugf(ctx, "teamHandler: team.member_showchase_change received")
312
313	if err := teams.HandleTeamMemberShowcaseChange(ctx, r.G()); err != nil {
314		return err
315	}
316
317	r.G().Log.CDebugf(ctx, "dismissing team.member_showcase_change item since it succeeded")
318	return r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
319}
320
321func (r *teamHandler) seitanCompletion(ctx context.Context, cli gregor1.IncomingInterface, item gregor.Item) error {
322	r.G().Log.CDebugf(ctx, "teamHandler: team.seitan received")
323	var msg keybase1.TeamSeitanMsg
324	if err := json.Unmarshal(item.Body().Bytes(), &msg); err != nil {
325		r.G().Log.CDebugf(ctx, "error unmarshaling team.seitan item: %s", err)
326		return err
327	}
328	r.G().Log.CDebugf(ctx, "team.seitan unmarshaled: %+v", msg)
329
330	if err := teams.HandleTeamSeitan(ctx, r.G(), msg); err != nil {
331		return err
332	}
333
334	r.G().Log.CDebugf(ctx, "dismissing team.seitan item since it succeeded")
335	return r.G().GregorState.DismissItem(ctx, cli, item.Metadata().MsgID())
336}
337
338func (r *teamHandler) Dismiss(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) {
339	return false, nil
340}
341
342func (r *teamHandler) IsAlive() bool {
343	return true
344}
345
346func (r *teamHandler) Name() string {
347	return teamHandlerName
348}
349