github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/list.go (about) 1 package teams 2 3 import ( 4 "fmt" 5 "sort" 6 7 "golang.org/x/net/context" 8 9 "github.com/keybase/client/go/libkb" 10 "github.com/keybase/client/go/protocol/keybase1" 11 "github.com/keybase/client/go/uidmap" 12 ) 13 14 type statusList struct { 15 Teams []keybase1.MemberInfo `json:"teams"` 16 Status libkb.AppStatus `json:"status"` 17 } 18 19 func (r *statusList) GetAppStatus() *libkb.AppStatus { 20 return &r.Status 21 } 22 23 func getTeamsListFromServer(ctx context.Context, g *libkb.GlobalContext, uid keybase1.UID, all bool, 24 countMembers bool, includeImplicitTeams bool, rootTeamID keybase1.TeamID) ([]keybase1.MemberInfo, error) { 25 var endpoint string 26 if all { 27 endpoint = "team/teammates_for_user" 28 } else { 29 endpoint = "team/for_user" 30 } 31 a := libkb.NewAPIArg(endpoint) 32 a.Args = libkb.HTTPArgs{} 33 if uid.Exists() { 34 a.Args["uid"] = libkb.S{Val: uid.String()} 35 } 36 if countMembers { 37 a.Args["count_members"] = libkb.B{Val: true} 38 } 39 if !rootTeamID.IsNil() { 40 a.Args["root_team_id"] = libkb.S{Val: rootTeamID.String()} 41 } 42 if includeImplicitTeams { 43 a.Args["include_implicit_teams"] = libkb.B{Val: true} 44 } 45 mctx := libkb.NewMetaContext(ctx, g) 46 a.SessionType = libkb.APISessionTypeREQUIRED 47 var list statusList 48 if err := mctx.G().API.GetDecode(mctx, a, &list); err != nil { 49 return nil, err 50 } 51 return list.Teams, nil 52 } 53 54 func memberNeedAdmin(member keybase1.MemberInfo, meUID keybase1.UID) bool { 55 return member.UserID == meUID && 56 (member.Role.IsAdminOrAbove() || (member.Implicit != nil && member.Implicit.Role.IsAdminOrAbove())) 57 } 58 59 // verifyMemberRoleInTeam checks if role give in MemberInfo matches 60 // what team chain says. Nothing is checked when MemberInfo's role is 61 // NONE, in this context it means that user has implied membership in 62 // the team and no role given in sigchain. 63 func verifyMemberRoleInTeam(ctx context.Context, userID keybase1.UID, expectedRole keybase1.TeamRole, 64 team *Team) (res keybase1.UserVersion, err error) { 65 if expectedRole == keybase1.TeamRole_NONE { 66 return res, nil 67 } 68 69 memberUV, err := team.chain().GetLatestUVWithUID(userID) 70 if err != nil { 71 return res, err 72 } 73 role, err := team.chain().GetUserRole(memberUV) 74 if err != nil { 75 return res, err 76 } 77 if role != expectedRole { 78 return res, fmt.Errorf("unexpected member role: expected %v but actual role is %v", expectedRole, role) 79 } 80 return memberUV, nil 81 } 82 83 type localLoadedTeams struct { 84 libkb.Contextified 85 teams map[keybase1.TeamID]*Team 86 } 87 88 func newLocalLoadedTeams(g *libkb.GlobalContext) localLoadedTeams { 89 return localLoadedTeams{ 90 Contextified: libkb.NewContextified(g), 91 teams: make(map[keybase1.TeamID]*Team), 92 } 93 } 94 95 // getTeamForMember tries to return *Team in a recent enough state to 96 // contain member with correct role as set in MemberInfo. It might 97 // trigger a reload with ForceRepoll if cached state does not match. 98 func (l *localLoadedTeams) getTeamForMember(ctx context.Context, member keybase1.MemberInfo, 99 needAdmin bool) (team *Team, uv keybase1.UserVersion, err error) { 100 teamID := member.TeamID 101 team = l.teams[teamID] 102 if team == nil { 103 // Team was not there in local cache - this is the first time 104 // localLoadedTeams is asked for this team. Try with no 105 // forceRepoll first. 106 team, err = Load(ctx, l.G(), keybase1.LoadTeamArg{ 107 ID: teamID, 108 NeedAdmin: needAdmin, 109 Public: teamID.IsPublic(), 110 ForceRepoll: false, 111 RefreshUIDMapper: true, 112 }) 113 if err != nil { 114 return nil, uv, err 115 } 116 l.teams[teamID] = team 117 } 118 119 memberUV, err := verifyMemberRoleInTeam(ctx, member.UserID, member.Role, team) 120 if err != nil { 121 team, err = Load(ctx, l.G(), keybase1.LoadTeamArg{ 122 ID: teamID, 123 NeedAdmin: needAdmin, 124 Public: teamID.IsPublic(), 125 ForceRepoll: true, 126 }) 127 if err != nil { 128 return nil, uv, err 129 } 130 l.teams[teamID] = team 131 132 memberUV, err = verifyMemberRoleInTeam(ctx, member.UserID, member.Role, team) 133 if err != nil { 134 return nil, uv, fmt.Errorf("server was wrong about role in team : %v", err) 135 } 136 } 137 138 return team, memberUV, nil 139 } 140 141 func getUsernameAndFullName(ctx context.Context, g *libkb.GlobalContext, 142 uid keybase1.UID) (username libkb.NormalizedUsername, fullName string, err error) { 143 username, err = g.GetUPAKLoader().LookupUsername(ctx, uid) 144 if err != nil { 145 return "", "", err 146 } 147 fullName, err = libkb.GetFullName(libkb.NewMetaContext(ctx, g), uid) 148 if err != nil { 149 return "", "", err 150 } 151 152 return username, fullName, err 153 } 154 155 func ListTeamsVerified(ctx context.Context, g *libkb.GlobalContext, 156 arg keybase1.TeamListVerifiedArg) (*keybase1.AnnotatedTeamList, error) { 157 tracer := g.CTimeTracer(ctx, "TeamList.ListTeamsVerified", true) 158 defer tracer.Finish() 159 m := libkb.NewMetaContext(ctx, g) 160 161 tracer.Stage("Resolve QueryUID") 162 var queryUID keybase1.UID 163 if arg.UserAssertion != "" { 164 res := g.Resolver.ResolveFullExpression(m, arg.UserAssertion) 165 if res.GetError() != nil { 166 return nil, res.GetError() 167 } 168 queryUID = res.GetUID() 169 } 170 171 meUID := g.ActiveDevice.UID() 172 if meUID.IsNil() { 173 return nil, libkb.LoginRequiredError{} 174 } 175 176 tracer.Stage("Server") 177 teams, err := getTeamsListFromServer(ctx, g, queryUID, false, /* all */ 178 false /* countMembers */, arg.IncludeImplicitTeams, keybase1.NilTeamID()) 179 if err != nil { 180 return nil, err 181 } 182 183 if arg.UserAssertion == "" { 184 queryUID = meUID 185 } 186 187 tracer.Stage("LookupQueryUsername") 188 queryUsername, queryFullName, err := getUsernameAndFullName(context.Background(), g, queryUID) 189 if err != nil { 190 return nil, err 191 } 192 193 res := &keybase1.AnnotatedTeamList{ 194 Teams: nil, 195 } 196 197 if len(teams) == 0 { 198 return res, nil 199 } 200 201 tracer.Stage("Loads") 202 203 loadedTeams := newLocalLoadedTeams(g) 204 expectEmptyList := true 205 206 for _, memberInfo := range teams { 207 serverSaysNeedAdmin := memberNeedAdmin(memberInfo, meUID) 208 team, _, err := loadedTeams.getTeamForMember(ctx, memberInfo, serverSaysNeedAdmin) 209 if err != nil { 210 m.Debug("| Error in getTeamForMember ID:%s UID:%s: %v; skipping team", memberInfo.TeamID, memberInfo.UserID, err) 211 expectEmptyList = false // so we tell user about errors at the end. 212 continue 213 } 214 215 if memberInfo.IsImplicitTeam && !arg.IncludeImplicitTeams { 216 m.Debug("| TeamList skipping implicit team: server-team:%v server-uid:%v", memberInfo.TeamID, memberInfo.UserID) 217 continue 218 } 219 220 expectEmptyList = false 221 222 if memberInfo.UserID != queryUID { 223 m.Debug("| Expected memberInfo for UID:%s, got UID:%s", queryUID, memberInfo.UserID) 224 continue 225 } 226 227 anMemberInfo := &keybase1.AnnotatedMemberInfo{ 228 TeamID: team.ID, 229 FqName: team.Name().String(), 230 UserID: memberInfo.UserID, 231 Role: memberInfo.Role, // memberInfo.Role has been verified during getTeamForMember 232 IsImplicitTeam: team.IsImplicit(), 233 IsOpenTeam: team.IsOpen(), 234 Implicit: memberInfo.Implicit, // This part is still server trust 235 Username: queryUsername.String(), 236 FullName: queryFullName, 237 MemberCount: 0, 238 Status: keybase1.TeamMemberStatus_ACTIVE, 239 AllowProfilePromote: memberInfo.AllowProfilePromote, 240 IsMemberShowcased: memberInfo.IsMemberShowcased, 241 } 242 243 if team.IsImplicit() { 244 displayName, err := team.ImplicitTeamDisplayNameString(ctx) 245 if err != nil { 246 m.Warning("| Failed to get ImplicitTeamDisplayNameString() for team %q: %v", team.ID, err) 247 } else { 248 anMemberInfo.ImpTeamDisplayName = displayName 249 } 250 } 251 252 anMemberInfo.MemberCount, err = team.calculateAndCacheMemberCount(ctx) 253 if err != nil { 254 continue 255 } 256 257 res.Teams = append(res.Teams, *anMemberInfo) 258 } 259 260 if len(res.Teams) == 0 && !expectEmptyList { 261 return res, fmt.Errorf("multiple errors while loading team list") 262 } 263 264 return res, nil 265 } 266 267 func ListAll(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamListTeammatesArg) (*keybase1.AnnotatedTeamList, error) { 268 tracer := g.CTimeTracer(ctx, "TeamList.ListAll", true) 269 defer tracer.Finish() 270 271 meUID := g.ActiveDevice.UID() 272 if meUID.IsNil() { 273 return nil, libkb.LoginRequiredError{} 274 } 275 276 tracer.Stage("Server") 277 teams, err := getTeamsListFromServer(ctx, g, "" /*uid*/, true /*all*/, false /* countMembers */, arg.IncludeImplicitTeams, keybase1.NilTeamID()) 278 if err != nil { 279 return nil, err 280 } 281 282 res := &keybase1.AnnotatedTeamList{ 283 Teams: nil, 284 } 285 286 if len(teams) == 0 { 287 return res, nil 288 } 289 290 tracer.Stage("Loads") 291 292 loadedTeams := newLocalLoadedTeams(g) 293 expectEmptyList := true 294 295 for _, memberInfo := range teams { 296 serverSaysNeedAdmin := memberNeedAdmin(memberInfo, meUID) 297 team, memberUV, err := loadedTeams.getTeamForMember(ctx, memberInfo, serverSaysNeedAdmin) 298 if err != nil { 299 g.Log.CDebugf(ctx, "| Error in getTeamForMember ID:%s UID:%s: %v; skipping team", memberInfo.TeamID, memberInfo.UserID, err) 300 expectEmptyList = false // so we tell user about errors at the end. 301 continue 302 } 303 304 // TODO: memberUV is always empty for implicit admins that are 305 // not real members, because getTeamForMember will not try to 306 // look into ancestor teams. 307 308 if memberInfo.IsImplicitTeam && !arg.IncludeImplicitTeams { 309 g.Log.CDebugf(ctx, "| TeamList skipping implicit team: server-team:%v server-uid:%v", memberInfo.TeamID, memberInfo.UserID) 310 continue 311 } 312 313 anMemberInfo := &keybase1.AnnotatedMemberInfo{ 314 TeamID: team.ID, 315 FqName: team.Name().String(), 316 UserID: memberInfo.UserID, 317 EldestSeqno: memberUV.EldestSeqno, 318 Role: memberInfo.Role, // memberInfo.Role has been verified during getTeamForMember 319 IsImplicitTeam: team.IsImplicit(), 320 Implicit: memberInfo.Implicit, // This part is still server trust 321 // Assume member is active, later this field might be 322 // mutated to false after consulting UIDMapper. 323 Status: keybase1.TeamMemberStatus_ACTIVE, 324 } 325 326 res.Teams = append(res.Teams, *anMemberInfo) 327 } 328 329 if len(teams) == 0 && !expectEmptyList { 330 return res, fmt.Errorf("Multiple errors during loading listAll") 331 } 332 333 tracer.Stage("UIDMapper") 334 335 var uids []keybase1.UID 336 for _, member := range res.Teams { 337 uids = append(uids, member.UserID) 338 } 339 340 namePkgs, err := uidmap.MapUIDsReturnMap(ctx, g.UIDMapper, g, uids, 341 defaultFullnameFreshness, defaultNetworkTimeBudget, true) 342 if err != nil { 343 // UIDMap returned an error, but there may be useful data in the result. 344 g.Log.CDebugf(ctx, "| MapUIDsReturnMap returned: %v", err) 345 } 346 347 for i := range res.Teams { 348 member := &res.Teams[i] 349 if pkg, ok := namePkgs[member.UserID]; ok { 350 member.Username = pkg.NormalizedUsername.String() 351 if pkg.FullName != nil { 352 member.FullName = string(pkg.FullName.FullName) 353 // TODO: We can't check if purely implicit admin is 354 // reset because we are not looking deep enough to get 355 // member uv. member.EldestSeqno will always be 0 for 356 // implicit admins. Only flag members that have actual 357 // role in the team here. 358 if member.Role != keybase1.TeamRole_NONE && pkg.FullName.EldestSeqno != member.EldestSeqno { 359 member.Status = keybase1.TeamMemberStatus_RESET 360 } 361 if pkg.FullName.Status == keybase1.StatusCode_SCDeleted { 362 member.Status = keybase1.TeamMemberStatus_DELETED 363 } 364 } 365 } 366 } 367 368 return res, nil 369 } 370 371 func ListSubteamsRecursive(ctx context.Context, g *libkb.GlobalContext, 372 parentTeamName string, forceRepoll bool) (res []keybase1.TeamIDAndName, err error) { 373 parent, err := Load(ctx, g, keybase1.LoadTeamArg{ 374 Name: parentTeamName, 375 NeedAdmin: true, 376 ForceRepoll: forceRepoll, 377 }) 378 if err != nil { 379 return nil, err 380 } 381 382 teams, err := parent.loadAllTransitiveSubteams(ctx, forceRepoll) 383 if err != nil { 384 return nil, err 385 } 386 387 for _, team := range teams { 388 res = append(res, keybase1.TeamIDAndName{ 389 Id: team.ID, 390 Name: team.Name(), 391 }) 392 } 393 return res, nil 394 } 395 396 const blankSeitanLabel = "<token without label>" 397 398 func ComputeSeitanInviteDisplayName(ctx context.Context, team *Team, invite keybase1.TeamInvite) (name keybase1.TeamInviteDisplayName, err error) { 399 pkey, err := SeitanDecodePKey(string(invite.Name)) 400 if err != nil { 401 return name, err 402 } 403 keyAndLabel, err := pkey.DecryptKeyAndLabel(ctx, team) 404 if err != nil { 405 return name, err 406 } 407 408 version, err := keyAndLabel.V() 409 if err != nil { 410 return name, err 411 } 412 var label keybase1.SeitanKeyLabel 413 switch version { 414 case keybase1.SeitanKeyAndLabelVersion_V1: 415 v1 := keyAndLabel.V1() 416 label = v1.L 417 case keybase1.SeitanKeyAndLabelVersion_V2: 418 v2 := keyAndLabel.V2() 419 label = v2.L 420 default: 421 return "", fmt.Errorf("Unknown version: %v", version) 422 } 423 424 labelType, err := label.T() 425 if err != nil { 426 return name, err 427 } 428 switch labelType { 429 case keybase1.SeitanKeyLabelType_SMS: 430 sms := label.Sms() 431 smsName := blankSeitanLabel 432 if sms.F != "" && sms.N != "" { 433 smsName = fmt.Sprintf("%s (%s)", sms.F, sms.N) 434 } else if sms.F != "" { 435 smsName = sms.F 436 } else if sms.N != "" { 437 smsName = sms.N 438 } 439 return keybase1.TeamInviteDisplayName(smsName), nil 440 case keybase1.SeitanKeyLabelType_GENERIC: 441 return keybase1.TeamInviteDisplayName(label.Generic().L), nil 442 default: 443 return keybase1.TeamInviteDisplayName(blankSeitanLabel), nil 444 } 445 } 446 447 func ComputeInvitelinkDisplayName(mctx libkb.MetaContext, team *Team, invite keybase1.TeamInvite) (name keybase1.TeamInviteDisplayName, err error) { 448 pkey, err := SeitanDecodePKey(string(invite.Name)) 449 if err != nil { 450 return name, err 451 } 452 keyAndLabel, err := pkey.DecryptKeyAndLabel(mctx.Ctx(), team) 453 if err != nil { 454 return name, err 455 } 456 457 version, err := keyAndLabel.V() 458 if err != nil { 459 return name, err 460 } 461 if version != keybase1.SeitanKeyAndLabelVersion_Invitelink { 462 return name, fmt.Errorf("AnnotateInvitelink: version not an invitelink: %v", version) 463 } 464 465 ikey := keyAndLabel.Invitelink().I 466 sikey, err := GenerateSIKeyInvitelink(ikey) 467 if err != nil { 468 return name, err 469 } 470 id, err := sikey.GenerateShortTeamInviteID() 471 if err != nil { 472 return name, err 473 } 474 475 url := GenerateInvitelinkURL(mctx, ikey, id) 476 name = keybase1.TeamInviteDisplayName(url) 477 return name, nil 478 } 479 480 func annotateTeamUsedInviteLogPoints(points []keybase1.TeamUsedInviteLogPoint, namePkgs map[keybase1.UID]libkb.UsernamePackage) (ret []keybase1.AnnotatedTeamUsedInviteLogPoint) { 481 for _, point := range points { 482 username := namePkgs[point.Uv.Uid].NormalizedUsername 483 ret = append(ret, keybase1.AnnotatedTeamUsedInviteLogPoint{TeamUsedInviteLogPoint: point, Username: username.String()}) 484 } 485 return ret 486 } 487 488 // GetAnnotatedInvitesAndMembersForUI returns annotated invites and members lists from the sigchain, 489 // except that Keybase-type invites are treated as team members. This is for the purpose of 490 // collecting them together in the UI. 491 func GetAnnotatedInvitesAndMembersForUI(mctx libkb.MetaContext, team *Team, 492 ) (members []keybase1.TeamMemberDetails, annotatedInvites []keybase1.AnnotatedTeamInvite, err error) { 493 494 allAnnotatedInvites, err := getAnnotatedInvites(mctx, team) 495 if err != nil { 496 return nil, nil, err 497 } 498 members, err = MembersDetails(mctx.Ctx(), mctx.G(), team) 499 if err != nil { 500 return nil, nil, err 501 } 502 503 for _, annotatedInvite := range allAnnotatedInvites { 504 inviteC, err := annotatedInvite.InviteMetadata.Invite.Type.C() 505 if err != nil { 506 return nil, nil, err 507 } 508 extC, err := annotatedInvite.InviteExt.C() 509 if err != nil { 510 return nil, nil, err 511 } 512 if inviteC != extC { 513 return nil, nil, fmt.Errorf("got invite category %v from invite but %v from inviteExt", inviteC, extC) 514 } 515 if inviteC == keybase1.TeamInviteCategory_KEYBASE { 516 code, err := annotatedInvite.InviteMetadata.Status.Code() 517 if err != nil { 518 return nil, nil, err 519 } 520 if code != keybase1.TeamInviteMetadataStatusCode_ACTIVE { 521 // Skip non active keybase-type invites 522 continue 523 } 524 525 keybaseExt := annotatedInvite.InviteExt.Keybase() 526 details := keybase1.TeamMemberDetails{ 527 Uv: keybaseExt.InviteeUv, 528 Username: keybaseExt.Username, 529 NeedsPUK: true, 530 FullName: keybaseExt.FullName, 531 Status: keybaseExt.Status, 532 Role: annotatedInvite.InviteMetadata.Invite.Role, 533 } 534 535 // Add the keybase-type invite to the members list. However, if there already exists a 536 // cryptomember who is inactive (reset or deleted), and additionally there is a 537 // keybase-type invite for the same UID, overwrite the inactive one with the invite. 538 overrodeExistingInactiveMember := false 539 for idx, member := range members { 540 if details.Uv.Uid.Equal(member.Uv.Uid) && !member.Status.IsActive() { 541 members[idx] = details 542 overrodeExistingInactiveMember = true 543 } 544 } 545 if !overrodeExistingInactiveMember { 546 members = append(members, details) 547 } 548 } else { 549 annotatedInvites = append(annotatedInvites, annotatedInvite) 550 } 551 } 552 553 return members, annotatedInvites, nil 554 } 555 556 func getAnnotatedInvites(mctx libkb.MetaContext, team *Team) (annotatedInvites []keybase1.AnnotatedTeamInvite, err error) { 557 inviteMDs := team.chain().inner.InviteMetadatas 558 if len(inviteMDs) == 0 { 559 return nil, nil 560 } 561 562 // UID list to pass to UIDMapper to get full names. Duplicate UIDs 563 // are fine, MapUIDsReturnMap will filter them out. 564 var uids []keybase1.UID 565 for _, inviteMD := range inviteMDs { 566 invite := inviteMD.Invite 567 uids = append(uids, invite.Inviter.Uid) 568 569 category, err := invite.Type.C() 570 if err != nil { 571 return nil, err 572 } 573 if category == keybase1.TeamInviteCategory_KEYBASE { 574 uv, err := invite.KeybaseUserVersion() 575 if err != nil { 576 return nil, err 577 } 578 uids = append(uids, uv.Uid) 579 } 580 for _, usedInviteLogPoint := range inviteMD.UsedInvites { 581 uids = append(uids, usedInviteLogPoint.Uv.Uid) 582 } 583 } 584 585 namePkgs, err := uidmap.MapUIDsReturnMapMctx(mctx, uids, defaultFullnameFreshness, 586 defaultNetworkTimeBudget, true) 587 if err != nil { 588 // UIDMap returned an error, but there may be useful data in the result. 589 mctx.Debug("AnnotateInvites: MapUIDsReturnMap failed: %s", err) 590 } 591 592 teamName := team.Name().String() 593 for _, inviteMD := range inviteMDs { 594 invite := inviteMD.Invite 595 inviterUsername := namePkgs[invite.Inviter.Uid].NormalizedUsername 596 597 category, err := invite.Type.C() 598 if err != nil { 599 return nil, err 600 } 601 // defaults; overridden by some invite types later 602 inviteExt := keybase1.NewAnnotatedTeamInviteExtDefault(category) 603 displayName := keybase1.TeamInviteDisplayName(invite.Name) 604 switch category { 605 case keybase1.TeamInviteCategory_SBS: 606 displayName = keybase1.TeamInviteDisplayName(fmt.Sprintf("%s@%s", invite.Name, string(invite.Type.Sbs()))) 607 case keybase1.TeamInviteCategory_KEYBASE: 608 // "keybase" invites (i.e. pukless users) have user version for name 609 inviteeUV, err := invite.KeybaseUserVersion() 610 if err != nil { 611 return nil, err 612 } 613 pkg := namePkgs[inviteeUV.Uid] 614 status := keybase1.TeamMemberStatus_ACTIVE 615 var fullName keybase1.FullName 616 if pkg.FullName != nil { 617 if pkg.FullName.EldestSeqno != inviteeUV.EldestSeqno { 618 status = keybase1.TeamMemberStatus_RESET 619 } 620 if pkg.FullName.Status == keybase1.StatusCode_SCDeleted { 621 status = keybase1.TeamMemberStatus_DELETED 622 } 623 fullName = pkg.FullName.FullName 624 } 625 626 if pkg.NormalizedUsername.IsNil() { 627 return nil, fmt.Errorf("failed to get username from UIDMapper for uv %v", inviteeUV) 628 } 629 630 displayName = keybase1.TeamInviteDisplayName(pkg.NormalizedUsername.String()) 631 inviteExt = keybase1.NewAnnotatedTeamInviteExtWithKeybase(keybase1.KeybaseInviteExt{ 632 InviteeUv: inviteeUV, 633 Status: status, 634 FullName: fullName, 635 Username: pkg.NormalizedUsername.String(), 636 }) 637 case keybase1.TeamInviteCategory_SEITAN: 638 displayName, err = ComputeSeitanInviteDisplayName(mctx.Ctx(), team, invite) 639 if err != nil { 640 // There are seitan invites in the wild from before 641 // https://github.com/keybase/client/pull/9816 These can no 642 // longer be decrypted, we hide them. 643 mctx.Debug("error annotating seitan invite (%v): %v", invite.Id, err) 644 continue 645 } 646 case keybase1.TeamInviteCategory_INVITELINK: 647 displayName, err = ComputeInvitelinkDisplayName(mctx, team, invite) 648 if err != nil { 649 mctx.Warning("error annotating invitelink (%v): %v", invite.Id, err) 650 continue 651 } 652 inviteExt = keybase1.NewAnnotatedTeamInviteExtWithInvitelink(keybase1.InvitelinkInviteExt{ 653 AnnotatedUsedInvites: annotateTeamUsedInviteLogPoints(inviteMD.UsedInvites, namePkgs), 654 }) 655 default: 656 } 657 658 now := mctx.G().Clock().Now() 659 isValid, validityDescription := inviteMD.ComputeValidity(now, team.Data.Chain.UserLog) 660 annotatedInvites = append(annotatedInvites, keybase1.AnnotatedTeamInvite{ 661 InviteMetadata: inviteMD, 662 DisplayName: displayName, 663 InviterUsername: inviterUsername.String(), 664 TeamName: teamName, 665 InviteExt: inviteExt, 666 IsValid: isValid, 667 ValidityDescription: validityDescription, 668 }) 669 } 670 671 // Sort most recent first 672 sort.Slice(annotatedInvites, func(i, j int) bool { 673 iSeqno := annotatedInvites[i].InviteMetadata.TeamSigMeta.SigMeta.SigChainLocation.Seqno 674 jSeqno := annotatedInvites[j].InviteMetadata.TeamSigMeta.SigMeta.SigChainLocation.Seqno 675 return iSeqno >= jSeqno 676 }) 677 678 return annotatedInvites, nil 679 } 680 681 func TeamTreeUnverified(ctx context.Context, g *libkb.GlobalContext, arg keybase1.TeamTreeUnverifiedArg) (res keybase1.TeamTreeResult, err error) { 682 serverList, err := getTeamsListFromServer(ctx, g, "" /* uid */, false, /* all */ 683 false /* countMembers */, false /* includeImplicitTeams */, arg.Name.RootID()) 684 if err != nil { 685 return res, err 686 } 687 rootName := arg.Name.RootAncestorName() 688 689 // Map from team name (string) to entry 690 entryMap := make(map[string]keybase1.TeamTreeEntry) 691 692 // The server might have omitted some teams, oh well. 693 // Trusts the server for role. 694 // Load the teams by ID to make sure they are valid and get the validated names. 695 for _, info := range serverList { 696 serverName, err := info.TeamName() 697 if err != nil { 698 return res, err 699 } 700 if !rootName.IsAncestorOf(serverName) && !rootName.Eq(serverName) { 701 // Skip those not in this tree. 702 continue 703 } 704 team, err := Load(ctx, g, keybase1.LoadTeamArg{ 705 ID: info.TeamID, 706 ForceRepoll: true, 707 }) 708 if err != nil { 709 return res, err 710 } 711 var admin bool // true if an admin or implicit admin 712 if info.Role.IsAdminOrAbove() { 713 admin = true 714 } 715 if info.Implicit != nil && info.Implicit.Role.IsAdminOrAbove() { 716 admin = true 717 } 718 entryMap[team.Name().String()] = keybase1.TeamTreeEntry{ 719 Name: team.Name(), 720 Admin: admin, 721 } 722 } 723 724 // Add all parent names (recursively) So that if only A.B.C is in the list, 725 // we add A.B and A as well. Adding map entries while iterating is safe. 726 // "If map entries are created during iteration, that entry may be produced 727 // during the iteration or may be skipped." 728 for _, entry := range entryMap { 729 name := entry.Name.DeepCopy() 730 for name.Depth() > 0 { 731 _, ok := entryMap[name.String()] 732 if !ok { 733 entryMap[name.String()] = keybase1.TeamTreeEntry{ 734 Name: name, 735 Admin: false, 736 } 737 } 738 name, err = name.Parent() 739 if err != nil || (!rootName.IsAncestorOf(name) && !rootName.Eq(name)) { 740 break 741 } 742 } 743 } 744 745 for _, entry := range entryMap { 746 res.Entries = append(res.Entries, entry) 747 } 748 749 if len(res.Entries) == 0 { 750 g.Log.CDebugf(ctx, "| TeamTree not teams matched") 751 // Try to get a nicer error by loading the team directly. 752 _, err = Load(ctx, g, keybase1.LoadTeamArg{Name: arg.Name.String()}) 753 if err != nil { 754 return res, err 755 } 756 return res, fmt.Errorf("team not found: %v", rootName) 757 } 758 759 // Order into a tree order. Which happens to be alphabetical ordering. 760 // Example: [a, a.b, a.b.c, a.b.d, a.e.f, a.e.g] 761 sort.Slice(res.Entries, func(i, j int) bool { 762 return res.Entries[i].Name.String() < res.Entries[j].Name.String() 763 }) 764 765 return res, nil 766 }