github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/service/team_handler.go (about) 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 6 package service 7 8 import ( 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 24 const teamHandlerName = "teamHandler" 25 26 type 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 37 var _ libkb.GregorInBandMessageHandler = (*teamHandler)(nil) 38 39 func newTeamHandler(g *libkb.GlobalContext, badger *badges.Badger) *teamHandler { 40 return &teamHandler{ 41 Contextified: libkb.NewContextified(g), 42 badger: badger, 43 } 44 } 45 46 func (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 86 func (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 130 func (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 150 type abandonMsg struct { 151 TeamID keybase1.TeamID `json:"team_id"` 152 } 153 154 func (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 174 func (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 179 func (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 200 func (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 211 func (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 226 func (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 240 func (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 259 func (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 276 func (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 293 func (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 310 func (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 321 func (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 338 func (r *teamHandler) Dismiss(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) { 339 return false, nil 340 } 341 342 func (r *teamHandler) IsAlive() bool { 343 return true 344 } 345 346 func (r *teamHandler) Name() string { 347 return teamHandlerName 348 }