1// Copyright 2018 Keybase, Inc. All rights reserved. Use of 2// this source code is governed by the included BSD license. 3 4package teams 5 6import ( 7 "context" 8 "fmt" 9 "testing" 10 11 "github.com/keybase/client/go/emails" 12 "github.com/keybase/client/go/kbtest" 13 14 "github.com/keybase/client/go/externalstest" 15 "github.com/keybase/client/go/libkb" 16 keybase1 "github.com/keybase/client/go/protocol/keybase1" 17 "github.com/stretchr/testify/require" 18) 19 20func TestTransactions1(t *testing.T) { 21 tc, owner, other, _, name := memberSetupMultiple(t) 22 defer tc.Cleanup() 23 24 team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{ 25 Name: name, 26 NeedAdmin: true, 27 }) 28 require.NoError(t, err) 29 30 tx := CreateAddMemberTx(team) 31 tx.AllowPUKless = true 32 err = tx.AddMemberByUsername(context.Background(), "t_alice", keybase1.TeamRole_WRITER, nil) 33 require.NoError(t, err) 34 require.Equal(t, 1, len(tx.payloads)) 35 require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag) 36 require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val) 37 38 err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil) 39 require.NoError(t, err) 40 require.Equal(t, 2, len(tx.payloads)) 41 require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag) 42 require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val) 43 require.Equal(t, txPayloadTagCryptomembers, tx.payloads[1].Tag) 44 require.IsType(t, &keybase1.TeamChangeReq{}, tx.payloads[1].Val) 45 46 err = tx.AddMemberByUsername(context.Background(), "t_tracy", keybase1.TeamRole_ADMIN, nil) 47 require.NoError(t, err) 48 49 // 3rd add (pukless member) should re-use first signature instead 50 // of creating new one. 51 require.Equal(t, 2, len(tx.payloads)) 52 require.Equal(t, txPayloadTagInviteKeybase, tx.payloads[0].Tag) 53 require.IsType(t, &SCTeamInvites{}, tx.payloads[0].Val) 54 require.Equal(t, txPayloadTagCryptomembers, tx.payloads[1].Tag) 55 require.IsType(t, &keybase1.TeamChangeReq{}, tx.payloads[1].Val) 56 57 err = tx.Post(libkb.NewMetaContextForTest(tc)) 58 require.NoError(t, err) 59 60 team, err = Load(context.Background(), tc.G, keybase1.LoadTeamArg{ 61 Name: name, 62 NeedAdmin: true, 63 ForceRepoll: true, 64 }) 65 require.NoError(t, err) 66 67 members, err := team.Members() 68 require.NoError(t, err) 69 require.Equal(t, 1, len(members.Owners)) 70 require.Equal(t, owner.GetUserVersion(), members.Owners[0]) 71 require.Equal(t, 0, len(members.Admins)) 72 require.Equal(t, 1, len(members.Writers)) 73 require.Equal(t, other.GetUserVersion(), members.Writers[0]) 74 require.Equal(t, 0, len(members.Readers)) 75 require.Equal(t, 0, len(members.Bots)) 76 require.Equal(t, 0, len(members.RestrictedBots)) 77 78 invites := team.GetActiveAndObsoleteInvites() 79 require.Equal(t, 2, len(invites)) 80} 81 82func TestTransactionRotateKey(t *testing.T) { 83 tc, _, otherA, otherB, name := memberSetupMultiple(t) 84 defer tc.Cleanup() 85 86 loadTeam := func() *Team { 87 team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{ 88 Name: name, 89 NeedAdmin: true, 90 ForceRepoll: true, 91 }) 92 require.NoError(t, err) 93 return team 94 } 95 96 team := loadTeam() 97 err := team.ChangeMembership(context.Background(), keybase1.TeamChangeReq{ 98 Writers: []keybase1.UserVersion{otherA.GetUserVersion()}, 99 }) 100 require.NoError(t, err) 101 102 team = loadTeam() 103 require.EqualValues(t, 1, team.Generation()) 104 105 tx := CreateAddMemberTx(team) 106 // Create payloads manually so user add and user del happen in 107 // separate links. 108 tx.payloads = []txPayload{ 109 { 110 Tag: txPayloadTagCryptomembers, 111 Val: &keybase1.TeamChangeReq{ 112 Writers: []keybase1.UserVersion{otherB.GetUserVersion()}, 113 }, 114 }, 115 { 116 Tag: txPayloadTagCryptomembers, 117 Val: &keybase1.TeamChangeReq{ 118 None: []keybase1.UserVersion{otherA.GetUserVersion()}, 119 }, 120 }, 121 } 122 err = tx.Post(libkb.NewMetaContextForTest(tc)) 123 require.NoError(t, err) 124 125 // Also if the transaction didn't create new PerTeamKey, bunch of 126 // assertions would have failed on the server. It doesn't matter 127 // which link the PerTeamKey is attached to, because key coverage 128 // is checked for the entire transaction, not individual links, 129 // but we always attach it to the first ChangeMembership link with 130 // member removals. 131 team = loadTeam() 132 require.EqualValues(t, 2, team.Generation()) 133} 134 135func TestPreprocessAssertions(t *testing.T) { 136 tc := externalstest.SetupTest(t, "assertions", 0) 137 defer tc.Cleanup() 138 139 tests := []struct { 140 s string 141 isServerTrust bool 142 hasSingle bool 143 isError bool 144 }{ 145 {"bob", false, true, false}, 146 {"bob+bob@twitter", false, false, false}, 147 {"[bob@gmail.com]@email", true, true, false}, 148 {"[bob@gmail.com]@email+bob", false, false, true}, 149 {"18005558638@phone", true, true, false}, 150 {"18005558638@phone+alice", false, false, true}, 151 {"18005558638@phone+[bob@gmail.com]@email", false, false, true}, 152 } 153 for _, test := range tests { 154 t.Logf("Testing: %s", test.s) 155 isServerTrust, single, full, err := preprocessAssertion(libkb.NewMetaContextForTest(tc), test.s) 156 require.Equal(t, isServerTrust, test.isServerTrust) 157 require.Equal(t, (single != nil), test.hasSingle) 158 if test.isError { 159 require.Error(t, err) 160 require.Nil(t, full) 161 } else { 162 require.NoError(t, err) 163 require.NotNil(t, full) 164 } 165 } 166} 167 168func TestAllowPukless(t *testing.T) { 169 tc, _, other, teamname := setupPuklessInviteTest(t) 170 defer tc.Cleanup() 171 172 team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{ 173 Name: teamname, 174 NeedAdmin: true, 175 }) 176 require.NoError(t, err) 177 178 assertError := func(err error) { 179 require.Error(t, err) 180 require.IsType(t, err, UserPUKlessError{}) 181 require.Contains(t, err.Error(), other.Username) 182 require.Contains(t, err.Error(), other.GetUserVersion().String()) 183 } 184 185 tx := CreateAddMemberTx(team) 186 tx.AllowPUKless = false // explicitly disallow, but it's also the default. 187 err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */) 188 assertError(err) 189 190 err = tx.AddMemberByUV(context.Background(), other.GetUserVersion(), keybase1.TeamRole_WRITER, nil /* botSettings */) 191 assertError(err) 192 193 { 194 username, uv, invite, err := tx.AddOrInviteMemberByAssertion(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */) 195 assertError(err) 196 // All this stuff is still returned despite an error 197 require.Equal(t, other.NormalizedUsername(), username) 198 require.Equal(t, other.GetUserVersion(), uv) 199 // But we aren't actually "inviting" them because of transaction setting. 200 require.False(t, invite) 201 } 202 203 { 204 candidate, err := tx.ResolveUPKV2FromAssertion(tc.MetaContext(), other.Username) 205 require.NoError(t, err) 206 username, uv, invite, err := tx.AddOrInviteMemberCandidate(context.Background(), candidate, keybase1.TeamRole_WRITER, nil /* botSettings */) 207 assertError(err) 208 // All this stuff is still returned despite an error 209 require.Equal(t, other.NormalizedUsername(), username) 210 require.Equal(t, other.GetUserVersion(), uv) 211 // But we aren't actually "inviting" them because of transaction setting. 212 require.False(t, invite) 213 } 214} 215 216func TestPostAllowPUKless(t *testing.T) { 217 tc, _, other, teamname := setupPuklessInviteTest(t) 218 defer tc.Cleanup() 219 220 team, err := Load(context.Background(), tc.G, keybase1.LoadTeamArg{ 221 Name: teamname, 222 NeedAdmin: true, 223 }) 224 require.NoError(t, err) 225 226 tx := CreateAddMemberTx(team) 227 tx.AllowPUKless = true 228 err = tx.AddMemberByUsername(context.Background(), other.Username, keybase1.TeamRole_WRITER, nil /* botSettings */) 229 require.NoError(t, err) 230 231 // Disallow PUKless after we have already added a PUKless user. 232 tx.AllowPUKless = false 233 err = tx.Post(tc.MetaContext()) 234 require.Error(t, err) 235 // Make sure it's the error about AllowPUKless. 236 require.Contains(t, err.Error(), "AllowPUKless") 237} 238 239func TestTransactionRoleChanges(t *testing.T) { 240 tc := SetupTest(t, "team", 1) 241 defer tc.Cleanup() 242 243 tc.Tp.SkipSendingSystemChatMessages = true 244 245 user := kbtest.TCreateFakeUser(tc) 246 kbtest.TCreateFakeUser(tc) // owner 247 248 _, teamID := createTeam2(tc) 249 250 res, err := AddMemberByID(tc.Context(), tc.G, teamID, user.Username, keybase1.TeamRole_READER, 251 nil /* botSettings */, nil /* emailInviteMsg */) 252 require.NoError(t, err) 253 require.False(t, res.Invited) 254 255 team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 256 require.NoError(t, err) 257 258 tx := CreateAddMemberTx(team) 259 // Try to upgrade role without `AllowRoleChanges` first. 260 err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */) 261 require.Error(t, err) 262 require.IsType(t, libkb.ExistsError{}, err) 263 264 require.Len(t, tx.payloads, 0) // should not have changed transaction 265 require.NoError(t, tx.err) // should not be a permanent error 266 267 // Set `AllowRoleChanges`. 268 tx.AllowRoleChanges = true 269 270 // Trying to add with same role as current is still an error. 271 err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_READER, nil /* botSettings */) 272 require.Error(t, err) 273 require.IsType(t, libkb.ExistsError{}, err) 274 275 // We can set a different role though (READER -> WRITER) 276 err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */) 277 require.NoError(t, err) 278 279 err = tx.Post(tc.MetaContext()) 280 require.NoError(t, err) 281 282 // See if role change worked 283 team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 284 require.NoError(t, err) 285 role, err := team.MemberRole(tc.Context(), user.GetUserVersion()) 286 require.NoError(t, err) 287 require.Equal(t, keybase1.TeamRole_WRITER, role) 288 289 // Should be able to go the other way as well (WRITER -> READER). 290 tx = CreateAddMemberTx(team) 291 tx.AllowRoleChanges = true 292 err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_READER, nil /* botSettings */) 293 require.NoError(t, err) 294 295 err = tx.Post(tc.MetaContext()) 296 require.NoError(t, err) 297 298 // See if it worked. 299 team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 300 require.NoError(t, err) 301 role, err = team.MemberRole(tc.Context(), user.GetUserVersion()) 302 require.NoError(t, err) 303 require.Equal(t, keybase1.TeamRole_READER, role) 304 305 userLog := team.chain().inner.UserLog[user.GetUserVersion()] 306 require.Len(t, userLog, 3) 307} 308 309func TestTransactionEmailExists(t *testing.T) { 310 tc := SetupTest(t, "team", 1) 311 defer tc.Cleanup() 312 313 kbtest.TCreateFakeUser(tc) 314 _, teamID := createTeam2(tc) 315 316 randomEmail := kbtest.GenerateRandomEmailAddress() 317 err := InviteEmailPhoneMember(tc.Context(), tc.G, teamID, randomEmail.String(), "email", keybase1.TeamRole_WRITER) 318 require.NoError(t, err) 319 320 team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 321 require.NoError(t, err) 322 323 invite, err := team.chain().FindActiveInviteString(tc.MetaContext(), randomEmail.String(), "email") 324 require.NoError(t, err) 325 require.Equal(t, keybase1.TeamRole_WRITER, invite.Role) 326 327 tx := CreateAddMemberTx(team) 328 tx.AllowRoleChanges = true 329 330 assertion := fmt.Sprintf("[%s]@email", randomEmail) 331 332 // Check if we can catch this error and continue forward 333 _, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */) 334 require.Error(t, err) 335 require.IsType(t, libkb.ExistsError{}, err) 336 337 // Changing roles of an invite using AddMemberTx is not possible right now. 338 _, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_READER, nil /* botSettings */) 339 require.Error(t, err) 340 require.IsType(t, libkb.ExistsError{}, err) 341 342 // Two errors above should not have tainted the transaction. 343 require.Len(t, tx.payloads, 0) 344 require.NoError(t, tx.err) 345} 346 347func TestTransactionResolvableEmailExists(t *testing.T) { 348 // Similar test but with resolvable email. 349 tc := SetupTest(t, "team", 1) 350 defer tc.Cleanup() 351 352 user := kbtest.TCreateFakeUser(tc) 353 354 // Add and verify email address. 355 usersEmail := kbtest.GenerateRandomEmailAddress() 356 err := emails.AddEmail(tc.MetaContext(), usersEmail, keybase1.IdentityVisibility_PUBLIC) 357 require.NoError(t, err) 358 err = kbtest.VerifyEmailAuto(tc.MetaContext(), usersEmail) 359 require.NoError(t, err) 360 361 kbtest.TCreateFakeUser(tc) // owner 362 363 _, teamID := createTeam2(tc) 364 team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 365 require.NoError(t, err) 366 367 assertion := fmt.Sprintf("[%s]@email", usersEmail) 368 369 // Invite email for the first time, should resolve and add user. 370 tx := CreateAddMemberTx(team) 371 372 username, uv, invited, err := tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */) 373 require.NoError(t, err) 374 require.Equal(t, user.NormalizedUsername(), username) 375 require.Equal(t, user.GetUserVersion(), uv) 376 require.False(t, invited) 377 378 err = tx.Post(tc.MetaContext()) 379 require.NoError(t, err) 380 381 // Ensure they were added as member (team.MemberRole). 382 team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 383 require.NoError(t, err) 384 role, err := team.MemberRole(tc.Context(), user.GetUserVersion()) 385 require.NoError(t, err) 386 require.Equal(t, keybase1.TeamRole_WRITER, role) 387 // And that e-mail wasn't added as invite. 388 hasInvite, err := team.HasActiveInvite(tc.MetaContext(), keybase1.TeamInviteName(usersEmail), "email") 389 require.NoError(t, err) 390 require.False(t, hasInvite) 391 392 // Try again, should fail. 393 tx = CreateAddMemberTx(team) 394 _, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */) 395 require.Error(t, err) 396 require.IsType(t, libkb.ExistsError{}, err) 397 398 require.Len(t, tx.payloads, 0) 399 require.NoError(t, tx.err) 400 401 // Role changes are possible with `AllowRoleChanges` because they are 402 // crypto-member. 403 tx = CreateAddMemberTx(team) 404 tx.AllowRoleChanges = true 405 _, _, _, err = tx.AddOrInviteMemberByAssertion(tc.Context(), assertion, keybase1.TeamRole_READER, nil /* botSettings */) 406 require.NoError(t, err) 407 408 err = tx.Post(tc.MetaContext()) 409 require.NoError(t, err) 410 411 team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 412 require.NoError(t, err) 413 role, err = team.MemberRole(tc.Context(), user.GetUserVersion()) 414 require.NoError(t, err) 415 require.Equal(t, keybase1.TeamRole_READER, role) 416} 417 418func TestTransactionAddEmailPukless(t *testing.T) { 419 // Add e-mail that resolves to a PUK-less user. 420 421 fus, tcs, cleanup := setupNTestsWithPukless(t, 2, 1) 422 defer cleanup() 423 424 usersEmail := kbtest.GenerateRandomEmailAddress() 425 err := emails.AddEmail(tcs[1].MetaContext(), usersEmail, keybase1.IdentityVisibility_PUBLIC) 426 require.NoError(t, err) 427 err = kbtest.VerifyEmailAuto(tcs[1].MetaContext(), usersEmail) 428 require.NoError(t, err) 429 430 _, teamID := createTeam2(*tcs[0]) 431 team, err := GetForTeamManagementByTeamID(tcs[0].Context(), tcs[0].G, teamID, true /* needAdmin */) 432 require.NoError(t, err) 433 434 assertion := fmt.Sprintf("[%s]@email", usersEmail) 435 436 tx := CreateAddMemberTx(team) 437 // Can't add without AllowPUKless. 438 _, _, _, err = tx.AddOrInviteMemberByAssertion(tcs[0].Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */) 439 require.Error(t, err) 440 require.IsType(t, UserPUKlessError{}, err) 441 442 // Failure to add should have left the transaction unmodified. 443 require.Len(t, tx.payloads, 0) 444 require.NoError(t, tx.err) 445 446 tx.AllowPUKless = true 447 username, uv, invited, err := tx.AddOrInviteMemberByAssertion(tcs[0].Context(), assertion, keybase1.TeamRole_WRITER, nil /* botSettings */) 448 require.NoError(t, err) 449 require.True(t, invited) 450 require.Equal(t, fus[1].NormalizedUsername(), username) 451 require.Equal(t, fus[1].GetUserVersion(), uv) 452 453 err = tx.Post(tcs[0].MetaContext()) 454 require.NoError(t, err) 455 456 team, err = GetForTeamManagementByTeamID(tcs[0].Context(), tcs[0].G, teamID, true /* needAdmin */) 457 require.NoError(t, err) 458 _, uv, found := team.FindActiveKeybaseInvite(fus[1].GetUID()) 459 require.True(t, found) 460 require.Equal(t, fus[1].GetUserVersion(), uv) 461 462 found, err = team.HasActiveInvite(tcs[0].MetaContext(), keybase1.TeamInviteName(usersEmail), "email") 463 require.NoError(t, err) 464 require.False(t, found) 465} 466 467func TestTransactionDowngradeAdmin(t *testing.T) { 468 tc := SetupTest(t, "team", 1) 469 defer tc.Cleanup() 470 471 user := kbtest.TCreateFakeUser(tc) 472 kbtest.TCreateFakeUser(tc) // owner 473 474 _, teamID := createTeam2(tc) 475 476 // Add user as admin. 477 res, err := AddMemberByID(tc.Context(), tc.G, teamID, user.Username, keybase1.TeamRole_ADMIN, 478 nil /* botSettings */, nil /* emailInviteMsg */) 479 require.NoError(t, err) 480 require.False(t, res.Invited) 481 482 // Load team, change role of user to writer (from admin). 483 team, err := GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 484 require.NoError(t, err) 485 486 memberRole, err := team.MemberRole(tc.Context(), user.GetUserVersion()) 487 require.NoError(t, err) 488 require.Equal(t, keybase1.TeamRole_ADMIN, memberRole) 489 490 tx := CreateAddMemberTx(team) 491 tx.AllowRoleChanges = true 492 err = tx.AddMemberByUsername(tc.Context(), user.Username, keybase1.TeamRole_WRITER, nil /* botSettings */) 493 require.NoError(t, err) 494 require.Len(t, tx.payloads, 1) 495 496 err = tx.Post(tc.MetaContext()) 497 require.NoError(t, err) 498 499 // See if it worked. 500 team, err = GetForTeamManagementByTeamID(tc.Context(), tc.G, teamID, true /* needAdmin */) 501 require.NoError(t, err) 502 503 memberRole, err = team.MemberRole(tc.Context(), user.GetUserVersion()) 504 require.NoError(t, err) 505 require.Equal(t, keybase1.TeamRole_WRITER, memberRole) 506} 507