github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/systests/teambot_test.go (about) 1 package systests 2 3 import ( 4 "testing" 5 "time" 6 7 "github.com/keybase/client/go/libkb" 8 "github.com/keybase/client/go/protocol/gregor1" 9 keybase1 "github.com/keybase/client/go/protocol/keybase1" 10 "github.com/keybase/client/go/teambot" 11 "github.com/keybase/client/go/teams" 12 "github.com/keybase/clockwork" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func checkNewTeambotKeyNotifications(tc *libkb.TestContext, notifications *teamNotifyHandler, 17 expectedArgs []keybase1.NewTeambotKeyArg) { 18 matches := map[keybase1.NewTeambotKeyArg]struct{}{} 19 numFound := 0 20 for { 21 select { 22 case arg := <-notifications.newTeambotKeyCh: 23 for _, expectedArg := range expectedArgs { 24 if expectedArg == arg { 25 matches[arg] = struct{}{} 26 break 27 } 28 } 29 // make don't have any unexpected notifications 30 if len(matches) <= numFound { 31 require.Fail(tc.T, "unexpected newTeamKeyNeeded notification", arg) 32 } 33 if len(matches) == len(expectedArgs) { 34 return 35 } 36 numFound++ 37 case <-time.After(5 * time.Second * libkb.CITimeMultiplier(tc.G)): 38 require.Fail(tc.T, "no notification on newTeambotKey") 39 } 40 } 41 } 42 43 func checkTeambotKeyNeededNotifications(tc *libkb.TestContext, notifications *teamNotifyHandler, 44 expectedArg keybase1.TeambotKeyNeededArg) { 45 select { 46 case arg := <-notifications.teambotKeyNeededCh: 47 require.Equal(tc.T, expectedArg, arg) 48 return 49 case <-time.After(5 * time.Second * libkb.CITimeMultiplier(tc.G)): 50 require.Fail(tc.T, "no notification on teambotKeyNeeded") 51 } 52 } 53 54 func noNewTeambotKeyNotification(tc *libkb.TestContext, notifications *teamNotifyHandler) { 55 select { 56 case arg := <-notifications.newTeambotKeyCh: 57 require.Fail(tc.T, "unexpected newTeambotKey notification", arg) 58 default: 59 } 60 } 61 62 func noTeambotKeyNeeded(tc *libkb.TestContext, notifications *teamNotifyHandler) { 63 select { 64 case arg := <-notifications.teambotKeyNeededCh: 65 require.Fail(tc.T, "unexpected teambotKeyNeeded notification", arg) 66 default: 67 } 68 } 69 70 func TestTeambotKey(t *testing.T) { 71 tt := newTeamTester(t) 72 defer tt.cleanup() 73 74 fc := clockwork.NewFakeClockAt(time.Now()) 75 76 user1 := tt.addUser("one") 77 user2 := tt.addUserWithPaper("two") 78 botua := tt.addUser("botua") 79 botuaUID := gregor1.UID(botua.uid.ToBytes()) 80 mctx1 := libkb.NewMetaContextForTest(*user1.tc) 81 mctx2 := libkb.NewMetaContextForTest(*user2.tc) 82 mctx3 := libkb.NewMetaContextForTest(*botua.tc) 83 memberKeyer1 := mctx1.G().GetTeambotMemberKeyer() 84 memberKeyer2 := mctx2.G().GetTeambotMemberKeyer() 85 botKeyer := mctx3.G().GetTeambotBotKeyer().(*teambot.BotKeyer) 86 botKeyer.SetClock(fc) 87 88 teamID, teamName := user1.createTeam2() 89 user1.addTeamMember(teamName.String(), user2.username, keybase1.TeamRole_WRITER) 90 user1.addRestrictedBotTeamMember(teamName.String(), botua.username, keybase1.TeamBotSettings{}) 91 92 // bot gets a key on addition to the team 93 newKeyArgs := []keybase1.NewTeambotKeyArg{ 94 { 95 Id: teamID, 96 Generation: 1, 97 Application: keybase1.TeamApplication_CHAT, 98 }, 99 { 100 Id: teamID, 101 Generation: 1, 102 Application: keybase1.TeamApplication_KVSTORE, 103 }, 104 } 105 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 106 107 // grab the latest chat application key and make sure the generation lines 108 // up with the teambotKey 109 team, err := teams.Load(mctx1.Ctx(), mctx1.G(), keybase1.LoadTeamArg{ 110 ID: teamID, 111 ForceRepoll: true, 112 }) 113 require.NoError(t, err) 114 appKey1, err := team.ChatKey(mctx1.Ctx()) 115 require.NoError(t, err) 116 117 // now created = false since we published on member addition 118 teambotKey, created, err := memberKeyer1.GetOrCreateTeambotKey(mctx1, teamID, botuaUID, appKey1) 119 require.NoError(t, err) 120 require.False(t, created) 121 require.Equal(t, appKey1.Generation(), teambotKey.Generation()) 122 123 teambotKey2, err := botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 124 require.NoError(t, err) 125 require.Equal(t, teambotKey, teambotKey2) 126 127 // delete the initial key to check regeneration flows 128 err = teambot.DeleteTeambotKeyForTest(mctx3, teamID, keybase1.TeamApplication_CHAT, 129 teambotKey.Metadata.Generation) 130 131 require.NoError(t, err) 132 133 // initial get, bot has no key to access 134 _, err = botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 135 require.IsType(t, teambot.TeambotTransientKeyError{}, err) 136 137 // cry for help has been issued. 138 keyNeededArg := keybase1.TeambotKeyNeededArg{ 139 Id: teamID, 140 Uid: botua.uid, 141 Generation: 1, 142 Application: keybase1.TeamApplication_CHAT, 143 } 144 checkTeambotKeyNeededNotifications(user1.tc, user1.notifications, keyNeededArg) 145 checkTeambotKeyNeededNotifications(user2.tc, user2.notifications, keyNeededArg) 146 147 // and answered. 148 newKeyArgs = []keybase1.NewTeambotKeyArg{{ 149 Id: teamID, 150 Generation: 1, 151 Application: keybase1.TeamApplication_CHAT, 152 }} 153 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 154 155 // bot can access the key 156 teambotKey2, err = botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 157 require.NoError(t, err) 158 require.Equal(t, teambotKey, teambotKey2) 159 noTeambotKeyNeeded(user1.tc, user1.notifications) 160 noTeambotKeyNeeded(user2.tc, user2.notifications) 161 noNewTeambotKeyNotification(botua.tc, botua.notifications) 162 163 // check for wrong application 164 _, err = botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_KBFS) 165 require.IsType(t, teambot.TeambotTransientKeyError{}, err) 166 167 // cry for help has been issued. 168 keyNeededArg = keybase1.TeambotKeyNeededArg{ 169 Id: teamID, 170 Uid: botua.uid, 171 Generation: 1, 172 Application: keybase1.TeamApplication_KBFS, 173 } 174 checkTeambotKeyNeededNotifications(user1.tc, user1.notifications, keyNeededArg) 175 checkTeambotKeyNeededNotifications(user2.tc, user2.notifications, keyNeededArg) 176 177 // and answered. 178 newKeyArgs = []keybase1.NewTeambotKeyArg{{ 179 Id: teamID, 180 Generation: 1, 181 Application: keybase1.TeamApplication_KBFS, 182 }} 183 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 184 185 _, err = botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_KBFS) 186 require.NoError(t, err) 187 noTeambotKeyNeeded(user1.tc, user1.notifications) 188 noTeambotKeyNeeded(user2.tc, user2.notifications) 189 noNewTeambotKeyNotification(botua.tc, botua.notifications) 190 191 // Test the AtGeneration flow 192 teambotKey2b, err := botKeyer.GetTeambotKeyAtGeneration(mctx3, teamID, 193 keybase1.TeamApplication_CHAT, teambotKey2.Metadata.Generation) 194 require.NoError(t, err) 195 require.Equal(t, teambotKey2, teambotKey2b) 196 noTeambotKeyNeeded(user1.tc, user1.notifications) 197 noTeambotKeyNeeded(user2.tc, user2.notifications) 198 noNewTeambotKeyNotification(botua.tc, botua.notifications) 199 200 // force a PTK rotation 201 user2.revokePaperKey() 202 user1.waitForRotateByID(teamID, keybase1.Seqno(4)) 203 204 // bot gets a new key on rotation 205 newKeyArgs = []keybase1.NewTeambotKeyArg{ 206 { 207 Id: teamID, 208 Generation: 2, 209 Application: keybase1.TeamApplication_CHAT, 210 }, 211 { 212 Id: teamID, 213 Generation: 2, 214 Application: keybase1.TeamApplication_KVSTORE, 215 }, 216 } 217 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 218 219 // delete to check regeneration flow 220 err = teambot.DeleteTeambotKeyForTest(mctx3, teamID, keybase1.TeamApplication_CHAT, 2) 221 require.NoError(t, err) 222 223 // Force a wrongKID error on the bot user by expiring the wrongKID cache 224 key := teambot.TeambotKeyWrongKIDCacheKey(teamID, botua.uid, teambotKey2.Metadata.Generation, 225 keybase1.TeamApplication_CHAT) 226 expired := keybase1.ToTime(fc.Now()) 227 err = mctx3.G().GetKVStore().PutObj(key, nil, expired) 228 require.NoError(t, err) 229 permitted, ctime, err := teambot.TeambotKeyWrongKIDPermitted(mctx3, teamID, botua.uid, 230 keybase1.TeamApplication_CHAT, teambotKey2.Metadata.Generation, keybase1.ToTime(fc.Now())) 231 require.NoError(t, err) 232 require.True(t, permitted) 233 require.Equal(t, expired, ctime) 234 235 fc.Advance(teambot.MaxTeambotKeyWrongKIDPermitted) // expire wrong KID cache 236 permitted, ctime, err = teambot.TeambotKeyWrongKIDPermitted(mctx3, teamID, botua.uid, 237 keybase1.TeamApplication_CHAT, teambotKey2.Metadata.Generation, keybase1.ToTime(fc.Now())) 238 require.NoError(t, err) 239 require.False(t, permitted) 240 require.Equal(t, expired, ctime) 241 242 _, err = botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 243 require.IsType(t, teambot.TeambotPermanentKeyError{}, err) 244 require.False(t, created) 245 keyNeededArg = keybase1.TeambotKeyNeededArg{ 246 Id: teamID, 247 Uid: botua.uid, 248 Generation: teambotKey2.Metadata.Generation + 1, 249 Application: keybase1.TeamApplication_CHAT, 250 } 251 checkTeambotKeyNeededNotifications(user1.tc, user1.notifications, keyNeededArg) 252 checkTeambotKeyNeededNotifications(user2.tc, user2.notifications, keyNeededArg) 253 newKeyArgs = []keybase1.NewTeambotKeyArg{{ 254 Id: teamID, 255 Generation: teambotKey2.Metadata.Generation + 1, 256 Application: keybase1.TeamApplication_CHAT, 257 }} 258 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 259 260 teambotKey3, err := botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 261 require.NoError(t, err) 262 require.Equal(t, teambotKey3.Metadata.Generation, teambotKey2.Metadata.Generation+1) 263 require.Equal(t, keybase1.TeamApplication_CHAT, teambotKey3.Metadata.Application) 264 265 // another PTK rotation happens, this time the bot can proceed with a key 266 // signed by the old PTK since the wrongKID cache did not expire 267 user1.removeTeamMember(teamName.String(), user2.username) 268 user1.addTeamMember(teamName.String(), user2.username, keybase1.TeamRole_WRITER) 269 user2.waitForNewlyAddedToTeamByID(teamID) 270 botua.waitForNewlyAddedToTeamByID(teamID) 271 272 newKeyArgs = []keybase1.NewTeambotKeyArg{ 273 { 274 Id: teamID, 275 Generation: 3, 276 Application: keybase1.TeamApplication_CHAT, 277 }, 278 { 279 Id: teamID, 280 Generation: 3, 281 Application: keybase1.TeamApplication_KVSTORE, 282 }, 283 } 284 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 285 286 err = teambot.DeleteTeambotKeyForTest(mctx3, teamID, keybase1.TeamApplication_CHAT, 3) 287 require.NoError(t, err) 288 289 // bot can access the old teambotKey, but asks for a new one to 290 // be created since it was signed by the old PTK 291 teambotKey4, err := botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 292 require.NoError(t, err) 293 require.Equal(t, teambotKey3, teambotKey4) 294 keyNeededArg = keybase1.TeambotKeyNeededArg{ 295 Id: teamID, 296 Uid: botua.uid, 297 Generation: teambotKey4.Metadata.Generation + 1, 298 Application: keybase1.TeamApplication_CHAT, 299 } 300 checkTeambotKeyNeededNotifications(user1.tc, user1.notifications, keyNeededArg) 301 checkTeambotKeyNeededNotifications(user2.tc, user2.notifications, keyNeededArg) 302 303 newKeyArgs = []keybase1.NewTeambotKeyArg{{ 304 Id: teamID, 305 Generation: teambotKey4.Metadata.Generation + 1, 306 Application: keybase1.TeamApplication_CHAT, 307 }} 308 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 309 310 team, err = teams.Load(mctx1.Ctx(), mctx1.G(), keybase1.LoadTeamArg{ 311 ID: teamID, 312 ForceRepoll: true, 313 }) 314 require.NoError(t, err) 315 appKey2, err := team.ApplicationKey(mctx1.Ctx(), keybase1.TeamApplication_CHAT) 316 require.NoError(t, err) 317 teambotKey, _, err = memberKeyer1.GetOrCreateTeambotKey(mctx1, teamID, botuaUID, appKey2) 318 require.NoError(t, err) 319 require.Equal(t, appKey1.Generation()+2, teambotKey.Generation()) 320 321 teambotKey2, err = botKeyer.GetLatestTeambotKey(mctx3, teamID, keybase1.TeamApplication_CHAT) 322 require.NoError(t, err) 323 require.Equal(t, teambotKey, teambotKey2) 324 noTeambotKeyNeeded(user1.tc, user1.notifications) 325 noTeambotKeyNeeded(user2.tc, user2.notifications) 326 noNewTeambotKeyNotification(botua.tc, botua.notifications) 327 328 // kill the cache and make sure we don't republish 329 memberKeyer1.PurgeCache(mctx1) 330 teambotKeyNoCache, created, err := memberKeyer1.GetOrCreateTeambotKey(mctx1, teamID, botuaUID, appKey2) 331 require.NoError(t, err) 332 // created is True since we attempt to publish but the generation remains 333 require.True(t, created) 334 require.Equal(t, teambotKey.Metadata.Generation, teambotKeyNoCache.Metadata.Generation) 335 require.Equal(t, keybase1.TeamApplication_CHAT, teambotKey.Metadata.Application) 336 337 // Make sure we can access the teambotKey at various generations 338 for i := keybase1.TeambotKeyGeneration(1); i < teambotKey.Metadata.Generation; i++ { 339 teambotKeyBot, err := botKeyer.GetTeambotKeyAtGeneration(mctx3, teamID, keybase1.TeamApplication_CHAT, i) 340 require.NoError(t, err) 341 noTeambotKeyNeeded(user1.tc, user1.notifications) 342 noTeambotKeyNeeded(user2.tc, user2.notifications) 343 noNewTeambotKeyNotification(botua.tc, botua.notifications) 344 345 appKey, err := team.ApplicationKeyAtGeneration(mctx1.Ctx(), keybase1.TeamApplication_CHAT, keybase1.PerTeamKeyGeneration(i)) 346 require.NoError(t, err) 347 348 teambotKeyNonBot1, _, err := memberKeyer1.GetOrCreateTeambotKey(mctx1, teamID, botuaUID, appKey) 349 require.NoError(t, err) 350 require.Equal(t, teambotKeyBot, teambotKeyNonBot1) 351 352 teambotKeyNonBot2, _, err := memberKeyer2.GetOrCreateTeambotKey(mctx2, teamID, botuaUID, appKey) 353 require.NoError(t, err) 354 require.Equal(t, teambotKeyBot, teambotKeyNonBot2) 355 } 356 357 // bot asks for a non-existent generation, no new key is created. 358 badGen := teambotKey.Metadata.Generation + 50 359 _, err = botKeyer.GetTeambotKeyAtGeneration(mctx3, teamID, keybase1.TeamApplication_CHAT, badGen) 360 require.IsType(t, teambot.TeambotTransientKeyError{}, err) 361 keyNeededArg = keybase1.TeambotKeyNeededArg{ 362 Id: teamID, 363 Uid: botua.uid, 364 Generation: badGen, 365 Application: keybase1.TeamApplication_CHAT, 366 } 367 checkTeambotKeyNeededNotifications(user1.tc, user1.notifications, keyNeededArg) 368 checkTeambotKeyNeededNotifications(user2.tc, user2.notifications, keyNeededArg) 369 noNewTeambotKeyNotification(botua.tc, botua.notifications) 370 } 371 372 func TestTeambotKeyRemovedMember(t *testing.T) { 373 tt := newTeamTester(t) 374 defer tt.cleanup() 375 376 user1 := tt.addUser("one") 377 botua := tt.addUser("botua") 378 botuaUID := gregor1.UID(botua.uid.ToBytes()) 379 mctx1 := libkb.NewMetaContextForTest(*user1.tc) 380 ekLib1 := mctx1.G().GetEKLib() 381 memberKeyer1 := mctx1.G().GetTeambotMemberKeyer() 382 383 teamID, teamName := user1.createTeam2() 384 user1.addRestrictedBotTeamMember(teamName.String(), botua.username, keybase1.TeamBotSettings{}) 385 newKeyArgs := []keybase1.NewTeambotKeyArg{ 386 { 387 Id: teamID, 388 Generation: 1, 389 Application: keybase1.TeamApplication_CHAT, 390 }, 391 { 392 Id: teamID, 393 Generation: 1, 394 Application: keybase1.TeamApplication_KVSTORE, 395 }, 396 } 397 checkNewTeambotKeyNotifications(botua.tc, botua.notifications, newKeyArgs) 398 user1.removeTeamMember(teamName.String(), botua.username) 399 400 team, err := teams.Load(mctx1.Ctx(), mctx1.G(), keybase1.LoadTeamArg{ 401 ID: teamID, 402 ForceRepoll: true, 403 }) 404 require.NoError(t, err) 405 appKey, err := team.ChatKey(mctx1.Ctx()) 406 require.NoError(t, err) 407 408 _, created, err := memberKeyer1.GetOrCreateTeambotKey(mctx1, teamID, botuaUID, appKey) 409 require.False(t, created) 410 require.NoError(t, err) 411 noNewTeambotKeyNotification(botua.tc, botua.notifications) 412 413 err = ekLib1.KeygenIfNeeded(mctx1) 414 require.NoError(t, err) 415 _, created, err = ekLib1.GetOrCreateLatestTeambotEK(mctx1, teamID, botuaUID) 416 require.False(t, created) 417 require.NoError(t, err) 418 noNewTeambotEKNotification(botua.tc, botua.notifications) 419 }