github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/service_helper.go (about) 1 package teams 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 "time" 8 9 "golang.org/x/net/context" 10 11 "github.com/keybase/client/go/avatars" 12 email_utils "github.com/keybase/client/go/emails" 13 "github.com/keybase/client/go/engine" 14 "github.com/keybase/client/go/externals" 15 "github.com/keybase/client/go/kbun" 16 "github.com/keybase/client/go/libkb" 17 "github.com/keybase/client/go/protocol/keybase1" 18 ) 19 20 func LoadTeamPlusApplicationKeys(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, 21 application keybase1.TeamApplication, refreshers keybase1.TeamRefreshers, includeKBFSKeys bool) (res keybase1.TeamPlusApplicationKeys, err error) { 22 23 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 24 ID: id, 25 Public: id.IsPublic(), // infer publicness from id 26 Refreshers: refreshers, 27 }) 28 if err != nil { 29 return res, err 30 } 31 return team.ExportToTeamPlusApplicationKeys(ctx, keybase1.Time(0), application, includeKBFSKeys) 32 } 33 34 // GetAnnotatedTeam bundles up various data, both on and off chain, about a specific team for 35 // consumption by the UI. In particular, it supplies almost all of the information on a team's 36 // subpage in the Teams tab. 37 // It always repolls to ensure latest version of a team, but member infos (username, full name, if 38 // they reset or not) are subject to UIDMapper caching. 39 func GetAnnotatedTeam(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID) (res keybase1.AnnotatedTeam, err error) { 40 tracer := g.CTimeTracer(ctx, "GetAnnotatedTeam", true) 41 defer tracer.Finish() 42 43 mctx := libkb.NewMetaContext(ctx, g) 44 45 tracer.Stage("load team") 46 t, err := GetMaybeAdminByID(ctx, g, teamID, teamID.IsPublic()) 47 if err != nil { 48 return res, err 49 } 50 51 settings := t.Settings() 52 53 tracer.Stage("members & invites") 54 members, annotatedInvites, err := GetAnnotatedInvitesAndMembersForUI(mctx, t) 55 if err != nil { 56 return res, err 57 } 58 members = membersFilterDeletedUsers(mctx.Ctx(), mctx.G(), members) 59 members = membersHideInactiveDuplicates(mctx.Ctx(), mctx.G(), members) 60 61 var transitiveSubteamsUnverified keybase1.SubteamListResult 62 var joinRequests []keybase1.TeamJoinRequest 63 var tarsDisabled bool 64 var showcase keybase1.TeamShowcase 65 if !t.IsImplicit() { 66 if settings.Open { 67 g.Log.CDebugf(ctx, "GetAnnotatedTeam: %q is an open team, filtering reset writers and readers", t.Name().String()) 68 members = keybase1.FilterInactiveReadersWriters(members) 69 } 70 71 tracer.Stage("transitive subteams") 72 transitiveSubteamsUnverified, err = ListSubteamsUnverified(mctx, t.Name()) 73 if err != nil { 74 return res, err 75 } 76 77 teamNameStr := t.Name().String() 78 myRole, err := t.myRole(ctx) 79 if err != nil { 80 return res, err 81 } 82 if myRole == keybase1.TeamRole_NONE || myRole.IsOrAbove(keybase1.TeamRole_ADMIN) { 83 // If we are an implicit admin, our role is NONE. Note that we would not be 84 // this far if we are not a member of that team - we would have failed with a 85 // membership error during team loading at the beginning of this function. 86 joinRequests, err = ListRequests(ctx, g, &teamNameStr) 87 if err != nil { 88 mctx.Debug("GetAnnotatedTeam: failed to load access requests: %s", err) 89 } 90 tarsDisabled, err = GetTarsDisabled(ctx, g, teamID) 91 if err != nil { 92 mctx.Debug("GetAnnotatedTeam: failed to load disabled TARs setting: %s", err) 93 } 94 } 95 96 showcase, err = GetTeamShowcase(ctx, g, teamID) 97 if err != nil { 98 return res, err 99 } 100 } 101 102 return keybase1.AnnotatedTeam{ 103 TeamID: teamID, 104 Name: t.Name().String(), 105 TransitiveSubteamsUnverified: transitiveSubteamsUnverified, 106 Members: members, 107 Invites: annotatedInvites, 108 Settings: settings, 109 JoinRequests: joinRequests, 110 TarsDisabled: tarsDisabled, 111 KeyGeneration: t.Generation(), 112 Showcase: showcase, 113 }, nil 114 } 115 116 func GetAnnotatedTeamByName(ctx context.Context, g *libkb.GlobalContext, teamName string) (res keybase1.AnnotatedTeam, err error) { 117 nameParsed, err := keybase1.TeamNameFromString(teamName) 118 if err != nil { 119 return res, err 120 } 121 teamID, err := ResolveNameToID(ctx, g, nameParsed) 122 if err != nil { 123 return res, err 124 } 125 return GetAnnotatedTeam(ctx, g, teamID) 126 } 127 128 func Details(ctx context.Context, g *libkb.GlobalContext, name string) (res keybase1.TeamDetails, err error) { 129 t, err := GetAnnotatedTeamByName(ctx, g, name) 130 if err != nil { 131 return res, err 132 } 133 return t.ToLegacyTeamDetails(), nil 134 } 135 136 func membersFilterDeletedUsers(ctx context.Context, g *libkb.GlobalContext, members []keybase1.TeamMemberDetails) (ret []keybase1.TeamMemberDetails) { 137 for _, member := range members { 138 if member.Status != keybase1.TeamMemberStatus_DELETED { 139 ret = append(ret, member) 140 } else { 141 g.Log.CDebugf(ctx, "membersHideDeletedUsers filtered out row: %v %v", member.Uv, member.Status) 142 } 143 } 144 return ret 145 } 146 147 // If a UID appears multiple times with different TeamMemberStatus, only show the 'ACTIVE' version. 148 // This can happen when an owner resets and is re-added by an admin (though the admin could 149 // choose to remove the old owner if they so wished). 150 func membersHideInactiveDuplicates(ctx context.Context, g *libkb.GlobalContext, members []keybase1.TeamMemberDetails) (ret []keybase1.TeamMemberDetails) { 151 seenActive := make(map[keybase1.UID]bool) 152 // Scan for active rows 153 for _, member := range members { 154 if member.Status == keybase1.TeamMemberStatus_ACTIVE { 155 seenActive[member.Uv.Uid] = true 156 } 157 } 158 // Filter out superseded inactive rows 159 for _, member := range members { 160 if member.Status == keybase1.TeamMemberStatus_ACTIVE || !seenActive[member.Uv.Uid] { 161 ret = append(ret, member) 162 } else { 163 g.Log.CDebugf(ctx, "membersHideInactiveDuplicates filtered out row: %v %v", member.Uv, member.Status) 164 } 165 } 166 return ret 167 } 168 169 func MembersDetails(ctx context.Context, g *libkb.GlobalContext, t *Team) (ret []keybase1.TeamMemberDetails, err error) { 170 mctx := libkb.NewMetaContext(ctx, g) 171 m, err := t.Members() 172 if err != nil { 173 return nil, err 174 } 175 176 owners, err := userVersionsToDetails(mctx, m.Owners, t) 177 if err != nil { 178 return nil, err 179 } 180 admins, err := userVersionsToDetails(mctx, m.Admins, t) 181 if err != nil { 182 return nil, err 183 } 184 writers, err := userVersionsToDetails(mctx, m.Writers, t) 185 if err != nil { 186 return nil, err 187 } 188 readers, err := userVersionsToDetails(mctx, m.Readers, t) 189 if err != nil { 190 return nil, err 191 } 192 bots, err := userVersionsToDetails(mctx, m.Bots, t) 193 if err != nil { 194 return nil, err 195 } 196 restrictedBots, err := userVersionsToDetails(mctx, m.RestrictedBots, t) 197 if err != nil { 198 return nil, err 199 } 200 ret = append(ret, owners...) 201 ret = append(ret, admins...) 202 ret = append(ret, writers...) 203 ret = append(ret, readers...) 204 ret = append(ret, bots...) 205 ret = append(ret, restrictedBots...) 206 return ret, nil 207 } 208 209 // userVersionsToDetails returns a slice of TeamMemberDetails objects, to be 210 // used in relation to a specific team. It requires the *Team parameter to get 211 // the role and joinTime. 212 func userVersionsToDetails(mctx libkb.MetaContext, uvs []keybase1.UserVersion, t *Team) (ret []keybase1.TeamMemberDetails, err error) { 213 uids := make([]keybase1.UID, len(uvs)) 214 for i, uv := range uvs { 215 uids[i] = uv.Uid 216 } 217 packages, err := mctx.G().UIDMapper.MapUIDsToUsernamePackages(mctx.Ctx(), mctx.G(), uids, 218 defaultFullnameFreshness, defaultNetworkTimeBudget, true /* forceNetworkForFullNames */) 219 if err != nil { 220 return nil, err 221 } 222 223 ret = make([]keybase1.TeamMemberDetails, len(uvs)) 224 225 for i, uv := range uvs { 226 pkg := packages[i] 227 status := keybase1.TeamMemberStatus_ACTIVE 228 var fullName keybase1.FullName 229 if pkg.FullName != nil { 230 if pkg.FullName.EldestSeqno != uv.EldestSeqno { 231 status = keybase1.TeamMemberStatus_RESET 232 } 233 if pkg.FullName.Status == keybase1.StatusCode_SCDeleted { 234 status = keybase1.TeamMemberStatus_DELETED 235 } 236 fullName = pkg.FullName.FullName 237 } 238 role, err := t.chain().GetUserRole(uv) 239 if err != nil { 240 return nil, err 241 } 242 ret[i] = keybase1.TeamMemberDetails{ 243 Uv: uvs[i], 244 Username: pkg.NormalizedUsername.String(), 245 FullName: fullName, 246 Status: status, 247 Role: role, 248 } 249 if status == keybase1.TeamMemberStatus_ACTIVE && t != nil { 250 joinTime, err := t.UserLastJoinTime(uv) 251 if err != nil { 252 return nil, err 253 } 254 ret[i].JoinTime = &joinTime 255 } 256 } 257 return ret, nil 258 } 259 260 func SetRoleOwner(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error { 261 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 262 if err != nil { 263 return err 264 } 265 return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Owners: []keybase1.UserVersion{uv}}) 266 } 267 268 func SetRoleAdmin(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error { 269 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 270 if err != nil { 271 return err 272 } 273 return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Admins: []keybase1.UserVersion{uv}}) 274 } 275 276 func SetRoleWriter(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error { 277 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 278 if err != nil { 279 return err 280 } 281 return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Writers: []keybase1.UserVersion{uv}}) 282 } 283 284 func SetRoleReader(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error { 285 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 286 if err != nil { 287 return err 288 } 289 return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Readers: []keybase1.UserVersion{uv}}) 290 } 291 292 func SetRoleBot(ctx context.Context, g *libkb.GlobalContext, teamname, username string) error { 293 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 294 if err != nil { 295 return err 296 } 297 return ChangeRoles(ctx, g, teamname, keybase1.TeamChangeReq{Bots: []keybase1.UserVersion{uv}}) 298 } 299 300 func SetRoleRestrictedBot(ctx context.Context, g *libkb.GlobalContext, teamname, username string, 301 botSettings keybase1.TeamBotSettings) error { 302 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 303 if err != nil { 304 return err 305 } 306 req := keybase1.TeamChangeReq{ 307 RestrictedBots: map[keybase1.UserVersion]keybase1.TeamBotSettings{ 308 uv: botSettings, 309 }, 310 } 311 return ChangeRoles(ctx, g, teamname, req) 312 } 313 314 func getUserProofsNoTracking(ctx context.Context, g *libkb.GlobalContext, username string) (*libkb.ProofSet, *libkb.IdentifyOutcome, error) { 315 arg := keybase1.Identify2Arg{ 316 UserAssertion: username, 317 UseDelegateUI: false, 318 Reason: keybase1.IdentifyReason{Reason: "clear invitation when adding team member"}, 319 CanSuppressUI: true, 320 IdentifyBehavior: keybase1.TLFIdentifyBehavior_RESOLVE_AND_CHECK, 321 NeedProofSet: true, 322 ActLoggedOut: true, 323 } 324 eng := engine.NewResolveThenIdentify2(g, &arg) 325 m := libkb.NewMetaContext(ctx, g) 326 if err := engine.RunEngine2(m, eng); err != nil { 327 return nil, nil, err 328 } 329 return eng.GetProofSet(), eng.GetIdentifyOutcome(), nil 330 } 331 332 func AddMemberByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, username string, 333 role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings, emailInviteMsg *string) (res keybase1.TeamAddMemberResult, err error) { 334 335 err = RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 336 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true /*needAdmin*/) 337 if err != nil { 338 return err 339 } 340 341 loggedInRole, err := t.myRole(ctx) 342 if err != nil { 343 return err 344 } 345 if role == keybase1.TeamRole_OWNER && loggedInRole == keybase1.TeamRole_ADMIN { 346 return fmt.Errorf("Cannot add owner to team as an admin") 347 } 348 349 tx := CreateAddMemberTx(t) 350 tx.AllowPUKless = true 351 tx.EmailInviteMsg = emailInviteMsg 352 resolvedUsername, uv, invite, err := tx.AddOrInviteMemberByAssertion(ctx, username, role, botSettings) 353 if err != nil { 354 return err 355 } 356 357 if !uv.IsNil() { 358 // Try to mark completed any invites for the user's social assertions. 359 // This can be a time-intensive process since it involves checking proofs. 360 // It is limited to a few seconds and failure is non-fatal. 361 timeoutCtx, timeoutCancel := context.WithTimeout(ctx, 2*time.Second) 362 if err := tx.CompleteSocialInvitesFor(timeoutCtx, uv, username); err != nil { 363 g.Log.CDebugf(ctx, "Failed in CompleteSocialInvitesFor, no invites will be cleared. Err was: %v", err) 364 } 365 timeoutCancel() 366 } 367 368 err = tx.Post(libkb.NewMetaContext(ctx, g)) 369 if err != nil { 370 return err 371 } 372 373 // return value assign to escape closure 374 res = keybase1.TeamAddMemberResult{ 375 User: &keybase1.User{Uid: uv.Uid, Username: resolvedUsername.String()}, 376 Invited: invite, 377 } 378 return nil 379 }) 380 return res, err 381 } 382 383 func AddMember(ctx context.Context, g *libkb.GlobalContext, teamname, username string, role keybase1.TeamRole, 384 botSettings *keybase1.TeamBotSettings) (res keybase1.TeamAddMemberResult, err error) { 385 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 386 Name: teamname, 387 ForceRepoll: true, 388 }) 389 if err != nil { 390 return res, err 391 } 392 return AddMemberByID(ctx, g, team.ID, username, role, botSettings, nil /* emailInviteMsg */) 393 } 394 395 type AddMembersRes struct { 396 Invite bool // Whether the membership addition was an invite. 397 Username libkb.NormalizedUsername // Resolved username. May be nil for social assertions. 398 } 399 400 // AddMembers adds a bunch of people to a team. Assertions can contain usernames or social assertions. 401 // Adds them all in a transaction so it's all or nothing. 402 // If the first transaction fails due to TeamContactSettingsBlock error, it 403 // will remove restricted users returned by the error, and retry once. 404 // On success, returns a list where len(added) + len(noAdded) = len(assertions) and in 405 // corresponding order, with restricted users having an empty AddMembersRes. 406 // 407 // @emailInviteMsg *string is an argument used as a welcome message in email invitations sent from the server 408 func AddMembers(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, users []keybase1.UserRolePair, 409 emailInviteMsg *string) (added []AddMembersRes, notAdded []keybase1.User, err error) { 410 mctx := libkb.NewMetaContext(ctx, g) 411 tracer := g.CTimeTracer(ctx, "team.AddMembers", true) 412 defer tracer.Finish() 413 414 // restrictedUsers is nil initially, but if first attempt at adding members 415 // results in "contact settings block error", restrictedUsers becomes a set 416 // of blocked uids. 417 var restrictedUsers contactRestrictedUsers 418 419 addNonRestrictedMembersFunc := func(ctx context.Context, _ int) error { 420 added = []AddMembersRes{} 421 notAdded = []keybase1.User{} 422 423 team, err := GetForTeamManagementByTeamID(ctx, g, teamID, true /*needAdmin*/) 424 if err != nil { 425 return err 426 } 427 428 if team.IsSubteam() && keybase1.UserRolePairsHaveOwner(users) { 429 // Do the "owner in subteam" check early before we do anything else. 430 return NewSubteamOwnersError() 431 } 432 433 tx := CreateAddMemberTx(team) 434 tx.AllowPUKless = true 435 tx.EmailInviteMsg = emailInviteMsg 436 437 type sweepEntry struct { 438 Assertion string 439 UV keybase1.UserVersion 440 } 441 var sweep []sweepEntry 442 for _, user := range users { 443 candidate, err := tx.ResolveUPKV2FromAssertion(mctx, user.Assertion) 444 if err != nil { 445 return NewAddMembersError(candidate.Full, err) 446 } 447 448 g.Log.CDebugf(ctx, "%q resolved to %s", user.Assertion, candidate.DebugString()) 449 450 if restricted, kbUser := restrictedUsers.checkCandidate(candidate); restricted { 451 // Skip users with contact setting restrictions. 452 notAdded = append(notAdded, kbUser) 453 continue 454 } 455 456 username, uv, invite, err := tx.AddOrInviteMemberCandidate(ctx, candidate, user.Role, user.BotSettings) 457 if err != nil { 458 if _, ok := err.(AttemptedInviteSocialOwnerError); ok { 459 return err 460 } 461 return NewAddMembersError(candidate.Full, err) 462 } 463 var normalizedUsername libkb.NormalizedUsername 464 if !username.IsNil() { 465 normalizedUsername = username 466 } 467 468 added = append(added, AddMembersRes{ 469 Invite: invite, 470 Username: normalizedUsername, 471 }) 472 if !uv.IsNil() { 473 sweep = append(sweep, sweepEntry{ 474 Assertion: user.Assertion, 475 UV: uv, 476 }) 477 } 478 } 479 480 // Try to mark completed any invites for the users' social assertions. 481 // This can be a time-intensive process since it involves checking proofs. 482 // It is limited to a few seconds and failure is non-fatal. 483 timeoutCtx, timeoutCancel := context.WithTimeout(ctx, 2*time.Second) 484 for _, x := range sweep { 485 if err := tx.CompleteSocialInvitesFor(timeoutCtx, x.UV, x.Assertion); err != nil { 486 g.Log.CWarningf(ctx, "Failed in CompleteSocialInvitesFor(%v, %v) -> %v", x.UV, x.Assertion, err) 487 } 488 } 489 timeoutCancel() 490 491 return tx.Post(libkb.NewMetaContext(ctx, g)) 492 } 493 494 // try to add 495 err = RetryIfPossible(ctx, g, addNonRestrictedMembersFunc) 496 if blockError, ok := err.(libkb.TeamContactSettingsBlockError); ok { 497 mctx.Debug("AddMembers: initial attempt failed with contact settings error: %v", err) 498 uids := blockError.BlockedUIDs() 499 if len(uids) == len(users) { 500 // If all users can't be added, quit. Do this check before calling 501 // `unpackContactRestrictedUsers` to avoid allocating and setting 502 // up the uid set if we fall in this case. 503 mctx.Debug("AddMembers: initial attempt failed and all users were restricted from being added. Not retrying.") 504 return nil, nil, err 505 } 506 restrictedUsers = unpackContactRestrictedUsers(blockError) 507 mctx.Debug("AddMembers: retrying without restricted users: %+v", blockError.BlockedUsernames()) 508 err = RetryIfPossible(ctx, g, addNonRestrictedMembersFunc) 509 } 510 511 if err != nil { 512 return nil, nil, err 513 } 514 return added, notAdded, nil 515 } 516 517 func ReAddMemberAfterReset(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 518 username string) (err error) { 519 defer g.CTrace(ctx, fmt.Sprintf("ReAddMemberAfterReset(%v,%v)", teamID, username), &err)() 520 err = reAddMemberAfterResetInner(ctx, g, teamID, username) 521 switch err.(type) { 522 case UserHasNotResetError: 523 // No-op is ok 524 g.Log.CDebugf(ctx, "suppressing error: %v", err) 525 return nil 526 default: 527 return err 528 } 529 } 530 531 func reAddMemberAfterResetInner(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 532 username string) error { 533 arg := libkb.NewLoadUserArg(g). 534 WithNetContext(ctx). 535 WithName(username). 536 WithPublicKeyOptional(). 537 WithForcePoll(true) 538 upak, _, err := g.GetUPAKLoader().LoadV2(arg) 539 if err != nil { 540 return err 541 } 542 _ = g.Pegboard.TrackUPAK(libkb.NewMetaContext(ctx, g), upak.Current) 543 uv := upak.Current.ToUserVersion() 544 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 545 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true) 546 if err != nil { 547 return err 548 } 549 550 // Look for invites first - invite will always be obsoleted (and 551 // removed) by membership or another invite; but membership can 552 // stay un-removed when superseded by new invite (is removed by 553 // new membership though). 554 var existingRole keybase1.TeamRole 555 var existingBotSettings *keybase1.TeamBotSettings 556 invite, existingUV, found := t.FindActiveKeybaseInvite(uv.Uid) 557 if found { 558 // User is PUKless member. 559 existingRole = invite.Role 560 } else { 561 foundUV, err := t.UserVersionByUID(ctx, uv.Uid) 562 if err != nil { 563 if _, ok := err.(libkb.NotFoundError); ok { 564 // username is neither crypto UV nor keybase invite in 565 // that team. bail out. 566 return libkb.NotFoundError{Msg: fmt.Sprintf("User %q (%s) is not a member of this team.", 567 username, uv.Uid)} 568 } 569 // ... or something else failed 570 return err 571 } 572 573 // User is existing crypto member - get their current role. 574 role, err := t.MemberRole(ctx, foundUV) 575 if err != nil { 576 return err 577 } 578 existingRole = role 579 existingUV = foundUV 580 581 if existingRole.IsRestrictedBot() { 582 bots, err := t.TeamBotSettings() 583 if err != nil { 584 return err 585 } 586 botSettings, ok := bots[existingUV] 587 if !ok { 588 botSettings = keybase1.TeamBotSettings{} 589 } 590 existingBotSettings = &botSettings 591 } 592 } 593 594 if existingUV.EldestSeqno == uv.EldestSeqno { 595 return NewUserHasNotResetError("user %s has not reset, no need to re-add, existing: %v new: %v", 596 username, existingUV.EldestSeqno, uv.EldestSeqno) 597 } 598 599 hasPUK := len(upak.Current.PerUserKeys) > 0 600 601 loggedInRole, err := t.myRole(ctx) 602 if err != nil { 603 return err 604 } 605 606 targetRole := existingRole 607 if existingRole.IsOrAbove(loggedInRole) { 608 // If an admin is trying to re-add an owner, re-add them as an admin. 609 // An admin cannot grant owner privileges, so this is the best we can do. 610 targetRole = loggedInRole 611 } 612 613 if !t.IsImplicit() { 614 _, err = AddMemberByID(ctx, g, t.ID, username, targetRole, existingBotSettings, nil /* emailInviteMsg */) 615 return err 616 } 617 618 tx := CreateAddMemberTx(t) 619 tx.AllowPUKless = true 620 if err := tx.ReAddMemberToImplicitTeam(ctx, uv, hasPUK, targetRole, existingBotSettings); err != nil { 621 return err 622 } 623 624 return tx.Post(libkb.NewMetaContext(ctx, g)) 625 }) 626 } 627 628 func InviteEmailPhoneMember(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, name string, typ string, role keybase1.TeamRole) error { 629 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 630 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true) 631 if err != nil { 632 return err 633 } 634 635 return t.InviteEmailPhoneMember(ctx, name, role, typ) 636 }) 637 } 638 639 func AddEmailsBulk(ctx context.Context, g *libkb.GlobalContext, teamname, emails string, role keybase1.TeamRole) (res keybase1.BulkRes, err error) { 640 mctx := libkb.NewMetaContext(ctx, g) 641 642 mctx.Debug("parsing email list for team %q", teamname) 643 644 actx := mctx.G().MakeAssertionContext(mctx) 645 646 emailList := email_utils.ParseSeparatedEmails(mctx, emails, &res.Malformed) 647 var toAdd []keybase1.UserRolePair 648 for _, email := range emailList { 649 assertion, err := libkb.ParseAssertionURLKeyValue(actx, "email", email, false /* strict */) 650 if err != nil { 651 res.Malformed = append(res.Malformed, email) 652 mctx.Debug("Failed to create assertion from email %q: %s", email, err) 653 continue 654 } 655 toAdd = append(toAdd, keybase1.UserRolePair{Assertion: assertion.String(), Role: role}) 656 } 657 658 if len(toAdd) == 0 { 659 // Nothing to do. 660 return res, nil 661 } 662 663 t, err := GetForTeamManagementByStringName(ctx, g, teamname, true) 664 if err != nil { 665 return res, err 666 } 667 668 _, _, err = AddMembers(ctx, g, t.ID, toAdd, nil /* emailInviteMsg */) 669 return res, err 670 } 671 672 func EditMember(ctx context.Context, g *libkb.GlobalContext, teamname, username string, 673 role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error { 674 teamGetter := func() (*Team, error) { return GetForTeamManagementByStringName(ctx, g, teamname, true) } 675 return editMember(ctx, g, teamGetter, username, role, botSettings) 676 } 677 678 func EditMemberByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 679 username string, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error { 680 teamGetter := func() (*Team, error) { return GetForTeamManagementByTeamID(ctx, g, teamID, true) } 681 return editMember(ctx, g, teamGetter, username, role, botSettings) 682 } 683 684 func EditMembers(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, users []keybase1.UserRolePair) (res keybase1.TeamEditMembersResult, err error) { 685 var failedToEdit []keybase1.UserRolePair 686 687 for _, userRolePair := range users { 688 err := EditMemberByID(ctx, g, teamID, userRolePair.Assertion, userRolePair.Role, userRolePair.BotSettings) 689 if err != nil { 690 failedToEdit = append(failedToEdit, userRolePair) 691 continue 692 } 693 } 694 695 res = keybase1.TeamEditMembersResult{Failures: failedToEdit} 696 return res, nil 697 } 698 699 func editMember(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), 700 username string, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) error { 701 702 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 703 if err == errInviteRequired { 704 return editMemberInvite(ctx, g, teamGetter, username, role, uv, botSettings) 705 } 706 if err != nil { 707 return err 708 } 709 710 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 711 t, err := teamGetter() 712 if err != nil { 713 return err 714 } 715 if !t.IsMember(ctx, uv) { 716 return libkb.NotFoundError{Msg: fmt.Sprintf("user %q is not a member of team %q", username, t.Name())} 717 } 718 existingRole, err := t.MemberRole(ctx, uv) 719 if err != nil { 720 return err 721 } 722 723 if existingRole == role { 724 if !role.IsRestrictedBot() { 725 g.Log.CDebugf(ctx, "bailing out, role given is the same as current") 726 return nil 727 } 728 teamBotSettings, err := t.TeamBotSettings() 729 if err != nil { 730 return err 731 } 732 existingBotSettings := teamBotSettings[uv] 733 if botSettings.Eq(&existingBotSettings) { 734 g.Log.CDebugf(ctx, "bailing out, role given is the same as current, botSettings unchanged") 735 return nil 736 } 737 } 738 739 req, err := reqFromRole(uv, role, botSettings) 740 if err != nil { 741 return err 742 } 743 744 return t.ChangeMembership(ctx, req) 745 }) 746 747 } 748 749 func editMemberInvite(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), 750 username string, role keybase1.TeamRole, 751 uv keybase1.UserVersion, botSettings *keybase1.TeamBotSettings) error { 752 t, err := teamGetter() 753 if err != nil { 754 return err 755 } 756 g.Log.CDebugf(ctx, "team %s: edit member %s, member is an invite link", t.ID, username) 757 758 // Note that there could be a problem if removeMemberInvite works but AddMember doesn't 759 // as the original invite will be lost. But the user will get an error and can try 760 // again. 761 if err := removeMemberInvite(ctx, g, t, username, uv); err != nil { 762 g.Log.CDebugf(ctx, "editMemberInvite error in removeMemberInvite: %s", err) 763 return err 764 } 765 // use AddMember in case it's possible to add them directly now 766 if _, err := AddMemberByID(ctx, g, t.ID, username, role, nil, nil /* emailInviteMsg */); err != nil { 767 g.Log.CDebugf(ctx, "editMemberInvite error in AddMember: %s", err) 768 return err 769 } 770 return nil 771 } 772 773 func SetBotSettings(ctx context.Context, g *libkb.GlobalContext, teamname, username string, 774 botSettings keybase1.TeamBotSettings) error { 775 teamGetter := func() (*Team, error) { 776 return GetForTeamManagementByStringName(ctx, g, teamname, false) 777 } 778 779 return setBotSettings(ctx, g, teamGetter, username, botSettings) 780 } 781 782 func SetBotSettingsByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 783 username string, botSettings keybase1.TeamBotSettings) error { 784 teamGetter := func() (*Team, error) { 785 return GetForTeamManagementByTeamID(ctx, g, teamID, false) 786 } 787 return setBotSettings(ctx, g, teamGetter, username, botSettings) 788 } 789 790 func setBotSettings(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), 791 username string, botSettings keybase1.TeamBotSettings) error { 792 793 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 794 if err != nil { 795 return err 796 } 797 798 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 799 t, err := teamGetter() 800 if err != nil { 801 return err 802 } 803 804 if !t.IsMember(ctx, uv) { 805 return libkb.NotFoundError{Msg: fmt.Sprintf("user %q is not a member of team %q", username, t.Name())} 806 } 807 role, err := t.MemberRole(ctx, uv) 808 if err != nil { 809 return err 810 } 811 if !role.IsRestrictedBot() { 812 return fmt.Errorf("%s is not a %v, but has the role %v", 813 username, keybase1.TeamRole_RESTRICTEDBOT, role) 814 } 815 816 return t.PostTeamBotSettings(ctx, map[keybase1.UserVersion]keybase1.TeamBotSettings{ 817 uv: botSettings, 818 }) 819 }) 820 } 821 822 func GetBotSettings(ctx context.Context, g *libkb.GlobalContext, 823 teamname, username string) (res keybase1.TeamBotSettings, err error) { 824 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 825 Name: teamname, 826 ForceRepoll: true, 827 }) 828 if err != nil { 829 return res, err 830 } 831 return getBotSettings(ctx, g, team, username) 832 } 833 834 func GetBotSettingsByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 835 username string) (res keybase1.TeamBotSettings, err error) { 836 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 837 ID: teamID, 838 ForceRepoll: true, 839 }) 840 if err != nil { 841 return res, err 842 } 843 return getBotSettings(ctx, g, team, username) 844 } 845 846 func getBotSettings(ctx context.Context, g *libkb.GlobalContext, 847 team *Team, username string) (res keybase1.TeamBotSettings, err error) { 848 uv, err := loadUserVersionByUsername(ctx, g, username, true /* useTracking */) 849 if err != nil { 850 return res, err 851 } 852 853 if !team.IsMember(ctx, uv) { 854 return res, libkb.NotFoundError{Msg: fmt.Sprintf("user %q is not a member of team %q", username, team.Name())} 855 } 856 857 role, err := team.MemberRole(ctx, uv) 858 if err != nil { 859 return res, err 860 } 861 if !role.IsRestrictedBot() { 862 return res, fmt.Errorf("%s is not a %v, but has the role %v", 863 username, keybase1.TeamRole_RESTRICTEDBOT, role) 864 } 865 866 botSettings, err := team.TeamBotSettings() 867 if err != nil { 868 return res, err 869 } 870 return botSettings[uv], nil 871 } 872 873 func MemberRole(ctx context.Context, g *libkb.GlobalContext, teamname, username string) (role keybase1.TeamRole, err error) { 874 uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */) 875 if err != nil { 876 return keybase1.TeamRole_NONE, err 877 } 878 879 err = RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 880 t, err := GetForTeamManagementByStringName(ctx, g, teamname, false) 881 if err != nil { 882 return err 883 } 884 // return value assign to escape closure 885 role, err = t.MemberRole(ctx, uv) 886 return err 887 }) 888 return role, err 889 } 890 891 func MemberRoleFromID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, username string) (role keybase1.TeamRole, err error) { 892 uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */) 893 if err != nil { 894 return keybase1.TeamRole_NONE, err 895 } 896 897 err = RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 898 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, false) 899 if err != nil { 900 return err 901 } 902 // return value assign to escape closure 903 role, err = t.MemberRole(ctx, uv) 904 return err 905 }) 906 return role, err 907 } 908 909 func RemoveMemberSingle(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 910 member keybase1.TeamMemberToRemove) (err error) { 911 members := []keybase1.TeamMemberToRemove{member} 912 res, err := RemoveMembers(ctx, g, teamID, members, false /* NoErrorOnPartialFailure */) 913 if err != nil { 914 msg := fmt.Sprintf("failed to remove member: %s", err) 915 if len(res.Failures) > 0 { 916 if res.Failures[0].ErrorAtTarget != nil { 917 msg += "; " + *res.Failures[0].ErrorAtTarget 918 } 919 if res.Failures[0].ErrorAtSubtree != nil { 920 msg += "; " + *res.Failures[0].ErrorAtSubtree 921 } 922 } 923 err = errors.New(msg) 924 } 925 return err 926 } 927 928 func RemoveMembers(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, 929 members []keybase1.TeamMemberToRemove, shouldNotErrorOnPartialFailure bool, 930 ) (res keybase1.TeamRemoveMembersResult, err error) { 931 mctx := libkb.NewMetaContext(ctx, g) 932 933 // Preliminary checks 934 for _, member := range members { 935 typ, err := member.Type() 936 if err != nil { 937 return res, err 938 } 939 switch typ { 940 case keybase1.TeamMemberToRemoveType_ASSERTION: 941 case keybase1.TeamMemberToRemoveType_INVITEID: 942 default: 943 return res, fmt.Errorf("unknown TeamMemberToRemoveType %v", typ) 944 } 945 } 946 947 // Removals 948 teamGetter := func() (*Team, error) { 949 return GetForTeamManagementByTeamID(ctx, g, teamID, true /* needAdmin */) 950 } 951 errstrp := func(e error) *string { 952 if e == nil { 953 return nil 954 } 955 s := e.Error() 956 return &s 957 } 958 var failures []keybase1.RemoveTeamMemberFailure 959 for _, member := range members { 960 typ, _ := member.Type() 961 switch typ { 962 case keybase1.TeamMemberToRemoveType_ASSERTION: 963 targetErr := remove(ctx, g, teamGetter, member.Assertion().Assertion) 964 var subtreeErr error 965 if member.Assertion().RemoveFromSubtree { 966 if targetErr == nil { 967 subtreeErr = removeMemberFromSubtree(mctx, teamID, member.Assertion().Assertion) 968 } else { 969 subtreeErr = errors.New("did not attempt to remove from subtree since removal failed at specified team") 970 } 971 } 972 if targetErr != nil || subtreeErr != nil { 973 failures = append(failures, keybase1.RemoveTeamMemberFailure{ 974 TeamMember: member, 975 ErrorAtTarget: errstrp(targetErr), 976 ErrorAtSubtree: errstrp(subtreeErr), 977 }) 978 } 979 case keybase1.TeamMemberToRemoveType_INVITEID: 980 targetErr := CancelInviteByID(ctx, g, teamID, member.Inviteid().InviteID) 981 if targetErr != nil { 982 failures = append(failures, keybase1.RemoveTeamMemberFailure{ 983 TeamMember: member, 984 ErrorAtSubtree: errstrp(targetErr), 985 }) 986 } 987 } 988 } 989 990 if !shouldNotErrorOnPartialFailure && len(failures) > 0 { 991 err = fmt.Errorf("failed to remove %d members", len(failures)) 992 } else { 993 err = nil 994 } 995 return keybase1.TeamRemoveMembersResult{Failures: failures}, err 996 } 997 998 // removeMemberFromSubtree removes member from all teams in the subtree of targetTeamID, 999 // *not including* targetTeamID itself 1000 func removeMemberFromSubtree(mctx libkb.MetaContext, targetTeamID keybase1.TeamID, 1001 assertion string) error { 1002 // We don't care about the roles; we just want the list of teams. So we can pass our 1003 // own username. 1004 myUsername := mctx.G().Env.GetUsername() 1005 guid := 0 1006 treeloader, err := NewTreeloader(mctx, myUsername.String(), targetTeamID, guid, 1007 false /* includeAncestors */) 1008 if err != nil { 1009 return fmt.Errorf("could not start loading subteams: %w", err) 1010 } 1011 // All or nothing; we can assume all results will be OK. 1012 teamTreeMemberships, err := treeloader.LoadSync(mctx) 1013 if err != nil { 1014 return fmt.Errorf("could not load subteams: %w", err) 1015 } 1016 var errs []error 1017 for _, membership := range teamTreeMemberships { 1018 status, _ := membership.Result.S() 1019 if status != keybase1.TeamTreeMembershipStatus_OK { 1020 return fmt.Errorf("removeMemberFromSubtree: unexpectedly got a non-OK status from Treeloader.LoadSync: %v", status) 1021 } 1022 1023 teamID := membership.Result.Ok().TeamID 1024 1025 // Don't remove member from the targetTeam; just the subtree 1026 if teamID == targetTeamID { 1027 continue 1028 } 1029 1030 teamGetter := func() (*Team, error) { 1031 return GetForTeamManagementByTeamID(mctx.Ctx(), mctx.G(), teamID, true /* needAdmin */) 1032 } 1033 1034 removeErr := remove(mctx.Ctx(), mctx.G(), teamGetter, assertion) 1035 var memberNotFoundErr *MemberNotFoundInChainError 1036 switch { 1037 case removeErr == nil: 1038 case errors.As(removeErr, &memberNotFoundErr): 1039 // If the member was not found in the sigchain (either via invite or cryptomember), we can 1040 // ignore the error. Because we got team memberships for ourselves and not the user, we 1041 // can't use the membership data provided by the Treeloader. (We're not using the 1042 // membership data from the treeloader because it does not support looking up by invites). 1043 default: 1044 errs = append(errs, fmt.Errorf("failed to remove from %s: %w", 1045 membership.TeamName, removeErr)) 1046 } 1047 } 1048 return libkb.CombineErrors(errs...) 1049 } 1050 1051 func RemoveMemberByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, username string) error { 1052 teamGetter := func() (*Team, error) { 1053 return GetForTeamManagementByTeamID(ctx, g, teamID, true) 1054 } 1055 return remove(ctx, g, teamGetter, username) 1056 } 1057 1058 // RemoveMember removes members by username or assertions. For a function that can handle removal 1059 // from subteams and inviteIDs, see RemoveMemberSingle and RemoveMembers. 1060 func RemoveMember(ctx context.Context, g *libkb.GlobalContext, teamName string, username string) error { 1061 teamGetter := func() (*Team, error) { 1062 return GetForTeamManagementByStringName(ctx, g, teamName, true) 1063 } 1064 return remove(ctx, g, teamGetter, username) 1065 } 1066 1067 func remove(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), username string) error { 1068 var inviteRequired bool 1069 uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */) 1070 if err != nil { 1071 switch err { 1072 case errInviteRequired: 1073 inviteRequired = true 1074 case errUserDeleted: // no-op 1075 default: 1076 return err 1077 } 1078 g.Log.CDebugf(ctx, "loadUserVersionByUsername(%s) returned %v,%q", username, uv, err) 1079 } 1080 1081 me, err := loadMeForSignatures(ctx, g) 1082 if err != nil { 1083 return err 1084 } 1085 1086 if me.GetNormalizedName().Eq(libkb.NewNormalizedUsername(username)) { 1087 return leave(ctx, g, teamGetter, false) 1088 } 1089 1090 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1091 t, err := teamGetter() 1092 if err != nil { 1093 return err 1094 } 1095 1096 if inviteRequired && !uv.Uid.Exists() { 1097 // Remove a non-keybase invite. 1098 return removeMemberInvite(ctx, g, t, username, uv) 1099 } 1100 1101 if _, _, found := t.FindActiveKeybaseInvite(uv.Uid); found { 1102 // Remove keybase invites. 1103 return removeKeybaseTypeInviteForUID(ctx, g, t, uv.Uid) 1104 } 1105 1106 existingUV, err := t.UserVersionByUID(ctx, uv.Uid) 1107 if err != nil { 1108 return NewMemberNotFoundInChainError(libkb.NotFoundError{Msg: fmt.Sprintf( 1109 "user %q is not a member of team %q", username, t.Name())}) 1110 } 1111 1112 removePermanently := t.IsOpen() || 1113 t.WasMostRecentlyAddedByInvitelink(existingUV) 1114 req := keybase1.TeamChangeReq{None: []keybase1.UserVersion{existingUV}} 1115 opts := ChangeMembershipOptions{ 1116 Permanent: removePermanently, 1117 SkipKeyRotation: t.CanSkipKeyRotation(), 1118 } 1119 return t.ChangeMembershipWithOptions(ctx, req, opts) 1120 }) 1121 } 1122 1123 func CancelEmailInvite(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, email string) (err error) { 1124 g.CTrace(ctx, "CancelEmailInvite", &err) 1125 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1126 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true) 1127 if err != nil { 1128 return err 1129 } 1130 1131 if !libkb.CheckEmail.F(email) { 1132 return errors.New("Invalid email address") 1133 } 1134 1135 return removeMemberInviteOfType(ctx, g, t, keybase1.TeamInviteName(email), "email") 1136 }) 1137 } 1138 1139 func CancelInviteByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, inviteID keybase1.TeamInviteID) (err error) { 1140 g.CTrace(ctx, "CancelInviteByID", &err) 1141 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1142 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true) 1143 if err != nil { 1144 return err 1145 } 1146 1147 // Service-side check for invite id, even though we operate on 1148 // TeamInviteID type, API consumer can give us any string. 1149 if _, err := keybase1.TeamInviteIDFromString(string(inviteID)); err != nil { 1150 return fmt.Errorf("Invalid invite ID: %s", err) 1151 } 1152 1153 return removeInviteID(ctx, t, inviteID) 1154 }) 1155 } 1156 1157 func leave(ctx context.Context, g *libkb.GlobalContext, teamGetter func() (*Team, error), permanent bool) error { 1158 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1159 t, err := teamGetter() 1160 if err != nil { 1161 return err 1162 } 1163 err = t.Leave(ctx, permanent) 1164 if err != nil { 1165 return err 1166 } 1167 1168 err = FreezeTeam(libkb.NewMetaContext(ctx, g), t.ID) 1169 if err != nil { 1170 g.Log.CDebugf(ctx, "leave FreezeTeam error: %+v", err) 1171 } 1172 1173 return nil 1174 }) 1175 } 1176 1177 func LeaveByID(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID, permanent bool) error { 1178 teamGetter := func() (*Team, error) { 1179 return GetForTeamManagementByTeamID(ctx, g, teamID, false) 1180 } 1181 return leave(ctx, g, teamGetter, permanent) 1182 } 1183 1184 func Leave(ctx context.Context, g *libkb.GlobalContext, teamname string, permanent bool) error { 1185 teamGetter := func() (*Team, error) { 1186 return GetForTeamManagementByStringName(ctx, g, teamname, false) 1187 } 1188 return leave(ctx, g, teamGetter, permanent) 1189 } 1190 1191 func Delete(ctx context.Context, g *libkb.GlobalContext, ui keybase1.TeamsUiInterface, teamID keybase1.TeamID) error { 1192 alreadyConfirmed := false 1193 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1194 t, err := GetForTeamManagementByTeamID(ctx, g, teamID, true) 1195 if err != nil { 1196 return err 1197 } 1198 1199 if !alreadyConfirmed { 1200 var confirmed bool 1201 if t.chain().IsSubteam() { 1202 confirmed, err = ui.ConfirmSubteamDelete(ctx, keybase1.ConfirmSubteamDeleteArg{TeamName: t.Name().String()}) 1203 } else { 1204 confirmed, err = ui.ConfirmRootTeamDelete(ctx, keybase1.ConfirmRootTeamDeleteArg{TeamName: t.Name().String()}) 1205 } 1206 if err != nil { 1207 return err 1208 } 1209 if !confirmed { 1210 return errors.New("team delete not confirmed") 1211 } 1212 alreadyConfirmed = true 1213 } 1214 1215 if t.chain().IsSubteam() { 1216 err = t.deleteSubteam(ctx) 1217 } else { 1218 err = t.deleteRoot(ctx) 1219 } 1220 if err != nil { 1221 return err 1222 } 1223 1224 err = TombstoneTeam(libkb.NewMetaContext(ctx, g), t.ID) 1225 if err != nil { 1226 g.Log.CDebugf(ctx, "Delete TombstoneTeam error: %+v", err) 1227 if g.Env.GetRunMode() == libkb.DevelRunMode { 1228 return err 1229 } 1230 } 1231 1232 return nil 1233 }) 1234 } 1235 1236 func AcceptServerTrustInvite(ctx context.Context, g *libkb.GlobalContext, token string) error { 1237 mctx := libkb.NewMetaContext(ctx, g) 1238 arg := apiArg("team/token") 1239 arg.Args.Add("token", libkb.S{Val: token}) 1240 _, err := mctx.G().API.Post(mctx, arg) 1241 return err 1242 } 1243 1244 func ChangeRoles(ctx context.Context, g *libkb.GlobalContext, teamname string, req keybase1.TeamChangeReq) error { 1245 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1246 // Don't needAdmin because we might be leaving, and this needs no information from stubbable links. 1247 t, err := GetForTeamManagementByStringName(ctx, g, teamname, false) 1248 if err != nil { 1249 return err 1250 } 1251 return t.ChangeMembership(ctx, req) 1252 }) 1253 } 1254 1255 var errInviteRequired = errors.New("invite required for username") 1256 var errUserDeleted = errors.New("user is deleted") 1257 1258 // loadUserVersionByUsername is a wrapper around `engine.ResolveAndCheck` to 1259 // return UV by username or assertion. When the argument does not resolve to a 1260 // Keybase user with PUK, `errInviteRequired` is returned. 1261 // 1262 // Returns `errInviteRequired` if given argument cannot be brought in as a 1263 // crypto member - so it is either a reset and not provisioned Keybase user 1264 // (keybase-type invite is required), or a social assertion that does not 1265 // resolve to a user. 1266 // 1267 // NOTE: This also doesn't try to resolve server-trust assertions. 1268 func loadUserVersionByUsername(ctx context.Context, g *libkb.GlobalContext, username string, useTracking bool) (keybase1.UserVersion, error) { 1269 m := libkb.NewMetaContext(ctx, g) 1270 upk, err := engine.ResolveAndCheck(m, username, useTracking) 1271 if err != nil { 1272 if e, ok := err.(libkb.ResolutionError); ok && e.Kind == libkb.ResolutionErrorNotFound { 1273 // couldn't find a keybase user for username assertion 1274 return keybase1.UserVersion{}, errInviteRequired 1275 } 1276 return keybase1.UserVersion{}, err 1277 } 1278 1279 return filterUserCornerCases(ctx, upk) 1280 } 1281 1282 func loadUserVersionByUID(ctx context.Context, g *libkb.GlobalContext, uid keybase1.UID) (keybase1.UserVersion, error) { 1283 upak, err := loadUPAK2(ctx, g, uid, true /*forcePoll */) 1284 if err != nil { 1285 return keybase1.UserVersion{}, err 1286 } 1287 return filterUserCornerCases(ctx, upak.Current) 1288 } 1289 1290 func filterUserCornerCases(ctx context.Context, upak keybase1.UserPlusKeysV2) (keybase1.UserVersion, error) { 1291 uv := upak.ToUserVersion() 1292 if upak.Status == keybase1.StatusCode_SCDeleted { 1293 return uv, errUserDeleted 1294 } 1295 if len(upak.PerUserKeys) == 0 { 1296 return uv, errInviteRequired 1297 } 1298 return uv, nil 1299 } 1300 1301 func reqFromRole(uv keybase1.UserVersion, role keybase1.TeamRole, botSettings *keybase1.TeamBotSettings) (req keybase1.TeamChangeReq, err error) { 1302 list := []keybase1.UserVersion{uv} 1303 if !role.IsRestrictedBot() && botSettings != nil { 1304 return req, fmt.Errorf("Unexpected botSettings for role %v", role) 1305 } 1306 switch role { 1307 case keybase1.TeamRole_OWNER: 1308 req.Owners = list 1309 case keybase1.TeamRole_ADMIN: 1310 req.Admins = list 1311 case keybase1.TeamRole_WRITER: 1312 req.Writers = list 1313 case keybase1.TeamRole_READER: 1314 req.Readers = list 1315 case keybase1.TeamRole_BOT: 1316 req.Bots = list 1317 case keybase1.TeamRole_RESTRICTEDBOT: 1318 if botSettings == nil { 1319 return req, errors.New("botSettings must be specified for RESTRICTEDBOT role") 1320 } 1321 req.RestrictedBots = map[keybase1.UserVersion]keybase1.TeamBotSettings{ 1322 uv: *botSettings, 1323 } 1324 default: 1325 return req, errors.New("invalid team role") 1326 } 1327 1328 return req, nil 1329 } 1330 1331 func makeIdentifyLiteRes(id keybase1.TeamID, name keybase1.TeamName) keybase1.IdentifyLiteRes { 1332 return keybase1.IdentifyLiteRes{ 1333 Ul: keybase1.UserOrTeamLite{ 1334 Id: id.AsUserOrTeam(), 1335 Name: name.String(), 1336 }, 1337 } 1338 } 1339 1340 func identifyLiteByID(ctx context.Context, g *libkb.GlobalContext, utid keybase1.UserOrTeamID, id2 keybase1.TeamID) (res keybase1.IdentifyLiteRes, err error) { 1341 1342 var id1 keybase1.TeamID 1343 if utid.Exists() { 1344 id1, err = utid.AsTeam() 1345 if err != nil { 1346 return res, err 1347 } 1348 } 1349 1350 if id1.Exists() && id2.Exists() && !id1.Eq(id2) { 1351 return res, errors.New("two team IDs given that don't match") 1352 } 1353 if !id1.Exists() { 1354 id1 = id2 1355 } 1356 if !id1.Exists() { 1357 return res, errors.New("empty IDs given") 1358 } 1359 var name keybase1.TeamName 1360 name, err = ResolveIDToName(ctx, g, id1) 1361 if err != nil { 1362 return res, err 1363 } 1364 1365 return makeIdentifyLiteRes(id1, name), nil 1366 } 1367 1368 func identifyLiteByName(ctx context.Context, g *libkb.GlobalContext, name keybase1.TeamName) (res keybase1.IdentifyLiteRes, err error) { 1369 var id keybase1.TeamID 1370 id, err = ResolveNameToID(ctx, g, name) 1371 if err != nil { 1372 return res, err 1373 } 1374 return makeIdentifyLiteRes(id, name), nil 1375 } 1376 1377 func IdentifyLite(ctx context.Context, g *libkb.GlobalContext, arg keybase1.IdentifyLiteArg, au libkb.AssertionURL) (res keybase1.IdentifyLiteRes, err error) { 1378 1379 if arg.Id.Exists() || au.IsTeamID() { 1380 return identifyLiteByID(ctx, g, arg.Id, au.ToTeamID()) 1381 } 1382 if au.IsTeamName() { 1383 return identifyLiteByName(ctx, g, au.ToTeamName()) 1384 } 1385 return res, errors.New("could not identify team by ID or name") 1386 } 1387 1388 func memberInvite(ctx context.Context, g *libkb.GlobalContext, teamname string, iname keybase1.TeamInviteName, itype keybase1.TeamInviteType) (*keybase1.TeamInvite, error) { 1389 t, err := GetForTeamManagementByStringName(ctx, g, teamname, true) 1390 if err != nil { 1391 return nil, err 1392 } 1393 return t.chain().FindActiveInvite(iname, itype) 1394 } 1395 1396 func RequestAccess(ctx context.Context, g *libkb.GlobalContext, teamname string) (keybase1.TeamRequestAccessResult, error) { 1397 arg := apiArg("team/request_access") 1398 arg.Args.Add("team", libkb.S{Val: teamname}) 1399 mctx := libkb.NewMetaContext(ctx, g) 1400 apiRes, err := g.API.Post(mctx, arg) 1401 1402 ret := keybase1.TeamRequestAccessResult{} 1403 if apiRes != nil && apiRes.Body != nil { 1404 // "is_open" key may not be included in result payload and it's 1405 // not an error. 1406 ret.Open, _ = apiRes.Body.AtKey("is_open").GetBool() 1407 } 1408 return ret, err 1409 } 1410 1411 func TeamAcceptInviteOrRequestAccess(ctx context.Context, g *libkb.GlobalContext, ui keybase1.TeamsUiInterface, tokenOrName string) (keybase1.TeamAcceptOrRequestResult, error) { 1412 g.Log.CDebugf(ctx, "trying seitan token") 1413 1414 mctx := libkb.NewMetaContext(ctx, g) 1415 1416 // If token looks at all like Seitan, don't pass to functions that might log or send to server. 1417 maybeSeitanToken, isSeitany := ParseSeitanTokenFromPaste(tokenOrName) 1418 if isSeitany { 1419 g.Log.CDebugf(ctx, "found seitan-y token") 1420 wasSeitan, err := ParseAndAcceptSeitanToken(mctx, ui, maybeSeitanToken) 1421 return keybase1.TeamAcceptOrRequestResult{WasSeitan: wasSeitan}, err 1422 } 1423 1424 g.Log.CDebugf(ctx, "trying email-style invite") 1425 err := AcceptServerTrustInvite(ctx, g, tokenOrName) 1426 if err == nil { 1427 return keybase1.TeamAcceptOrRequestResult{ 1428 WasToken: true, 1429 }, nil 1430 } 1431 g.Log.CDebugf(ctx, "email-style invite error: %v", err) 1432 var reportErr error 1433 switch err := err.(type) { 1434 case libkb.TeamInviteTokenReusedError: 1435 reportErr = err 1436 default: 1437 reportErr = libkb.TeamInviteBadTokenError{} 1438 } 1439 1440 g.Log.CDebugf(ctx, "trying team name") 1441 _, err = keybase1.TeamNameFromString(tokenOrName) 1442 if err == nil { 1443 ret2, err := RequestAccess(ctx, g, tokenOrName) 1444 ret := keybase1.TeamAcceptOrRequestResult{ 1445 WasTeamName: true, 1446 WasOpenTeam: ret2.Open, // this is probably just false in error case 1447 } 1448 return ret, err 1449 } 1450 g.Log.CDebugf(ctx, "not a team name") 1451 1452 // We don't know what this thing is. Return the error from AcceptInvite. 1453 return keybase1.TeamAcceptOrRequestResult{}, reportErr 1454 } 1455 1456 type accessRequest struct { 1457 FQName string `json:"fq_name"` 1458 TeamID keybase1.TeamID `json:"team_id"` 1459 UID keybase1.UID `json:"uid"` 1460 Ctime time.Time `json:"ctime"` 1461 Username string `json:"username"` 1462 } 1463 1464 type accessRequestList struct { 1465 Requests []accessRequest `json:"requests"` 1466 Status libkb.AppStatus `json:"status"` 1467 } 1468 1469 func (r *accessRequestList) GetAppStatus() *libkb.AppStatus { 1470 return &r.Status 1471 } 1472 1473 // Lists all requests in all of user-owned teams or a single team and tries to 1474 // resolve their full names. 1475 // 1476 // Full names are not guaranteed to be present in the response. Given a large 1477 // enough volume of access requests by unknown (to us) users, it's possible to 1478 // run into a scenario where resolving thousands of username bundles would take 1479 // longer than the 10s. 1480 func ListRequests(ctx context.Context, g *libkb.GlobalContext, teamName *string) ([]keybase1.TeamJoinRequest, error) { 1481 var arg libkb.APIArg 1482 mctx := libkb.NewMetaContext(ctx, g) 1483 if teamName != nil { 1484 arg = apiArg("team/access_requests") 1485 arg.Args.Add("team", libkb.S{Val: *teamName}) 1486 } else { 1487 arg = apiArg("team/laar") 1488 } 1489 1490 var arList accessRequestList 1491 if err := mctx.G().API.GetDecode(mctx, arg, &arList); err != nil { 1492 return nil, err 1493 } 1494 1495 var ( 1496 joinRequests = make([]keybase1.TeamJoinRequest, len(arList.Requests)) 1497 requesterUIDs = make([]keybase1.UID, len(arList.Requests)) 1498 ) 1499 for i, ar := range arList.Requests { 1500 username := libkb.NewNormalizedUsername(ar.Username) 1501 uid := libkb.GetUIDByNormalizedUsername(g, username) 1502 1503 requesterUIDs[i] = uid 1504 joinRequests[i] = keybase1.TeamJoinRequest{ 1505 Name: ar.FQName, 1506 Username: username.String(), 1507 Ctime: keybase1.ToUnixTime(ar.Ctime), 1508 } 1509 } 1510 1511 packages, err := g.UIDMapper.MapUIDsToUsernamePackages(ctx, g, requesterUIDs, 1512 defaultFullnameFreshness, 10*time.Second, true /* forceNetworkForFullNames */) 1513 if err != nil { 1514 g.Log.Debug("TeamsListRequests: failed to run uid mapper: %s", err) 1515 } 1516 for i, uid := range requesterUIDs { 1517 if packages[i].NormalizedUsername.IsNil() { 1518 g.Log.Debug("TeamsListRequests: failed to get username for: %s", uid) 1519 continue 1520 } 1521 if packages[i].FullName != nil { 1522 joinRequests[i].FullName = packages[i].FullName.FullName 1523 } 1524 } 1525 1526 return joinRequests, nil 1527 } 1528 1529 type myAccessRequestsList struct { 1530 Requests []struct { 1531 FQName string `json:"fq_name"` 1532 TeamID keybase1.TeamID `json:"team_id"` 1533 } `json:"requests"` 1534 Status libkb.AppStatus `json:"status"` 1535 } 1536 1537 func (r *myAccessRequestsList) GetAppStatus() *libkb.AppStatus { 1538 return &r.Status 1539 } 1540 1541 func ListMyAccessRequests(ctx context.Context, g *libkb.GlobalContext, teamName *string) (res []keybase1.TeamName, err error) { 1542 mctx := libkb.NewMetaContext(ctx, g) 1543 arg := apiArg("team/my_access_requests") 1544 if teamName != nil { 1545 arg.Args.Add("team", libkb.S{Val: *teamName}) 1546 } 1547 1548 var arList myAccessRequestsList 1549 if err := mctx.G().API.GetDecode(mctx, arg, &arList); err != nil { 1550 return nil, err 1551 } 1552 1553 for _, req := range arList.Requests { 1554 name, err := keybase1.TeamNameFromString(req.FQName) 1555 if err != nil { 1556 return nil, err 1557 } 1558 res = append(res, name) 1559 } 1560 1561 return res, nil 1562 } 1563 1564 func IgnoreRequest(ctx context.Context, g *libkb.GlobalContext, teamName, username string) error { 1565 mctx := libkb.NewMetaContext(ctx, g) 1566 uv, err := loadUserVersionByUsername(ctx, g, username, false /* useTracking */) 1567 if err != nil { 1568 if err == errInviteRequired { 1569 return libkb.NotFoundError{ 1570 Msg: fmt.Sprintf("No keybase user found (%s)", username), 1571 } 1572 } 1573 return err 1574 } 1575 arg := apiArg("team/deny_access") 1576 arg.Args.Add("team", libkb.S{Val: teamName}) 1577 arg.Args.Add("uid", libkb.S{Val: uv.Uid.String()}) 1578 if _, err := mctx.G().API.Post(mctx, arg); err != nil { 1579 return err 1580 } 1581 t, err := GetForTeamManagementByStringName(ctx, g, teamName, true) 1582 if err != nil { 1583 return err 1584 } 1585 return t.notifyNoChainChange(ctx, keybase1.TeamChangeSet{Misc: true}) 1586 } 1587 1588 func apiArg(endpoint string) libkb.APIArg { 1589 arg := libkb.NewAPIArg(endpoint) 1590 arg.Args = libkb.NewHTTPArgs() 1591 arg.SessionType = libkb.APISessionTypeREQUIRED 1592 return arg 1593 } 1594 1595 func GetRootID(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) (keybase1.TeamID, error) { 1596 team, _, err := g.GetTeamLoader().Load(ctx, keybase1.LoadTeamArg{ 1597 ID: id, 1598 Public: id.IsPublic(), 1599 StaleOK: true, 1600 }) 1601 1602 if err != nil { 1603 return keybase1.TeamID(""), err 1604 } 1605 1606 return team.Name.RootAncestorName().ToTeamID(id.IsPublic()), nil 1607 } 1608 1609 func ChangeTeamSettings(ctx context.Context, g *libkb.GlobalContext, teamName string, settings keybase1.TeamSettings) error { 1610 mctx := libkb.NewMetaContext(ctx, g) 1611 id, err := GetTeamIDByNameRPC(mctx, teamName) 1612 if err != nil { 1613 return err 1614 } 1615 return ChangeTeamSettingsByID(ctx, g, id, settings) 1616 } 1617 1618 func ChangeTeamSettingsByID(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, 1619 settings keybase1.TeamSettings) error { 1620 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1621 t, err := GetForTeamManagementByTeamID(ctx, g, id, true) 1622 if err != nil { 1623 return err 1624 } 1625 1626 if !settings.Open && !t.IsOpen() { 1627 g.Log.CDebugf(ctx, "team is already closed, just returning: %s", id) 1628 return nil 1629 } 1630 1631 if settings.Open && t.IsOpen() && t.OpenTeamJoinAs() == settings.JoinAs { 1632 g.Log.CDebugf(ctx, "team is already open with default role: team: %s role: %s", 1633 id, strings.ToLower(t.OpenTeamJoinAs().String())) 1634 return nil 1635 } 1636 1637 // Rotate if team is moving from open to closed. 1638 rotateKey := t.IsOpen() && !settings.Open 1639 return t.PostTeamSettings(ctx, settings, rotateKey) 1640 }) 1641 } 1642 1643 func removeMemberInvite(ctx context.Context, g *libkb.GlobalContext, team *Team, username string, uv keybase1.UserVersion) (err error) { 1644 g.CTrace(ctx, "removeMemberInvite", &err) 1645 var lookingFor keybase1.TeamInviteName 1646 var typ string 1647 if !uv.IsNil() { 1648 lookingFor = uv.TeamInviteName() 1649 typ = "keybase" 1650 } else { 1651 ptyp, name, err := parseSocialAssertion(libkb.NewMetaContext(ctx, g), username) 1652 if err != nil { 1653 return err 1654 } 1655 lookingFor = keybase1.TeamInviteName(name) 1656 typ = ptyp 1657 } 1658 1659 return removeMemberInviteOfType(ctx, g, team, lookingFor, typ) 1660 } 1661 1662 func removeMemberInviteOfType(ctx context.Context, g *libkb.GlobalContext, team *Team, inviteName keybase1.TeamInviteName, typ string) error { 1663 g.Log.CDebugf(ctx, "looking for active invite in %s for %s/%s", team.Name(), typ, inviteName) 1664 1665 // make sure this is a valid invite type 1666 itype, err := TeamInviteTypeFromString(libkb.NewMetaContext(ctx, g), typ) 1667 if err != nil { 1668 return err 1669 } 1670 validatedType, err := itype.String() 1671 if err != nil { 1672 return err 1673 } 1674 1675 for _, invMD := range team.chain().ActiveInvites() { 1676 inv := invMD.Invite 1677 invTypeStr, err := inv.Type.String() 1678 if err != nil { 1679 return err 1680 } 1681 if invTypeStr != validatedType { 1682 continue 1683 } 1684 if inv.Name != inviteName { 1685 continue 1686 } 1687 1688 g.Log.CDebugf(ctx, "found invite %s for %s/%s, removing it", inv.Id, validatedType, inviteName) 1689 return removeInviteID(ctx, team, inv.Id) 1690 } 1691 1692 g.Log.CDebugf(ctx, "no invites found to remove for %s/%s", validatedType, inviteName) 1693 return NewMemberNotFoundInChainError(libkb.NotFoundError{}) 1694 } 1695 1696 func removeKeybaseTypeInviteForUID(ctx context.Context, g *libkb.GlobalContext, team *Team, uid keybase1.UID) (err error) { 1697 g.CTrace(ctx, "removeKeybaseTypeInviteForUID", &err) 1698 g.Log.CDebugf(ctx, "looking for active or obsolete keybase-type invite in %s for %s", team.Name(), uid) 1699 1700 // Remove all invites with given UID, so we don't have to worry 1701 // about old teams that might have duplicates. 1702 1703 var toRemove []keybase1.TeamInviteID 1704 allInvites := team.GetActiveAndObsoleteInvites() 1705 for _, invite := range allInvites { 1706 if inviteUv, err := invite.KeybaseUserVersion(); err == nil { 1707 if inviteUv.Uid.Equal(uid) { 1708 g.Log.CDebugf(ctx, "found keybase-type invite %s for %s, removing", invite.Id, invite.Name) 1709 toRemove = append(toRemove, invite.Id) 1710 } 1711 } 1712 } 1713 1714 if len(toRemove) > 0 { 1715 g.Log.CDebugf(ctx, "found %d keybase-type invites for %s, trying to post remove invite link", 1716 len(toRemove), uid) 1717 return removeMultipleInviteIDs(ctx, team, toRemove) 1718 } 1719 1720 g.Log.CDebugf(ctx, "no keybase-invites found to remove %s", uid) 1721 return NewMemberNotFoundInChainError(libkb.NotFoundError{}) 1722 } 1723 1724 func removeMultipleInviteIDs(ctx context.Context, team *Team, invIDs []keybase1.TeamInviteID) error { 1725 var cancelList []SCTeamInviteID 1726 for _, invID := range invIDs { 1727 cancelList = append(cancelList, SCTeamInviteID(invID)) 1728 } 1729 invites := SCTeamInvites{ 1730 Cancel: &cancelList, 1731 } 1732 return team.postTeamInvites(ctx, invites) 1733 } 1734 1735 func removeInviteID(ctx context.Context, team *Team, invID keybase1.TeamInviteID) (err error) { 1736 defer team.MetaContext(ctx).Trace("remoteInviteID", &err)() 1737 cancelList := []SCTeamInviteID{SCTeamInviteID(invID)} 1738 invites := SCTeamInvites{ 1739 Cancel: &cancelList, 1740 } 1741 return team.postTeamInvites(ctx, invites) 1742 } 1743 1744 func CreateSeitanToken(ctx context.Context, g *libkb.GlobalContext, teamname string, role keybase1.TeamRole, label keybase1.SeitanKeyLabel) (keybase1.SeitanIKey, error) { 1745 t, err := GetForTeamManagementByStringName(ctx, g, teamname, true) 1746 if err != nil { 1747 return "", err 1748 } 1749 ikey, err := t.InviteSeitan(ctx, role, label) 1750 if err != nil { 1751 return "", err 1752 } 1753 1754 return keybase1.SeitanIKey(ikey), err 1755 } 1756 1757 func CreateSeitanTokenV2(ctx context.Context, g *libkb.GlobalContext, teamname string, role keybase1.TeamRole, label keybase1.SeitanKeyLabel) (keybase1.SeitanIKeyV2, error) { 1758 t, err := GetForTeamManagementByStringName(ctx, g, teamname, true) 1759 if err != nil { 1760 return "", err 1761 } 1762 ikey, err := t.InviteSeitanV2(ctx, role, label) 1763 if err != nil { 1764 return "", err 1765 } 1766 1767 return keybase1.SeitanIKeyV2(ikey), err 1768 } 1769 1770 func CreateInvitelink(mctx libkb.MetaContext, teamname string, 1771 role keybase1.TeamRole, maxUses keybase1.TeamInviteMaxUses, 1772 etime *keybase1.UnixTime) (invitelink keybase1.Invitelink, err error) { 1773 t, err := GetForTeamManagementByStringName(mctx.Ctx(), mctx.G(), teamname, true) 1774 if err != nil { 1775 return invitelink, err 1776 } 1777 ikey, id, err := t.InviteInvitelink(mctx.Ctx(), role, maxUses, etime) 1778 if err != nil { 1779 return invitelink, err 1780 } 1781 shortID, err := id.ToShortInviteID() 1782 if err != nil { 1783 return invitelink, err 1784 } 1785 return keybase1.Invitelink{ 1786 Ikey: ikey, 1787 Url: GenerateInvitelinkURL(mctx, ikey, shortID), 1788 }, err 1789 } 1790 1791 // CreateTLF is called by KBFS when a TLF ID is associated with an implicit team. 1792 // Should work on either named or implicit teams. 1793 func CreateTLF(ctx context.Context, g *libkb.GlobalContext, arg keybase1.CreateTLFArg) (err error) { 1794 defer g.CTrace(ctx, fmt.Sprintf("CreateTLF(%v)", arg), &err)() 1795 ctx = libkb.WithLogTag(ctx, "CREATETLF") 1796 return RetryIfPossible(ctx, g, func(ctx context.Context, _ int) error { 1797 t, err := GetForTeamManagementByTeamID(ctx, g, arg.TeamID, false) 1798 if err != nil { 1799 return err 1800 } 1801 role, err := t.myRole(ctx) 1802 if err != nil { 1803 return err 1804 } 1805 if !role.IsWriterOrAbove() { 1806 return fmt.Errorf("permission denied: need writer access (or above)") 1807 } 1808 return t.AssociateWithTLFID(ctx, arg.TlfID) 1809 }) 1810 } 1811 1812 func GetKBFSTeamSettings(ctx context.Context, g *libkb.GlobalContext, isPublic bool, teamID keybase1.TeamID) (res keybase1.KBFSTeamSettings, err error) { 1813 defer g.CTrace(ctx, fmt.Sprintf("GetKBFSTeamSettings(%v,%v)", isPublic, teamID), &err)() 1814 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 1815 ID: teamID, 1816 Public: isPublic, 1817 }) 1818 if err != nil { 1819 return res, err 1820 } 1821 res.TlfID = team.LatestKBFSTLFID() 1822 g.Log.CDebugf(ctx, "res: %+v", res) 1823 return res, err 1824 } 1825 1826 func CanUserPerform(ctx context.Context, g *libkb.GlobalContext, teamname string) (ret keybase1.TeamOperation, err error) { 1827 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 1828 Name: teamname, 1829 StaleOK: true, 1830 Public: false, // assume private team 1831 AllowNameLookupBurstCache: true, 1832 AuditMode: keybase1.AuditMode_SKIP, 1833 }) 1834 if err != nil { 1835 // Note: we eat the error here, assuming it meant this user 1836 // is not a member 1837 g.Log.CWarningf(ctx, "CanUserPerform team Load failure, continuing: %v)", err) 1838 return ret, nil 1839 } 1840 meUV, err := team.currentUserUV(ctx) 1841 if err != nil { 1842 return ret, err 1843 } 1844 1845 getIsImplicitAdmin := func() (bool, error) { 1846 if team.ID.IsRootTeam() { 1847 return false, nil 1848 } 1849 uvs, err := g.GetTeamLoader().ImplicitAdmins(ctx, team.ID) 1850 if err != nil { 1851 return false, err 1852 } 1853 for _, uv := range uvs { 1854 if uv == meUV { 1855 return true, nil 1856 } 1857 } 1858 return false, nil 1859 } 1860 1861 teamRole, err := team.MemberRole(ctx, meUV) 1862 if err != nil { 1863 return ret, err 1864 } 1865 1866 isRoleOrAbove := teamRole.IsOrAbove 1867 1868 canMemberShowcase := func() (bool, error) { 1869 if teamRole.IsOrAbove(keybase1.TeamRole_ADMIN) { 1870 return true, nil 1871 } else if teamRole == keybase1.TeamRole_NONE { 1872 return false, nil 1873 } 1874 showcase, err := GetTeamShowcase(ctx, g, team.ID) 1875 if err != nil { 1876 return false, err 1877 } 1878 return showcase.AnyMemberShowcase, nil 1879 } 1880 1881 getHasOtherOwner := func() (bool, error) { 1882 owners, err := team.UsersWithRole(keybase1.TeamRole_OWNER) 1883 if err != nil { 1884 return false, err 1885 } 1886 if len(owners) > 1 { 1887 return true, nil 1888 } 1889 for _, owner := range owners { 1890 if owner == meUV { 1891 g.Log.CDebugf(ctx, "hasOtherOwner: I am the sole owner") 1892 return false, nil 1893 } 1894 } 1895 return true, nil 1896 } 1897 1898 isBot := isRoleOrAbove(keybase1.TeamRole_BOT) 1899 isWriter := isRoleOrAbove(keybase1.TeamRole_WRITER) 1900 isAdmin := isRoleOrAbove(keybase1.TeamRole_ADMIN) 1901 isOwner := isRoleOrAbove(keybase1.TeamRole_OWNER) 1902 isImplicitAdmin, err := getIsImplicitAdmin() 1903 if err != nil { 1904 return ret, err 1905 } 1906 1907 // team settings 1908 ret.ListFirst = isImplicitAdmin 1909 ret.JoinTeam = teamRole == keybase1.TeamRole_NONE && isImplicitAdmin 1910 ret.SetPublicityAny = isAdmin || isImplicitAdmin 1911 ret.ManageMembers = isAdmin || isImplicitAdmin 1912 ret.ManageSubteams = isAdmin || isImplicitAdmin 1913 ret.RenameTeam = team.IsSubteam() && isImplicitAdmin 1914 ret.SetTeamShowcase = isAdmin || isImplicitAdmin 1915 ret.ChangeOpenTeam = isAdmin || isImplicitAdmin 1916 ret.ChangeTarsDisabled = isAdmin || isImplicitAdmin 1917 ret.EditTeamDescription = isAdmin || isImplicitAdmin 1918 ret.ManageBots = isAdmin || isImplicitAdmin 1919 ret.ManageEmojis = isWriter 1920 ret.DeleteOtherEmojis = isAdmin 1921 ret.SetMemberShowcase, err = canMemberShowcase() 1922 if err != nil { 1923 return ret, err 1924 } 1925 if team.chain().IsSubteam() { 1926 ret.DeleteTeam = isImplicitAdmin 1927 } else { 1928 ret.DeleteTeam = isOwner 1929 } 1930 1931 // only check hasOtherOwner if we have to. 1932 if teamRole != keybase1.TeamRole_NONE { 1933 leaveTeam := true 1934 if isOwner { 1935 hasOtherOwner, err := getHasOtherOwner() 1936 if err != nil { 1937 return ret, err 1938 } 1939 leaveTeam = hasOtherOwner 1940 } 1941 ret.LeaveTeam = leaveTeam 1942 } 1943 1944 // chat settings 1945 ret.Chat = isBot 1946 ret.CreateChannel = isWriter 1947 ret.RenameChannel = isWriter 1948 ret.EditChannelDescription = isWriter 1949 ret.DeleteChannel = isAdmin 1950 ret.SetRetentionPolicy = isAdmin 1951 ret.SetMinWriterRole = isAdmin 1952 ret.DeleteChatHistory = isAdmin 1953 ret.DeleteOtherMessages = isAdmin 1954 ret.PinMessage = isWriter 1955 1956 return ret, err 1957 } 1958 1959 func RotateKey(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamRotateKeyArg) (err error) { 1960 teamID := arg.TeamID 1961 defer g.CTrace(ctx, fmt.Sprintf("RotateKey(%+v)", arg), &err)() 1962 return RetryIfPossible(ctx, g, func(ctx context.Context, attempt int) error { 1963 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 1964 ID: teamID, 1965 Public: teamID.IsPublic(), 1966 ForceRepoll: attempt > 0, 1967 }) 1968 if err != nil { 1969 return err 1970 } 1971 return team.Rotate(ctx, arg.Rt) 1972 }) 1973 } 1974 1975 func RotateKeyVisible(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) error { 1976 return RotateKey(ctx, g, keybase1.TeamRotateKeyArg{TeamID: id, Rt: keybase1.RotationType_VISIBLE}) 1977 } 1978 1979 func TeamDebug(ctx context.Context, g *libkb.GlobalContext, teamID keybase1.TeamID) (res keybase1.TeamDebugRes, err error) { 1980 defer g.CTrace(ctx, fmt.Sprintf("TeamDebug(%v)", teamID), &err)() 1981 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 1982 ID: teamID, 1983 Public: teamID.IsPublic(), 1984 ForceRepoll: true, 1985 }) 1986 if err != nil { 1987 return res, err 1988 } 1989 return keybase1.TeamDebugRes{Chain: team.Data.Chain}, nil 1990 } 1991 1992 func MapImplicitTeamIDToDisplayName(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, isPublic bool) (folder keybase1.Folder, err error) { 1993 1994 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 1995 ID: id, 1996 Public: isPublic, 1997 }) 1998 if err != nil { 1999 return folder, err 2000 } 2001 2002 if !team.IsImplicit() { 2003 return folder, NewExplicitTeamOperationError("MapImplicitTeamIDToDisplayName") 2004 } 2005 2006 itdn, err := team.ImplicitTeamDisplayName(ctx) 2007 if err != nil { 2008 return folder, err 2009 } 2010 2011 folder.Name, err = FormatImplicitTeamDisplayName(ctx, g, itdn) 2012 if err != nil { 2013 return folder, err 2014 } 2015 folder.Private = !isPublic 2016 if isPublic { 2017 folder.FolderType = keybase1.FolderType_PUBLIC 2018 } else { 2019 folder.FolderType = keybase1.FolderType_PRIVATE 2020 } 2021 return folder, nil 2022 } 2023 2024 type disableTARsRes struct { 2025 Status libkb.AppStatus `json:"status"` 2026 Disabled bool `json:"disabled"` 2027 } 2028 2029 func (c *disableTARsRes) GetAppStatus() *libkb.AppStatus { 2030 return &c.Status 2031 } 2032 2033 func GetTarsDisabled(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID) (bool, error) { 2034 mctx := libkb.NewMetaContext(ctx, g) 2035 arg := apiArg("team/disable_tars") 2036 arg.Args.Add("tid", libkb.S{Val: id.String()}) 2037 var ret disableTARsRes 2038 if err := mctx.G().API.GetDecode(mctx, arg, &ret); err != nil { 2039 return false, err 2040 } 2041 2042 return ret.Disabled, nil 2043 } 2044 2045 func SetTarsDisabled(ctx context.Context, g *libkb.GlobalContext, id keybase1.TeamID, disabled bool) error { 2046 mctx := libkb.NewMetaContext(ctx, g) 2047 t, err := GetForTeamManagementByTeamID(ctx, g, id, true) 2048 if err != nil { 2049 return err 2050 } 2051 2052 arg := apiArg("team/disable_tars") 2053 arg.Args.Add("tid", libkb.S{Val: id.String()}) 2054 arg.Args.Add("disabled", libkb.B{Val: disabled}) 2055 if _, err := mctx.G().API.Post(mctx, arg); err != nil { 2056 return err 2057 } 2058 return t.notifyNoChainChange(ctx, keybase1.TeamChangeSet{Misc: true}) 2059 } 2060 2061 type listProfileAddServerRes struct { 2062 libkb.AppStatusEmbed 2063 Teams []listProfileAddResEntry `json:"teams"` 2064 } 2065 2066 type listProfileAddResEntry struct { 2067 TeamID keybase1.TeamID `json:"team_id"` 2068 FqName string `json:"fq_name"` 2069 IsOpenTeam bool `json:"is_open_team"` 2070 // Whether the caller has admin powers. 2071 CallerAdmin bool `json:"caller_admin"` 2072 // Whether the 'them' user is an explicit member. 2073 ThemMember bool `json:"them_member"` 2074 } 2075 2076 func TeamProfileAddList(ctx context.Context, g *libkb.GlobalContext, username string) (res []keybase1.TeamProfileAddEntry, err error) { 2077 uname := kbun.NewNormalizedUsername(username) 2078 uid, err := g.GetUPAKLoader().LookupUID(ctx, uname) 2079 if err != nil { 2080 return nil, err 2081 } 2082 arg := apiArg("team/list_profile_add") 2083 arg.Args.Add("uid", libkb.S{Val: uid.String()}) 2084 var serverRes listProfileAddServerRes 2085 mctx := libkb.NewMetaContext(ctx, g) 2086 if err = mctx.G().API.GetDecode(mctx, arg, &serverRes); err != nil { 2087 return nil, err 2088 } 2089 for _, entry := range serverRes.Teams { 2090 teamName, err := keybase1.TeamNameFromString(entry.FqName) 2091 if err != nil { 2092 mctx.Debug("TeamProfileAddList server returned bad team name %v: %v", entry.FqName, err) 2093 continue 2094 } 2095 disabledReason := "" 2096 if !entry.CallerAdmin { 2097 disabledReason = "Only admins can add people." 2098 } else if entry.ThemMember { 2099 disabledReason = fmt.Sprintf("%v is already a member.", uname.String()) 2100 } 2101 res = append(res, keybase1.TeamProfileAddEntry{ 2102 TeamID: entry.TeamID, 2103 TeamName: teamName, 2104 Open: entry.IsOpenTeam, 2105 DisabledReason: disabledReason, 2106 }) 2107 } 2108 return res, nil 2109 } 2110 2111 func ChangeTeamAvatar(mctx libkb.MetaContext, arg keybase1.UploadTeamAvatarArg) error { 2112 team, err := Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 2113 Name: arg.Teamname, 2114 Public: false, 2115 ForceRepoll: false, 2116 NeedAdmin: true, 2117 }) 2118 if err != nil { 2119 return fixupTeamGetError(mctx.Ctx(), mctx.G(), err, arg.Teamname, false /* public */) 2120 } 2121 2122 if err := avatars.UploadImage(mctx, arg.Filename, &team.ID, arg.Crop); err != nil { 2123 return err 2124 } 2125 2126 if arg.SendChatNotification { 2127 SendTeamChatChangeAvatar(mctx, team.Name().String(), mctx.G().Env.GetUsername().String()) 2128 } 2129 return nil 2130 } 2131 2132 func FindNextMerkleRootAfterRemoval(mctx libkb.MetaContext, arg keybase1.FindNextMerkleRootAfterTeamRemovalBySigningKeyArg) (res keybase1.NextMerkleRootRes, err error) { 2133 defer mctx.Trace(fmt.Sprintf("teams.FindNextMerkleRootAfterRemoval(%+v)", arg), &err)() 2134 2135 team, err := Load(mctx.Ctx(), mctx.G(), keybase1.LoadTeamArg{ 2136 ID: arg.Team, 2137 Public: arg.IsPublic, 2138 ForceRepoll: false, 2139 NeedAdmin: false, 2140 }) 2141 if err != nil { 2142 return res, err 2143 } 2144 upak, _, err := mctx.G().GetUPAKLoader().LoadV2(libkb.NewLoadUserArgWithMetaContext(mctx). 2145 WithUID(arg.Uid). 2146 WithPublicKeyOptional(). 2147 WithForcePoll(false)) 2148 if err != nil { 2149 return res, err 2150 } 2151 2152 vers, _ := upak.FindKID(arg.SigningKey) 2153 if vers == nil { 2154 return res, libkb.NotFoundError{Msg: fmt.Sprintf("KID %s not found for %s", arg.SigningKey, arg.Uid)} 2155 } 2156 2157 uv := vers.ToUserVersion() 2158 logPoints := team.chain().inner.UserLog[uv] 2159 demotionPredicate := func(p keybase1.UserLogPoint) bool { 2160 if arg.AnyRoleAllowed { 2161 return !p.Role.IsBotOrAbove() 2162 } 2163 return !p.Role.IsWriterOrAbove() 2164 } 2165 var earliestDemotion int 2166 var logPoint *keybase1.UserLogPoint 2167 for i := len(logPoints) - 1; i >= 0; i-- { 2168 if demotionPredicate(logPoints[i]) { 2169 earliestDemotion = i 2170 } else if earliestDemotion != 0 { 2171 p := logPoints[earliestDemotion].DeepCopy() 2172 logPoint = &p 2173 break 2174 } 2175 } 2176 if logPoint == nil { 2177 return res, libkb.NotFoundError{Msg: "no downgraded log point for user found"} 2178 } 2179 2180 return libkb.FindNextMerkleRootAfterTeamRemoval(mctx, keybase1.FindNextMerkleRootAfterTeamRemovalArg{ 2181 Uid: arg.Uid, 2182 Team: arg.Team, 2183 IsPublic: arg.IsPublic, 2184 TeamSigchainSeqno: logPoint.SigMeta.SigChainLocation.Seqno, 2185 Prev: logPoint.SigMeta.PrevMerkleRootSigned, 2186 }) 2187 } 2188 2189 func ProfileTeamLoad(mctx libkb.MetaContext, arg keybase1.LoadTeamArg) (res keybase1.ProfileTeamLoadRes, err error) { 2190 pre := mctx.G().Clock().Now() 2191 _, err = Load(mctx.Ctx(), mctx.G(), arg) 2192 post := mctx.G().Clock().Now() 2193 res.LoadTimeNsec = post.Sub(pre).Nanoseconds() 2194 return res, err 2195 } 2196 2197 func GetTeamIDByNameRPC(mctx libkb.MetaContext, teamName string) (res keybase1.TeamID, err error) { 2198 nameParsed, err := keybase1.TeamNameFromString(teamName) 2199 if err != nil { 2200 return "", err 2201 } 2202 id, err := ResolveNameToID(mctx.Ctx(), mctx.G(), nameParsed) 2203 if err != nil { 2204 return "", err 2205 } 2206 return id, nil 2207 } 2208 2209 func FindAssertionsInTeamNoResolve(mctx libkb.MetaContext, teamID keybase1.TeamID, assertions []string) (ret []string, err error) { 2210 team, err := GetForTeamManagementByTeamID(mctx.Ctx(), mctx.G(), teamID, true /* needAdmin */) 2211 if err != nil { 2212 return nil, err 2213 } 2214 2215 // Don't check one assertion more than once, if we got duplicates. 2216 checkedAssertions := make(map[string]struct{}) 2217 2218 actx := externals.MakeAssertionContext(mctx) 2219 for _, assertionStr := range assertions { 2220 if _, found := checkedAssertions[assertionStr]; found { 2221 continue 2222 } 2223 checkedAssertions[assertionStr] = struct{}{} 2224 2225 assertion, err := libkb.AssertionParseAndOnly(actx, assertionStr) 2226 if err != nil { 2227 return nil, fmt.Errorf("failed to parse assertion %q: %w", assertionStr, err) 2228 } 2229 2230 var url libkb.AssertionURL 2231 urls := assertion.CollectUrls(nil) 2232 if len(urls) > 1 { 2233 // For compound assertions, try to find exactly one Keybase 2234 // assertion among the factors. Any other compound assertions 2235 // should not have been passed to this function. 2236 for _, u := range urls { 2237 if u.IsKeybase() { 2238 if url != nil { 2239 return nil, fmt.Errorf("assertion %q has more than one Keybase username", assertionStr) 2240 } 2241 url = u 2242 } 2243 } 2244 if url == nil { 2245 return nil, fmt.Errorf("assertion %q does not have Keybase username", assertionStr) 2246 } 2247 } else { 2248 url = urls[0] 2249 } 2250 2251 if url.IsKeybase() { 2252 // Load the user to get the right eldest seqno. We don't want 2253 // untrusted seqnos here from uidmapper, because UI might be 2254 // making decisions about whom to add to the team basing on 2255 // results from this function. 2256 loadUserArg := libkb.NewLoadUserArgWithMetaContext(mctx).WithName(url.GetValue()).WithPublicKeyOptional() 2257 user, err := libkb.LoadUser(loadUserArg) 2258 if err != nil { 2259 if _, ok := err.(libkb.NotFoundError); ok { 2260 // User not found - that's fine. 2261 continue 2262 } 2263 return nil, fmt.Errorf("error when loading user for assertion %q: %w", assertionStr, err) 2264 } 2265 if user.GetStatus() != keybase1.StatusCode_SCOk { 2266 // User is deleted or similar. Skip for now. 2267 continue 2268 } 2269 uv := user.ToUserVersion() 2270 _, kbInviteUV, found := team.FindActiveKeybaseInvite(user.GetUID()) 2271 if found && kbInviteUV.Eq(uv) { 2272 // Either user still doesn't have a PUK, or if they do, they 2273 // should be added automatically through team_rekeyd 2274 // notification soon. 2275 ret = append(ret, assertionStr) 2276 continue 2277 } 2278 2279 teamUVs := team.AllUserVersionsByUID(mctx.Ctx(), user.GetUID()) 2280 for _, teamUV := range teamUVs { 2281 if teamUV.Eq(uv) { 2282 ret = append(ret, assertionStr) 2283 break 2284 } 2285 // or else user is in the team but with old UV, so it's fine to 2286 // add them again. 2287 } 2288 } else { 2289 social, err := assertion.ToSocialAssertion() 2290 if err != nil { 2291 return nil, fmt.Errorf( 2292 "Don't know what to do with %q - not a social assertion or keybase username: %w", 2293 assertionStr, err) 2294 } 2295 hasInvite, err := team.HasActiveInvite(mctx, social.TeamInviteName(), social.TeamInviteType()) 2296 if err != nil { 2297 return nil, fmt.Errorf("Failed checking %q: %w", assertionStr, err) 2298 } 2299 if hasInvite { 2300 ret = append(ret, assertionStr) 2301 } 2302 } 2303 } 2304 2305 return ret, nil 2306 }