github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/systests/team_tx_test.go (about) 1 package systests 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/require" 8 "golang.org/x/net/context" 9 10 "github.com/keybase/client/go/libkb" 11 keybase1 "github.com/keybase/client/go/protocol/keybase1" 12 "github.com/keybase/client/go/teams" 13 ) 14 15 func testTeamTx1(t *testing.T, byUV bool) { 16 tt := newTeamTester(t) 17 defer tt.cleanup() 18 19 ann := makeUserStandalone(t, tt, "ann", standaloneUserArgs{ 20 disableGregor: true, 21 suppressTeamChatAnnounce: true, 22 }) 23 t.Logf("Signed up ann (%s)", ann.username) 24 25 bob := tt.addPuklessUser("bob") 26 t.Logf("Signed up PUK-less user bob (%s)", bob.username) 27 28 tracy := tt.addUser("trc") 29 t.Logf("Signed up PUK-ful user trc (%s)", tracy.username) 30 31 botua := tt.addUser("ua") 32 t.Logf("Signed up user ua (%s) to be a bot", tracy.username) 33 34 restrictedBotua := tt.addUser("r_ua") 35 t.Logf("Signed up user ua (%s) to be a restricted bot", tracy.username) 36 37 team := ann.createTeam() 38 t.Logf("Team created (%s)", team) 39 40 // TRANSACTION 1 - add bob (keybase-type invite) and tracy (crypto member) 41 42 teamObj := ann.loadTeam(team, true /* admin */) 43 44 var err error 45 tx := teams.CreateAddMemberTx(teamObj) 46 tx.AllowPUKless = true 47 if byUV { 48 err = tx.AddMemberByUV(context.Background(), bob.userVersion(), keybase1.TeamRole_WRITER, nil) 49 require.NoError(t, err) 50 err = tx.AddMemberByUV(context.Background(), tracy.userVersion(), keybase1.TeamRole_READER, nil) 51 require.NoError(t, err) 52 err = tx.AddMemberByUV(context.Background(), botua.userVersion(), keybase1.TeamRole_BOT, nil) 53 require.NoError(t, err) 54 err = tx.AddMemberByUV(context.Background(), restrictedBotua.userVersion(), keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{}) 55 require.NoError(t, err) 56 } else { 57 err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil) 58 require.NoError(t, err) 59 err = tx.AddMemberByUsername(context.Background(), tracy.username, keybase1.TeamRole_READER, nil) 60 require.NoError(t, err) 61 err = tx.AddMemberByUsername(context.Background(), botua.username, keybase1.TeamRole_BOT, nil) 62 require.NoError(t, err) 63 err = tx.AddMemberByUsername(context.Background(), restrictedBotua.username, keybase1.TeamRole_RESTRICTEDBOT, &keybase1.TeamBotSettings{}) 64 require.NoError(t, err) 65 } 66 67 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 68 require.NoError(t, err) 69 70 teamObj = ann.loadTeam(team, true /* admin */) 71 require.Equal(t, 1, teamObj.NumActiveInvites()) 72 invites := teamObj.GetActiveAndObsoleteInvites() 73 require.Equal(t, 1, len(invites)) 74 for _, invite := range teamObj.GetActiveAndObsoleteInvites() { 75 uv, err := invite.KeybaseUserVersion() 76 require.NoError(t, err) 77 require.EqualValues(t, bob.userVersion(), uv) 78 } 79 80 members, err := teamObj.Members() 81 require.NoError(t, err) 82 require.Equal(t, 1, len(members.Owners)) 83 require.Equal(t, 0, len(members.Admins)) 84 require.Equal(t, 0, len(members.Writers)) 85 require.Equal(t, 1, len(members.Readers)) 86 require.EqualValues(t, tracy.userVersion(), members.Readers[0]) 87 require.Equal(t, 1, len(members.Bots)) 88 require.EqualValues(t, botua.userVersion(), members.Bots[0]) 89 require.Equal(t, 1, len(members.RestrictedBots)) 90 require.EqualValues(t, restrictedBotua.userVersion(), members.RestrictedBots[0]) 91 92 // TRANSACTION 2 - bob gets puk, add bob but not through SBS - we 93 // expect the invite to be sweeped away by this transaction. 94 95 bob.perUserKeyUpgrade() 96 97 teamObj = ann.loadTeam(team, true /* admin */) 98 tx = teams.CreateAddMemberTx(teamObj) 99 err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil) 100 require.NoError(t, err) 101 102 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 103 require.NoError(t, err) 104 105 teamObj = ann.loadTeam(team, true /* admin */) 106 members, err = teamObj.Members() 107 require.NoError(t, err) 108 require.Equal(t, 1, len(members.Owners)) 109 require.Equal(t, 0, len(members.Admins)) 110 require.Equal(t, 1, len(members.Writers)) 111 require.EqualValues(t, bob.userVersion(), members.Writers[0]) 112 require.Equal(t, 0, len(teamObj.GetActiveAndObsoleteInvites())) 113 require.Equal(t, 1, len(members.Readers)) 114 require.EqualValues(t, tracy.userVersion(), members.Readers[0]) 115 require.Equal(t, 1, len(members.Bots)) 116 require.EqualValues(t, botua.userVersion(), members.Bots[0]) 117 require.Equal(t, 1, len(members.RestrictedBots)) 118 require.EqualValues(t, restrictedBotua.userVersion(), members.RestrictedBots[0]) 119 } 120 121 func TestTeamTxAddByUsername(t *testing.T) { 122 testTeamTx1(t, false /* byUV */) 123 } 124 125 func TestTeamTxAddByUV(t *testing.T) { 126 testTeamTx1(t, true /* byUV */) 127 } 128 129 func TestTeamTxDependency(t *testing.T) { 130 tt := newTeamTester(t) 131 defer tt.cleanup() 132 133 ann := makeUserStandalone(t, tt, "ann", standaloneUserArgs{ 134 disableGregor: true, 135 suppressTeamChatAnnounce: true, 136 }) 137 t.Logf("Signed up ann (%s)", ann.username) 138 139 bob := tt.addPuklessUser("bob") 140 t.Logf("Signed up PUK-less user bob (%s)", bob.username) 141 142 tracy := tt.addUser("trc") 143 t.Logf("Signed up PUK-ful user trc (%s)", tracy.username) 144 145 team := ann.createTeam() 146 t.Logf("Team created (%s)", team) 147 148 ann.addTeamMember(team, bob.username, keybase1.TeamRole_WRITER) 149 150 teamObj := ann.loadTeam(team, true /* admin */) 151 members, err := teamObj.Members() 152 require.NoError(t, err) 153 require.Equal(t, 1, len(members.Owners)) 154 require.Equal(t, 0, len(members.Admins)+len(members.Writers)+len(members.Readers)+len(members.Bots)+len(members.RestrictedBots)) 155 require.EqualValues(t, ann.userVersion(), members.Owners[0]) 156 require.Equal(t, 1, teamObj.NumActiveInvites()) 157 158 bob.perUserKeyUpgrade() 159 160 // Transaction time! 161 162 // The transaction will try to achieve the following: 163 // 1) Add Tracy as crypto member, 164 // 2) sweep old bob@keybase invite (pukless member), 165 // 3) add bob as crypto member. 166 167 // The catch is that (3) depends on (2), so signature that does 168 // (3) has to happen after (2). Signatures in flight after (2) are 169 // as follows: 170 // 1. change_membership (adds: trc) 171 // 2. invite (cancel: bob@keybase) 172 173 // Adding bob as a crypto member should not mutate change_membership 1., 174 // but instead create new change_membership. 175 176 teamObj = ann.loadTeam(team, true /* admin */) 177 178 tx := teams.CreateAddMemberTx(teamObj) 179 err = tx.AddMemberByUsername(context.Background(), tracy.username, keybase1.TeamRole_READER, nil) 180 require.NoError(t, err) 181 err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil) 182 require.NoError(t, err) 183 184 payloads := tx.DebugPayloads() 185 require.Equal(t, 3, len(payloads)) 186 187 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 188 require.NoError(t, err) 189 190 // State is still fine even without ordering, because nor server 191 // neither team player cares about that. 192 193 teamObj = ann.loadTeam(team, true /* admin */) 194 members, err = teamObj.Members() 195 require.NoError(t, err) 196 require.Equal(t, 1, len(members.Owners)) 197 require.EqualValues(t, ann.userVersion(), members.Owners[0]) 198 require.Equal(t, 0, len(members.Admins)) 199 require.Equal(t, 1, len(members.Writers)) 200 require.EqualValues(t, bob.userVersion(), members.Writers[0]) 201 require.Equal(t, 1, len(members.Readers)) 202 require.EqualValues(t, tracy.userVersion(), members.Readers[0]) 203 require.Equal(t, 0, teamObj.NumActiveInvites()) 204 require.Equal(t, 0, len(teamObj.GetActiveAndObsoleteInvites())) 205 require.Equal(t, 0, len(members.Bots)) 206 require.Equal(t, 0, len(members.RestrictedBots)) 207 208 // Try the opposite logic: reset bob, and try to re-add them as 209 // pukless. The `invite` link should happen after crypto member 210 // sweeping `change_membership`. 211 bob.reset() 212 bob.loginAfterResetPukless() 213 214 tx = teams.CreateAddMemberTx(teamObj) 215 tx.AllowPUKless = true 216 _, _, _, err = tx.AddOrInviteMemberByAssertion(context.Background(), fmt.Sprintf("%s@rooter", tracy.username), keybase1.TeamRole_WRITER, nil) 217 require.NoError(t, err) 218 err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_WRITER, nil) 219 require.NoError(t, err) 220 221 payloads = tx.DebugPayloads() 222 require.Equal(t, 3, len(payloads)) 223 224 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 225 require.NoError(t, err) 226 } 227 228 func TestTeamTxSweepMembers(t *testing.T) { 229 tt := newTeamTester(t) 230 defer tt.cleanup() 231 232 ann := tt.addUser("ann") 233 t.Logf("Signed up user ann (%s)", ann.username) 234 235 bob := tt.addUser("bob") 236 t.Logf("Signed up user bob (%s)", bob.username) 237 238 pat := tt.addPuklessUser("pat") 239 t.Logf("Signed up PUKless user pat (%s)", pat.username) 240 241 team := ann.createTeam() 242 t.Logf("Team created (%s)", team) 243 244 ann.addTeamMember(team, bob.username, keybase1.TeamRole_WRITER) 245 246 bob.reset() 247 bob.loginAfterReset() 248 249 t.Logf("Bob (%s) resets and reprovisions, he is now: %v", bob.username, bob.userVersion()) 250 251 // Wait for CLKR and RotateKey link. 252 teamID := ann.loadTeam(team, false /* admin */).ID 253 ann.waitForAnyRotateByID(teamID, keybase1.Seqno(2) /* toSeqno */, keybase1.Seqno(1) /* toHiddenSeqno */) 254 255 teamObj := ann.loadTeam(team, true /* admin */) 256 tx := teams.CreateAddMemberTx(teamObj) 257 err := tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_READER, nil) 258 require.NoError(t, err) 259 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 260 require.NoError(t, err) 261 262 teamObj = ann.loadTeam(team, true /* admin */) 263 members, err := teamObj.Members() 264 require.NoError(t, err) 265 require.Equal(t, 1, len(members.Owners)) 266 require.Equal(t, 1, len(members.Readers)) 267 require.Equal(t, 0, len(members.Admins)+len(members.Writers)+len(members.Bots)+len(members.RestrictedBots)) 268 require.EqualValues(t, ann.userVersion(), members.Owners[0]) 269 require.EqualValues(t, bob.userVersion(), members.Readers[0]) 270 require.Equal(t, 0, len(teamObj.GetActiveAndObsoleteInvites())) 271 } 272 273 func TestTeamTxMultipleMembers(t *testing.T) { 274 tt := newTeamTester(t) 275 defer tt.cleanup() 276 277 ann := tt.addUser("ann") 278 t.Logf("Signed up user ann (%s)", ann.username) 279 280 // user 0 - ann, team owner 281 // user 1,2,3 - zzz, normal user 282 // user 4,5,6 - yyy, pukless user 283 284 for i := 0; i < 3; i++ { 285 user := tt.addUser("zzz") 286 t.Logf("Signed up normal user %d (%s, %v)", i, user.username, user.userVersion()) 287 } 288 289 for i := 0; i < 3; i++ { 290 user := tt.addPuklessUser("yyy") 291 t.Logf("Signed up pukless user %d (%s, %v)", i, user.username, user.userVersion()) 292 } 293 294 team := ann.createTeam() 295 t.Logf("Team created (%s)", team) 296 297 teamObj := ann.loadTeam(team, true /* admin */) 298 tx := teams.CreateAddMemberTx(teamObj) 299 tx.AllowPUKless = true 300 for i := 1; i < 7; i++ { 301 err := tx.AddMemberByUsername(context.Background(), tt.users[i].username, keybase1.TeamRole_WRITER, nil) 302 require.NoError(t, err) 303 } 304 err := tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 305 require.NoError(t, err) 306 307 for i := 4; i <= 5; i++ { 308 user := tt.users[i] 309 user.reset() 310 user.loginAfterReset() 311 t.Logf("Reset pukless user %d (%s, %v)", i, user.username, user.userVersion()) 312 } 313 314 teamObj = ann.loadTeam(team, true /* admin */) 315 tx = teams.CreateAddMemberTx(teamObj) 316 for i := 4; i <= 5; i++ { 317 err := tx.AddMemberByUsername(context.Background(), tt.users[i].username, keybase1.TeamRole_WRITER, nil) 318 require.NoError(t, err) 319 } 320 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 321 require.NoError(t, err) 322 323 teamObj = ann.loadTeam(team, true /* admin */) 324 members, err := teamObj.Members() 325 require.NoError(t, err) 326 require.Equal(t, 1, len(members.Owners)) 327 require.Equal(t, 5, len(members.Writers)) 328 require.Equal(t, 0, len(members.Readers)+len(members.Admins)+len(members.Bots)+len(members.RestrictedBots)) 329 330 invites := teamObj.GetActiveAndObsoleteInvites() 331 require.Equal(t, 1, len(invites)) 332 for _, invite := range invites { 333 uv, err := invite.KeybaseUserVersion() 334 require.NoError(t, err) 335 require.Equal(t, tt.users[6].userVersion(), uv) 336 } 337 } 338 339 func TestTeamTxSubteamAdmins(t *testing.T) { 340 // Test if AddMemberTx properly keys implicit admins to teams 341 // through the use of 'implicit_team_keys'. 342 343 tt := newTeamTester(t) 344 defer tt.cleanup() 345 346 ann := tt.addUser("ann") 347 t.Logf("Signed up user ann (%s)", ann.username) 348 349 bob := tt.addUser("bob") 350 t.Logf("Signed up user bob (%s)", bob.username) 351 352 team := ann.createTeam() 353 t.Logf("Team created (%s)", team) 354 355 teamName, err := keybase1.TeamNameFromString(team) 356 require.NoError(t, err) 357 _, err = teams.CreateSubteam(context.Background(), ann.tc.G, "golfers", teamName, keybase1.TeamRole_NONE /* addSelfAs */) 358 require.NoError(t, err) 359 _, err = teams.CreateSubteam(context.Background(), ann.tc.G, "pokerpals", teamName, keybase1.TeamRole_NONE /* addSelfAs */) 360 require.NoError(t, err) 361 362 teamObj := ann.loadTeam(team, true /* admin */) 363 tx := teams.CreateAddMemberTx(teamObj) 364 err = tx.AddMemberByUsername(context.Background(), bob.username, keybase1.TeamRole_ADMIN, nil) 365 require.NoError(t, err) 366 err = tx.Post(libkb.NewMetaContextForTest(*ann.tc)) 367 require.NoError(t, err) 368 } 369 370 func TestTeamTxBadAdds(t *testing.T) { 371 tt := newTeamTester(t) 372 defer tt.cleanup() 373 374 ann := tt.addUser("ann") 375 t.Logf("Signed up user ann (%s)", ann.username) 376 377 bob := tt.addUser("bob") 378 t.Logf("Signed up user bob (%s)", bob.username) 379 380 bobUV := bob.userVersion() 381 bob.reset() 382 383 team := ann.createTeam() 384 t.Logf("Team created (%s)", team) 385 386 teamObj := ann.loadTeam(team, true /* admin */) 387 tx := teams.CreateAddMemberTx(teamObj) 388 389 // Tring to add bob using old UV (from before reset) 390 err := tx.AddMemberByUV(context.Background(), bobUV, keybase1.TeamRole_WRITER, nil) 391 require.Error(t, err) 392 require.True(t, tx.IsEmpty()) 393 394 bob.loginAfterReset() 395 bobUV = bob.userVersion() 396 397 bob.delete() 398 399 // Trying to add deleted bob. 400 err = tx.AddMemberByUV(context.Background(), bobUV, keybase1.TeamRole_WRITER, nil) 401 require.Error(t, err) 402 require.IsType(t, libkb.UserDeletedError{}, err) 403 require.True(t, tx.IsEmpty()) 404 }