1package teams 2 3import ( 4 "errors" 5 "fmt" 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/teams/hidden" 12 jsonw "github.com/keybase/go-jsonw" 13) 14 15// Create a new user/version pair. 16func NewUserVersion(uid keybase1.UID, eldestSeqno keybase1.Seqno) keybase1.UserVersion { 17 return keybase1.NewUserVersion(uid, eldestSeqno) 18} 19 20const TeamSigChainPlayerSupportedLinkVersion = 2 21 22// Accessor wrapper for keybase1.TeamSigChainState 23type TeamSigChainState struct { 24 inner keybase1.TeamSigChainState 25 hidden *keybase1.HiddenTeamChain 26} 27 28func newTeamSigChainState(t Teamer) TeamSigChainState { 29 ret := TeamSigChainState{hidden: t.HiddenChain()} 30 if t.MainChain() != nil { 31 ret.inner = t.MainChain().Chain 32 } 33 return ret 34} 35 36func (t TeamSigChainState) DeepCopy() TeamSigChainState { 37 ret := TeamSigChainState{ 38 inner: t.inner.DeepCopy(), 39 } 40 if t.hidden != nil { 41 tmp := t.hidden.DeepCopy() 42 ret.hidden = &tmp 43 } 44 return ret 45} 46 47func (t TeamSigChainState) DeepCopyToPtr() *TeamSigChainState { 48 t2 := t.DeepCopy() 49 return &t2 50} 51 52func (t TeamSigChainState) GetID() keybase1.TeamID { 53 return t.inner.Id 54} 55 56func (t TeamSigChainState) IsSubteam() bool { 57 return t.inner.ParentID != nil 58} 59 60func (t TeamSigChainState) IsImplicit() bool { 61 return t.inner.Implicit 62} 63 64func (t TeamSigChainState) IsPublic() bool { 65 return t.inner.Public 66} 67 68func (t TeamSigChainState) IsOpen() bool { 69 return t.inner.Open 70} 71 72func (t TeamSigChainState) LatestLastNamePart() keybase1.TeamNamePart { 73 return t.inner.NameLog[len(t.inner.NameLog)-1].LastPart 74} 75 76// Only non-nil if this is a subteam. 77func (t TeamSigChainState) GetParentID() *keybase1.TeamID { 78 return t.inner.ParentID 79} 80 81func (t TeamSigChainState) GetLatestSeqno() keybase1.Seqno { 82 return t.inner.LastSeqno 83} 84 85func (t TeamSigChainState) GetLatestHiddenSeqno() keybase1.Seqno { 86 if t.hidden == nil { 87 return keybase1.Seqno(0) 88 } 89 return t.hidden.Last 90} 91 92func (t TeamSigChainState) GetLatestLinkID() keybase1.LinkID { 93 return t.inner.LastLinkID 94} 95 96func (t TeamSigChainState) GetLatestHighSeqno() keybase1.Seqno { 97 return t.inner.LastHighSeqno 98} 99 100func (t TeamSigChainState) GetLatestHighLinkID() keybase1.LinkID { 101 return t.inner.LastHighLinkID 102} 103 104func (t TeamSigChainState) GetLatestLibkbLinkID() (libkb.LinkID, error) { 105 return libkb.ImportLinkID(t.GetLatestLinkID()) 106} 107 108func (t TeamSigChainState) GetLinkIDBySeqno(seqno keybase1.Seqno) (keybase1.LinkID, error) { 109 l1, ok := t.inner.LinkIDs[seqno] 110 if !ok { 111 return l1, fmt.Errorf("seqno %v not in chain", seqno) 112 } 113 return l1, nil 114} 115 116func (t TeamSigChainState) GetLibkbLinkIDBySeqno(seqno keybase1.Seqno) (l2 libkb.LinkID, err error) { 117 l1, err := t.GetLinkIDBySeqno(seqno) 118 if err != nil { 119 return l2, err 120 } 121 return libkb.ImportLinkID(l1) 122} 123 124func (t TeamSigChainState) GetLatestGeneration() keybase1.PerTeamKeyGeneration { 125 ret := t.inner.MaxPerTeamKeyGeneration 126 if h := t.hidden.MaxReaderPerTeamKeyGeneration(); h > ret { 127 ret = h 128 } 129 return ret 130} 131 132func (t TeamSigChainState) GetLatestKBFSGeneration(appType keybase1.TeamApplication) (int, error) { 133 info, ok := t.inner.TlfLegacyUpgrade[appType] 134 if !ok { 135 return 0, errors.New("no KBFS keys available") 136 } 137 return info.LegacyGeneration, nil 138} 139 140func (t TeamSigChainState) makeHiddenRatchet(mctx libkb.MetaContext) (ret *hidden.Ratchet, err error) { 141 return hidden.MakeRatchet(mctx, t.hidden) 142} 143 144func (t TeamSigChainState) GetUserRole(user keybase1.UserVersion) (keybase1.TeamRole, error) { 145 return t.getUserRole(user), nil 146} 147 148// Get the user's role right after link at seqno was processed. 149func (t TeamSigChainState) GetUserRoleAtSeqno(user keybase1.UserVersion, seqno keybase1.Seqno) (keybase1.TeamRole, error) { 150 role := keybase1.TeamRole_NONE 151 if seqno <= 0 { 152 return role, fmt.Errorf("seqno %v is less than 1", seqno) 153 } 154 for _, point := range t.inner.UserLog[user] { 155 if point.SigMeta.SigChainLocation.Seqno > seqno { 156 return role, nil 157 } 158 role = point.Role 159 } 160 return role, nil 161} 162 163func (t TeamSigChainState) MemberCtime(user keybase1.UserVersion) *keybase1.Time { 164 points := t.inner.UserLog[user] 165 if len(points) == 0 { 166 return nil 167 } 168 // see if the user ever left the team so we return their most recent join 169 // time. 170 for i := len(points) - 1; i > 0; i-- { 171 // if we left the team at some point, return our later join time 172 if points[i].Role == keybase1.TeamRole_NONE { 173 if i < len(points)-1 && points[i+1].Role != keybase1.TeamRole_NONE { 174 return &points[i+1].SigMeta.Time 175 } 176 } 177 } 178 // we never left the team, give our original join time. 179 return &points[0].SigMeta.Time 180} 181 182func (t TeamSigChainState) GetUserLogPoint(user keybase1.UserVersion) *keybase1.UserLogPoint { 183 points := t.inner.UserLog[user] 184 if len(points) == 0 { 185 return nil 186 } 187 tmp := points[len(points)-1].DeepCopy() 188 return &tmp 189} 190 191func (t TeamSigChainState) GetAdminUserLogPoint(user keybase1.UserVersion) *keybase1.UserLogPoint { 192 ret := t.GetUserLogPoint(user) 193 if ret == nil { 194 return nil 195 } 196 if ret.Role != keybase1.TeamRole_ADMIN && ret.Role != keybase1.TeamRole_OWNER { 197 return nil 198 } 199 return ret 200} 201 202func (t TeamSigChainState) getUserRole(user keybase1.UserVersion) keybase1.TeamRole { 203 return t.inner.UserRole(user) 204} 205 206func (t TeamSigChainState) GetUserLastJoinTime(user keybase1.UserVersion) (time keybase1.Time, err error) { 207 return t.inner.GetUserLastJoinTime(user) 208} 209 210// GetUserLastRoleChangeTime returns the time of the last role change for user 211// in team. If the user left the team as a last change, the time of such leave 212// event is returned. If the user was never in the team, then this function 213// returns time=0 and wasMember=false. 214func (t TeamSigChainState) GetUserLastRoleChangeTime(user keybase1.UserVersion) (time keybase1.Time, wasMember bool) { 215 return t.inner.GetUserLastRoleChangeTime(user) 216} 217 218// NewStyle invites are completed in the `used_invites` field in the change 219// membership link, can optionally specify an expiration time, and a maximum 220// number of uses (potentially infinite). 221func IsNewStyleInvite(invite keybase1.TeamInvite) (bool, error) { 222 return invite.MaxUses != nil, nil 223} 224 225// assertBecameAdminAt asserts that the user (uv) became admin at the SigChainLocation given. 226// Figure out when this admin permission was revoked, if at all. If the promotion event 227// wasn't found as specified, then return an AdminPermissionError. In addition, we return 228// a bookend object, in the success case, to convey when the adminship was downgraded, if at all. 229func (t TeamSigChainState) assertBecameAdminAt(uv keybase1.UserVersion, scl keybase1.SigChainLocation) (ret proofTermBookends, err error) { 230 points := t.inner.UserLog[uv] 231 linkMap := t.inner.LinkIDs 232 for i := len(points) - 1; i >= 0; i-- { 233 point := points[i] 234 if point.SigMeta.SigChainLocation.Eq(scl) { 235 if !point.Role.IsAdminOrAbove() { 236 return ret, NewAdminPermissionError(t.GetID(), uv, "not admin permission") 237 } 238 ret.left = newProofTerm(t.GetID().AsUserOrTeam(), point.SigMeta, linkMap) 239 r := findRoleDowngrade(points[(i+1):], keybase1.TeamRole_ADMIN) 240 if r != nil { 241 tmp := newProofTerm(t.GetID().AsUserOrTeam(), *r, linkMap) 242 ret.right = &tmp 243 } 244 return ret, nil 245 } 246 } 247 return ret, NewAdminPermissionError(t.GetID(), uv, "not found") 248} 249 250// Find a point where the role is taken away. 251func findRoleDowngrade(points []keybase1.UserLogPoint, role keybase1.TeamRole) *keybase1.SignatureMetadata { 252 for _, p := range points { 253 if !p.Role.IsOrAbove(role) { 254 return &p.SigMeta 255 } 256 } 257 return nil 258} 259 260// AssertWasRoleOrAboveAt asserts that user `uv` had `role` or above on the 261// team just after the given SigChainLocation `scl`. 262// We start at the point given, go backwards until we find a promotion, 263// then go forwards to make sure there wasn't a demotion before the specified time. 264// If there was, return a PermissionError. If no adminship was found at all, return a PermissionError. 265func (t TeamSigChainState) AssertWasRoleOrAboveAt(uv keybase1.UserVersion, 266 role keybase1.TeamRole, scl keybase1.SigChainLocation) (err error) { 267 mkErr := func(format string, args ...interface{}) error { 268 msg := fmt.Sprintf(format, args...) 269 if role.IsOrAbove(keybase1.TeamRole_ADMIN) { 270 return NewAdminPermissionError(t.GetID(), uv, msg) 271 } 272 return NewPermissionError(t.GetID(), uv, msg) 273 } 274 if scl.Seqno < keybase1.Seqno(0) { 275 return mkErr("negative seqno: %v", scl.Seqno) 276 } 277 points := t.inner.UserLog[uv] 278 for i := len(points) - 1; i >= 0; i-- { 279 point := points[i] 280 if err := point.SigMeta.SigChainLocation.Comparable(scl); err != nil { 281 return mkErr(err.Error()) 282 } 283 if point.SigMeta.SigChainLocation.LessThanOrEqualTo(scl) && point.Role.IsOrAbove(role) { 284 // OK great, we found a point with the role in the log that's less than or equal to the given one. 285 // But now we reverse and go forward, and check that it wasn't revoked or downgraded. 286 // If so, that's a problem! 287 if right := findRoleDowngrade(points[(i+1):], role); right != nil && right.SigChainLocation.LessThanOrEqualTo(scl) { 288 return mkErr("%v permission was downgraded too soon!", role) 289 } 290 return nil 291 } 292 } 293 return mkErr("%v role point not found", role) 294} 295 296func (t TeamSigChainState) AssertWasWriterAt(uv keybase1.UserVersion, scl keybase1.SigChainLocation) (err error) { 297 return t.AssertWasRoleOrAboveAt(uv, keybase1.TeamRole_WRITER, scl) 298} 299 300func (t TeamSigChainState) AssertWasAdminAt(uv keybase1.UserVersion, scl keybase1.SigChainLocation) (err error) { 301 return t.AssertWasRoleOrAboveAt(uv, keybase1.TeamRole_ADMIN, scl) 302} 303 304func (t TeamSigChainState) GetUsersWithRole(role keybase1.TeamRole) (res []keybase1.UserVersion, err error) { 305 if role == keybase1.TeamRole_NONE { 306 return nil, errors.New("cannot get users with NONE role") 307 } 308 for uv := range t.inner.UserLog { 309 if t.getUserRole(uv) == role { 310 res = append(res, uv) 311 } 312 } 313 return res, nil 314} 315 316func (t TeamSigChainState) GetUsersWithRoleOrAbove(role keybase1.TeamRole) (res []keybase1.UserVersion, err error) { 317 if role == keybase1.TeamRole_NONE { 318 return nil, errors.New("cannot get users with NONE role") 319 } 320 for uv := range t.inner.UserLog { 321 if t.getUserRole(uv).IsOrAbove(role) { 322 res = append(res, uv) 323 } 324 } 325 return res, nil 326} 327 328func (t TeamSigChainState) GetLatestUVWithUID(uid keybase1.UID) (res keybase1.UserVersion, err error) { 329 found := false 330 for uv := range t.inner.UserLog { 331 if uv.Uid == uid && t.getUserRole(uv) != keybase1.TeamRole_NONE && (!found || res.EldestSeqno < uv.EldestSeqno) { 332 res = uv 333 found = true 334 } 335 } 336 337 if !found { 338 return res, errors.New("did not find user with given uid") 339 } 340 return res.DeepCopy(), nil 341} 342 343func (t TeamSigChainState) GetAllUVsWithUID(uid keybase1.UID) (res []keybase1.UserVersion) { 344 for uv := range t.inner.UserLog { 345 if uv.Uid == uid && t.getUserRole(uv) != keybase1.TeamRole_NONE { 346 res = append(res, uv) 347 } 348 } 349 return res 350} 351 352func (t TeamSigChainState) GetAllUVs() (res []keybase1.UserVersion) { 353 return t.inner.GetAllUVs() 354} 355 356func (t TeamSigChainState) GetLatestPerTeamKey(mctx libkb.MetaContext) (res keybase1.PerTeamKey, err error) { 357 res, _, err = t.getLatestPerTeamKeyWithMerkleSeqno(mctx) 358 return res, err 359} 360 361func (t TeamSigChainState) getLatestPerTeamKeyWithMerkleSeqno(mctx libkb.MetaContext) (res keybase1.PerTeamKey, mr keybase1.MerkleRootV2, err error) { 362 var hk *keybase1.PerTeamKey 363 if t.hidden != nil { 364 hk = t.hidden.MaxReaderPerTeamKey() 365 } 366 var ok bool 367 res, ok = t.inner.PerTeamKeys[t.inner.MaxPerTeamKeyGeneration] 368 369 if hk == nil && ok { 370 return res, t.inner.MerkleRoots[res.Seqno], nil 371 } 372 if !ok && hk != nil { 373 return *hk, t.hidden.MerkleRoots[hk.Seqno], nil 374 } 375 if !ok && hk == nil { 376 // if this happens it's a programming error 377 mctx.Debug("PTK not found error debug dump: inner %+v", t.inner) 378 if t.hidden != nil { 379 mctx.Debug("PTK not found error debug dump: hidden: %+v", *t.hidden) 380 } 381 return res, mr, fmt.Errorf("per-team-key not found for latest generation %d", t.inner.MaxPerTeamKeyGeneration) 382 } 383 if hk.Gen > res.Gen { 384 return *hk, t.hidden.MerkleRoots[hk.Seqno], nil 385 } 386 return res, t.inner.MerkleRoots[res.Seqno], nil 387} 388 389// checkNewPTK takes an existing state (t) and checks that a new PTK found (ptk) doesn't clash 390// against any PTKs already in the state. 391func (t *TeamSigChainState) checkNewPTK(ptk SCPerTeamKey) error { 392 // If there was no prior state, then the new PTK is OK 393 if t == nil { 394 return nil 395 } 396 gen := ptk.Generation 397 chk := func(existing keybase1.PerTeamKey, found bool) error { 398 if !found { 399 return nil 400 } 401 if !existing.SigKID.Equal(ptk.SigKID) || !existing.EncKID.Equal(ptk.EncKID) { 402 return fmt.Errorf("PTK clash at generation %d", gen) 403 } 404 return nil 405 } 406 if t.hidden != nil { 407 tmp, found := t.hidden.GetReaderPerTeamKeyAtGeneration(gen) 408 if err := chk(tmp, found); err != nil { 409 return err 410 } 411 } 412 tmp, found := t.inner.PerTeamKeys[gen] 413 if err := chk(tmp, found); err != nil { 414 return err 415 } 416 return nil 417} 418 419func (t *TeamSigChainState) GetLatestPerTeamKeyCTime() keybase1.UnixTime { 420 return t.inner.PerTeamKeyCTime 421} 422 423func (t TeamSigChainState) GetPerTeamKeyAtGeneration(gen keybase1.PerTeamKeyGeneration) (keybase1.PerTeamKey, error) { 424 res, ok := t.inner.PerTeamKeys[gen] 425 if ok { 426 return res, nil 427 } 428 res, ok = t.hidden.GetReaderPerTeamKeyAtGeneration(gen) 429 if ok { 430 return res, nil 431 } 432 return keybase1.PerTeamKey{}, libkb.NotFoundError{Msg: fmt.Sprintf("per-team-key not found for generation %d", gen)} 433} 434 435func (t TeamSigChainState) HasAnyStubbedLinks() bool { 436 for _, v := range t.inner.StubbedLinks { 437 if v { 438 return true 439 } 440 } 441 return false 442} 443 444// Whether the link has been processed and is not stubbed. 445func (t TeamSigChainState) IsLinkFilled(seqno keybase1.Seqno) bool { 446 if seqno > t.inner.LastSeqno { 447 return false 448 } 449 if seqno < 0 { 450 return false 451 } 452 return !t.inner.StubbedLinks[seqno] 453} 454 455func (t TeamSigChainState) HasStubbedSeqno(seqno keybase1.Seqno) bool { 456 return t.inner.StubbedLinks[seqno] 457} 458 459func (t TeamSigChainState) GetSubteamName(id keybase1.TeamID) (*keybase1.TeamName, error) { 460 lastPoint := t.getLastSubteamPoint(id) 461 if lastPoint != nil { 462 return &lastPoint.Name, nil 463 } 464 return nil, fmt.Errorf("subteam not found: %v", id.String()) 465} 466 467// Inform the UserLog and Bots of a user's role. 468// Mutates the UserLog and Bots. 469// Must be called with seqno's and events in correct order. 470func (t *TeamSigChainState) inform(u keybase1.UserVersion, role keybase1.TeamRole, sigMeta keybase1.SignatureMetadata) { 471 t.inner.UserLog[u] = append(t.inner.UserLog[u], keybase1.UserLogPoint{ 472 Role: role, 473 SigMeta: sigMeta, 474 }) 475 // Clear an entry in Bots if any 476 if !role.IsRestrictedBot() { 477 delete(t.inner.Bots, u) 478 } 479} 480 481func (t *TeamSigChainState) informNewInvite(i keybase1.TeamInvite, teamSigMeta keybase1.TeamSignatureMetadata) { 482 t.inner.InviteMetadatas[i.Id] = keybase1.NewTeamInviteMetadata(i, teamSigMeta) 483} 484 485func (t *TeamSigChainState) informCanceledInvite(i keybase1.TeamInviteID, 486 cancelTeamSigMeta keybase1.TeamSignatureMetadata) { 487 inviteMD, ok := t.inner.InviteMetadatas[i] 488 if !ok { 489 return 490 } 491 inviteMD.Status = keybase1.NewTeamInviteMetadataStatusWithCancelled(keybase1.TeamInviteMetadataCancel{ 492 TeamSigMeta: cancelTeamSigMeta, 493 }) 494 t.inner.InviteMetadatas[i] = inviteMD 495} 496 497func (t *TeamSigChainState) informCompletedInvite(i keybase1.TeamInviteID, 498 completeTeamSigMeta keybase1.TeamSignatureMetadata) { 499 inviteMD, ok := t.inner.InviteMetadatas[i] 500 if !ok { 501 return 502 } 503 inviteMD.Status = keybase1.NewTeamInviteMetadataStatusWithCompleted(keybase1.TeamInviteMetadataCompleted{ 504 TeamSigMeta: completeTeamSigMeta, 505 }) 506 t.inner.InviteMetadatas[i] = inviteMD 507} 508 509func (t *TeamSigChainState) getLastSubteamPoint(id keybase1.TeamID) *keybase1.SubteamLogPoint { 510 if len(t.inner.SubteamLog[id]) > 0 { 511 return &t.inner.SubteamLog[id][len(t.inner.SubteamLog[id])-1] 512 } 513 return nil 514} 515 516// Inform the SubteamLog of a subteam name change. 517// Links must be added in order by seqno for each subteam (asserted here). 518// Links for different subteams can interleave. 519// Mutates the SubteamLog. 520// Name collisions are allowed here. They are allowed because you might 521// be removed a subteam and then its future name changes and deletion would be stubbed. 522// See ListSubteams for details. 523func (t *TeamSigChainState) informSubteam(id keybase1.TeamID, name keybase1.TeamName, seqno keybase1.Seqno) error { 524 lastPoint := t.getLastSubteamPoint(id) 525 if lastPoint != nil && lastPoint.Seqno.Eq(seqno) { 526 return fmt.Errorf("re-entry into subteam log for seqno: %v", seqno) 527 } 528 if lastPoint != nil && seqno < lastPoint.Seqno { 529 return fmt.Errorf("cannot add to subteam log out of order: %v < %v", seqno, lastPoint.Seqno) 530 } 531 if lastPoint != nil && lastPoint.Name.IsNil() { 532 return fmt.Errorf("cannot process subteam rename because %v was deleted at seqno %v", id, lastPoint.Seqno) 533 } 534 t.inner.SubteamLog[id] = append(t.inner.SubteamLog[id], keybase1.SubteamLogPoint{ 535 Name: name, 536 Seqno: seqno, 537 }) 538 return nil 539} 540 541// Inform the SubteamLog of a subteam deletion. 542// Links must be added in order by seqno for each subteam (asserted here). 543// Links for different subteams can interleave. 544// Mutates the SubteamLog. 545func (t *TeamSigChainState) informSubteamDelete(id keybase1.TeamID, seqno keybase1.Seqno) error { 546 lastPoint := t.getLastSubteamPoint(id) 547 if lastPoint != nil && lastPoint.Seqno.Eq(seqno) { 548 return fmt.Errorf("re-entry into subteam log for seqno: %v", seqno) 549 } 550 if lastPoint != nil && seqno < lastPoint.Seqno { 551 return fmt.Errorf("cannot add to subteam log out of order: %v < %v", seqno, lastPoint.Seqno) 552 } 553 // Don't check for deleting the same team twice. Just allow it. 554 t.inner.SubteamLog[id] = append(t.inner.SubteamLog[id], keybase1.SubteamLogPoint{ 555 Seqno: seqno, 556 }) 557 return nil 558} 559 560// Only call this on a Team that has been loaded with NeedAdmin. 561// Otherwise, you might get incoherent answers due to links that 562// were stubbed over the life of the cached object. 563// 564// For subteams that you were removed from, this list may 565// still include them because your removal was stubbed. 566// The list will not contain duplicate names. 567// Since this should only be called when you are an admin, 568// none of that should really come up, but it's here just to be less fragile. 569func (t *TeamSigChainState) ListSubteams() (res []keybase1.TeamIDAndName) { 570 return t.inner.ListSubteams() 571} 572 573// Check that a subteam rename occurred just so. 574// That the subteam `subteamID` got a new name `newName` at exactly `seqno` in this, 575// the parent, chain. 576// Note this only checks against the last part of `newName` because mid-team renames are such a pain. 577// This is currently linear in the number of times that subteam has been renamed. 578// It should be easy to add an index if need be. 579func (t *TeamSigChainState) SubteamRenameOccurred( 580 subteamID keybase1.TeamID, newName keybase1.TeamName, seqno keybase1.Seqno) error { 581 582 points := t.inner.SubteamLog[subteamID] 583 if len(points) == 0 { 584 return fmt.Errorf("subteam %v has no name log", subteamID) 585 } 586 for _, point := range points { 587 if point.Seqno == seqno { 588 if point.Name.LastPart().Eq(newName.LastPart()) { 589 // found it! 590 return nil 591 } 592 } 593 if point.Seqno > seqno { 594 break 595 } 596 } 597 return fmt.Errorf("subteam %v did not have rename entry in log: %v %v", 598 subteamID, newName, seqno) 599} 600 601func (t *TeamSigChainState) ActiveInvites() (ret []keybase1.TeamInviteMetadata) { 602 for _, md := range t.inner.InviteMetadatas { 603 if code, err := md.Status.Code(); err == nil && 604 code == keybase1.TeamInviteMetadataStatusCode_ACTIVE { 605 ret = append(ret, md) 606 } 607 } 608 return ret 609} 610 611func (t *TeamSigChainState) NumActiveInvites() int { 612 return len(t.ActiveInvites()) 613} 614 615func (t *TeamSigChainState) HasActiveInvite(name keybase1.TeamInviteName, typ keybase1.TeamInviteType) (bool, error) { 616 i, err := t.FindActiveInvite(name, typ) 617 if err != nil { 618 if _, ok := err.(libkb.NotFoundError); ok { 619 return false, nil 620 } 621 return false, err 622 } 623 if i != nil { 624 return true, nil 625 } 626 return false, nil 627} 628 629func (t *TeamSigChainState) FindActiveInvite(name keybase1.TeamInviteName, typ keybase1.TeamInviteType) (*keybase1.TeamInvite, error) { 630 for _, inviteMD := range t.ActiveInvites() { 631 if inviteMD.Invite.Name == name && inviteMD.Invite.Type.Eq(typ) { 632 return &inviteMD.Invite, nil 633 } 634 } 635 return nil, libkb.NotFoundError{} 636} 637 638func (t *TeamSigChainState) FindActiveInviteString(mctx libkb.MetaContext, name string, typ string) (ret keybase1.TeamInvite, err error) { 639 itype, err := TeamInviteTypeFromString(mctx, typ) 640 if err != nil { 641 return ret, err 642 } 643 invPtr, err := t.FindActiveInvite(keybase1.TeamInviteName(name), itype) 644 switch { 645 case err != nil: 646 return ret, err 647 case invPtr == nil: 648 return ret, libkb.NotFoundError{} 649 } 650 return *invPtr, nil 651} 652 653// FindActiveInviteMDByID returns potentially expired invites that have not been 654// explicitly cancelled, since the sigchain player is agnostic to the concept 655// of time. We treat invite expiration times as advisory for admin clients 656// completing invites, but do not check them in the sigchain player. 657func (t *TeamSigChainState) FindActiveInviteMDByID( 658 id keybase1.TeamInviteID) (inviteMD keybase1.TeamInviteMetadata, found bool) { 659 res, found := t.inner.InviteMetadatas[id] 660 if !found { 661 return inviteMD, false 662 } 663 code, err := res.Status.Code() 664 if err != nil { 665 return inviteMD, false 666 } 667 if code != keybase1.TeamInviteMetadataStatusCode_ACTIVE { 668 return inviteMD, false 669 } 670 return res, true 671} 672 673func (t *TeamSigChainState) IsInviteObsolete(id keybase1.TeamInviteID) bool { 674 inviteMD, found := t.inner.InviteMetadatas[id] 675 if !found { 676 return false 677 } 678 code, err := inviteMD.Status.Code() 679 if err != nil { 680 return false 681 } 682 return code == keybase1.TeamInviteMetadataStatusCode_OBSOLETE 683} 684 685// FindActiveKeybaseInvite finds and returns a Keybase-type 686// invite for given UID. Ordering here is not guaranteed, caller 687// shouldn't assume that returned invite will be the oldest/newest one 688// for the UID. 689func (t *TeamSigChainState) FindActiveKeybaseInvite(uid keybase1.UID) (keybase1.TeamInvite, keybase1.UserVersion, bool) { 690 for _, inviteMD := range t.ActiveInvites() { 691 if inviteUv, err := inviteMD.Invite.KeybaseUserVersion(); err == nil { 692 if inviteUv.Uid.Equal(uid) { 693 return inviteMD.Invite, inviteUv, true 694 } 695 } 696 } 697 return keybase1.TeamInvite{}, keybase1.UserVersion{}, false 698} 699 700func (t *TeamSigChainState) GetMerkleRoots() map[keybase1.Seqno]keybase1.MerkleRootV2 { 701 return t.inner.MerkleRoots 702} 703 704func (t TeamSigChainState) TeamBotSettings() map[keybase1.UserVersion]keybase1.TeamBotSettings { 705 return t.inner.Bots 706} 707 708// -------------------------------------------------- 709 710// AppendChainLink process a chain link. 711// It must have already been partially verified by TeamLoader. 712// `reader` is the user who is processing the chain. 713// `state` is moved into this function. There must exist no live references into it from now on. 714// If `state` is nil this is the first link of the chain. 715// `signer` may be nil iff link is stubbed. 716func AppendChainLink(ctx context.Context, g *libkb.GlobalContext, reader keybase1.UserVersion, state *TeamSigChainState, 717 link *ChainLinkUnpacked, signer *SignerX) (res TeamSigChainState, err error) { 718 t := &teamSigchainPlayer{ 719 Contextified: libkb.NewContextified(g), 720 reader: reader, 721 } 722 var latestSeqno keybase1.Seqno 723 if state != nil { 724 latestSeqno = state.GetLatestSeqno() 725 } 726 res, err = t.appendChainLinkHelper(libkb.NewMetaContext(ctx, g), state, link, signer) 727 if err != nil { 728 return TeamSigChainState{}, NewAppendLinkError(link, latestSeqno, err) 729 } 730 return res, err 731} 732 733// InflateLink adds the full inner link for a link that has already been added in stubbed form. 734// `state` is moved into this function. There must exist no live references into it from now on. 735func InflateLink(ctx context.Context, g *libkb.GlobalContext, reader keybase1.UserVersion, state TeamSigChainState, 736 link *ChainLinkUnpacked, signer SignerX) (res TeamSigChainState, err error) { 737 if link.isStubbed() { 738 return TeamSigChainState{}, NewStubbedError(link) 739 } 740 if link.Seqno() > state.GetLatestSeqno() { 741 return TeamSigChainState{}, NewInflateErrorWithNote(link, 742 fmt.Sprintf("seqno off the chain %v > %v", link.Seqno(), state.GetLatestSeqno())) 743 } 744 745 // Check the that the link id matches our stubbed record. 746 seenLinkID, err := state.GetLibkbLinkIDBySeqno(link.Seqno()) 747 if err != nil { 748 return TeamSigChainState{}, err 749 } 750 if !seenLinkID.Eq(link.LinkID()) { 751 return TeamSigChainState{}, NewInflateErrorWithNote(link, 752 fmt.Sprintf("link id mismatch: %v != %v", link.LinkID().String(), seenLinkID.String())) 753 } 754 755 // Check that the link has not already been inflated. 756 if _, ok := state.inner.StubbedLinks[link.Seqno()]; !ok { 757 return TeamSigChainState{}, NewInflateErrorWithNote(link, "already inflated") 758 } 759 760 t := &teamSigchainPlayer{ 761 Contextified: libkb.NewContextified(g), 762 reader: reader, 763 } 764 iRes, err := t.addInnerLink(libkb.NewMetaContext(ctx, g), &state, link, signer, true) 765 if err != nil { 766 return TeamSigChainState{}, err 767 } 768 769 delete(iRes.newState.inner.StubbedLinks, link.Seqno()) 770 771 return iRes.newState, nil 772 773} 774 775// Helper struct for playing sigchains. 776type teamSigchainPlayer struct { 777 libkb.Contextified 778 reader keybase1.UserVersion // the user processing the chain 779} 780 781// Add a chain link to the end. 782// `prevState` is moved into this function. There must exist no live references into it from now on. 783// `signer` may be nil iff link is stubbed. 784// If `prevState` is nil this is the first chain link. 785func (t *teamSigchainPlayer) appendChainLinkHelper( 786 mctx libkb.MetaContext, prevState *TeamSigChainState, link *ChainLinkUnpacked, signer *SignerX) ( 787 res TeamSigChainState, err error) { 788 789 err = t.checkOuterLink(mctx.Ctx(), prevState, link) 790 if err != nil { 791 return res, fmt.Errorf("team sigchain outer link: %s", err) 792 } 793 794 var newState *TeamSigChainState 795 if link.isStubbed() { 796 if prevState == nil { 797 return res, NewStubbedErrorWithNote(link, "first link cannot be stubbed") 798 } 799 newState2 := prevState.DeepCopy() 800 newState = &newState2 801 } else { 802 if signer == nil || !signer.signer.Uid.Exists() { 803 return res, NewInvalidLink(link, "signing user not provided for team link") 804 } 805 iRes, err := t.addInnerLink(mctx, prevState, link, *signer, false) 806 if err != nil { 807 return res, err 808 } 809 newState = &iRes.newState 810 } 811 812 newState.inner.LastSeqno = link.Seqno() 813 newState.inner.LastLinkID = link.LinkID().Export() 814 newState.inner.LinkIDs[link.Seqno()] = link.LinkID().Export() 815 816 if link.isStubbed() { 817 newState.inner.StubbedLinks[link.Seqno()] = true 818 } 819 820 // Store the head merkle sequence to the DB, for use in the audit mechanism. 821 // For the purposes of testing, we disable this feature, so we can check 822 // the lazy-repopulation scheme for old stored teams (without a full cache bust). 823 if link.Seqno() == keybase1.Seqno(1) && !link.isStubbed() && !t.G().Env.Test.TeamNoHeadMerkleStore { 824 tmp := link.inner.Body.MerkleRoot.ToMerkleRootV2() 825 newState.inner.HeadMerkle = &tmp 826 } 827 828 if !link.isStubbed() && newState.inner.MerkleRoots != nil { 829 newState.inner.MerkleRoots[link.Seqno()] = link.inner.Body.MerkleRoot.ToMerkleRootV2() 830 } 831 832 return *newState, nil 833} 834 835func (t *teamSigchainPlayer) checkOuterLink(ctx context.Context, prevState *TeamSigChainState, link *ChainLinkUnpacked) (err error) { 836 if prevState == nil { 837 if link.Seqno() != 1 { 838 return NewUnexpectedSeqnoError(keybase1.Seqno(1), link.Seqno()) 839 } 840 } else { 841 if link.Seqno() != prevState.inner.LastSeqno+1 { 842 return NewUnexpectedSeqnoError(prevState.inner.LastSeqno+1, link.Seqno()) 843 } 844 } 845 846 if prevState == nil { 847 if len(link.Prev()) != 0 { 848 return fmt.Errorf("expected outer nil prev but got:%s", link.Prev()) 849 } 850 } else { 851 prevStateLastLinkID, err := libkb.ImportLinkID(prevState.inner.LastLinkID) 852 if err != nil { 853 return fmt.Errorf("invalid prev last link id: %v", err) 854 } 855 if !link.Prev().Eq(prevStateLastLinkID) { 856 return fmt.Errorf("wrong outer prev: %s != %s", link.Prev(), prevState.inner.LastLinkID) 857 } 858 } 859 860 return nil 861} 862 863type checkInnerLinkResult struct { 864 newState TeamSigChainState 865} 866 867// Check and add the inner link. 868// `isInflate` is false if this is a new link and true if it is a link which has already been added as stubbed. 869// Does not modify `prevState` but returns a new state. 870func (t *teamSigchainPlayer) addInnerLink(mctx libkb.MetaContext, 871 prevState *TeamSigChainState, link *ChainLinkUnpacked, signer SignerX, 872 isInflate bool) (res checkInnerLinkResult, err error) { 873 874 if link.inner == nil { 875 return res, NewStubbedError(link) 876 } 877 payload := *link.inner 878 879 if !signer.signer.Uid.Exists() { 880 return res, NewInvalidLink(link, "empty link signer") 881 } 882 883 // This may be superfluous. 884 err = link.AssertInnerOuterMatch() 885 if err != nil { 886 return res, err 887 } 888 889 // completely ignore these fields 890 _ = payload.ExpireIn 891 _ = payload.SeqType 892 893 if payload.Tag != "signature" { 894 return res, NewInvalidLink(link, "unrecognized tag: '%s'", payload.Tag) 895 } 896 897 if payload.Body.Team == nil { 898 return res, NewInvalidLink(link, "missing team section") 899 } 900 team := payload.Body.Team 901 902 if len(team.ID) == 0 { 903 return res, NewInvalidLink(link, "missing team id") 904 } 905 teamID, err := keybase1.TeamIDFromString(string(team.ID)) 906 if err != nil { 907 return res, err 908 } 909 910 if teamID.IsPublic() != team.Public { 911 return res, fmt.Errorf("link specified public:%v but ID is public:%v", 912 team.Public, teamID.IsPublic()) 913 } 914 915 if prevState != nil && !prevState.inner.Id.Equal(teamID) { 916 return res, fmt.Errorf("wrong team id: %s != %s", teamID.String(), prevState.inner.Id.String()) 917 } 918 919 if prevState != nil && prevState.IsImplicit() != team.Implicit { 920 return res, fmt.Errorf("link specified implicit:%v but team was already implicit:%v", 921 team.Implicit, prevState.IsImplicit()) 922 } 923 924 if prevState != nil && prevState.IsPublic() != team.Public { 925 return res, fmt.Errorf("link specified public:%v but team was already public:%v", 926 team.Implicit, prevState.IsImplicit()) 927 } 928 929 if team.Public && (!team.Implicit || (prevState != nil && !prevState.IsImplicit())) { 930 return res, fmt.Errorf("public non-implicit teams are not supported") 931 } 932 933 err = t.checkSeqnoToAdd(prevState, link.Seqno(), isInflate) 934 if err != nil { 935 return res, err 936 } 937 // When isInflate then it is likely prevSeqno != prevState.GetLatestSeqno() 938 prevSeqno := link.Seqno() - 1 939 940 allowInflate := func(allow bool) error { 941 if isInflate && !allow { 942 return fmt.Errorf("inflating link type not supported: %v", payload.Body.Type) 943 } 944 return nil 945 } 946 allowInImplicitTeam := func(allow bool) error { 947 if team.Implicit && !allow { 948 return NewImplicitTeamOperationError(payload.Body.Type) 949 } 950 return nil 951 } 952 enforceFirstInChain := func(firstInChain bool) error { 953 if firstInChain { 954 if prevState != nil { 955 return fmt.Errorf("link type '%s' unexpected at seqno:%v", payload.Body.Type, prevState.inner.LastSeqno+1) 956 } 957 } else { 958 if prevState == nil { 959 return fmt.Errorf("link type '%s' unexpected at beginning", payload.Body.Type) 960 } 961 } 962 return nil 963 } 964 enforceGeneric := func(name string, rule Tristate, hasReal bool) error { 965 switch rule { 966 case TristateDisallow: 967 if hasReal { 968 return fmt.Errorf("sigchain link contains unexpected '%s'", name) 969 } 970 case TristateRequire: 971 if !hasReal { 972 return fmt.Errorf("sigchain link missing %s", name) 973 } 974 case TristateOptional: 975 default: 976 return fmt.Errorf("unsupported tristate (fault): %v", rule) 977 } 978 return nil 979 } 980 enforce := func(rules LinkRules) error { 981 return libkb.PickFirstError( 982 enforceGeneric("name", rules.Name, team.Name != nil), 983 enforceGeneric("members", rules.Members, team.Members != nil), 984 enforceGeneric("parent", rules.Parent, team.Parent != nil), 985 enforceGeneric("subteam", rules.Subteam, team.Subteam != nil), 986 enforceGeneric("per-team-key", rules.PerTeamKey, team.PerTeamKey != nil), 987 enforceGeneric("admin", rules.Admin, team.Admin != nil), 988 enforceGeneric("invites", rules.Invites, team.Invites != nil), 989 enforceGeneric("completed-invites", rules.CompletedInvites, team.CompletedInvites != nil), 990 enforceGeneric("settings", rules.Settings, team.Settings != nil), 991 enforceGeneric("kbfs", rules.KBFS, team.KBFS != nil), 992 enforceGeneric("box-summary-hash", rules.BoxSummaryHash, team.BoxSummaryHash != nil), 993 enforceGeneric("bot_settings", rules.BotSettings, team.BotSettings != nil), 994 allowInImplicitTeam(rules.AllowInImplicitTeam), 995 allowInflate(rules.AllowInflate), 996 enforceFirstInChain(rules.FirstInChain), 997 ) 998 } 999 1000 checkAdmin := func(op string) (signerIsExplicitOwner bool, err error) { 1001 signerRole, err := prevState.GetUserRoleAtSeqno(signer.signer, prevSeqno) 1002 if err != nil { 1003 signerRole = keybase1.TeamRole_NONE 1004 } 1005 signerIsExplicitOwner = signerRole == keybase1.TeamRole_OWNER 1006 if signerRole.IsAdminOrAbove() || signer.implicitAdmin { 1007 return signerIsExplicitOwner, nil 1008 } 1009 return signerIsExplicitOwner, fmt.Errorf("link signer does not have permission to %s: %v is a %v", op, signer, signerRole) 1010 } 1011 1012 checkExplicitWriter := func(op string) (err error) { 1013 signerRole, err := prevState.GetUserRoleAtSeqno(signer.signer, prevSeqno) 1014 if err != nil { 1015 signerRole = keybase1.TeamRole_NONE 1016 } 1017 if !signerRole.IsWriterOrAbove() { 1018 return fmt.Errorf("link signer does not have writer permission to %s: %v is a %v", op, signer, signerRole) 1019 } 1020 return nil 1021 } 1022 1023 moveState := func() { 1024 // Move prevState to res.newState. 1025 // Re-use the object without deep copying. There must be no other live references into prevState. 1026 res.newState = *prevState 1027 prevState = nil 1028 } 1029 isHighLink := false 1030 1031 teamSigMeta := keybase1.NewTeamSigMeta(payload.SignatureMetadata(), signer.signer) 1032 1033 switch libkb.LinkType(payload.Body.Type) { 1034 case libkb.LinkTypeTeamRoot: 1035 isHighLink = true 1036 err = enforce(LinkRules{ 1037 Name: TristateRequire, 1038 Members: TristateRequire, 1039 PerTeamKey: TristateRequire, 1040 BoxSummaryHash: TristateOptional, 1041 Invites: TristateOptional, 1042 Settings: TristateOptional, 1043 AllowInImplicitTeam: true, 1044 FirstInChain: true, 1045 }) 1046 if err != nil { 1047 return res, err 1048 } 1049 1050 // Check the team name 1051 teamName, err := keybase1.TeamNameFromString(string(*team.Name)) 1052 if err != nil { 1053 return res, err 1054 } 1055 if !teamName.IsRootTeam() { 1056 return res, fmt.Errorf("root team has subteam name: %s", teamName) 1057 } 1058 1059 // Whether this is an implicit team 1060 isImplicit := teamName.IsImplicit() 1061 if isImplicit != team.Implicit { 1062 return res, fmt.Errorf("link specified implicit:%v but name specified implicit:%v", 1063 team.Implicit, isImplicit) 1064 } 1065 1066 // Check the team ID 1067 // assert that team_name = hash(team_id) 1068 // this is only true for root teams 1069 if !teamID.Equal(teamName.ToTeamID(team.Public)) { 1070 return res, fmt.Errorf("team id:%s does not match team name:%s", teamID, teamName) 1071 } 1072 if teamID.IsSubTeam() { 1073 return res, fmt.Errorf("malformed root team id") 1074 } 1075 1076 roleUpdates, err := t.sanityCheckMembers(*team.Members, sanityCheckMembersOptions{ 1077 requireOwners: true, 1078 allowRemovals: false, 1079 onlyOwnersOrReaders: isImplicit, 1080 }) 1081 if err != nil { 1082 return res, err 1083 } 1084 1085 perTeamKey, err := t.checkPerTeamKey(*link.source, *team.PerTeamKey, nil) 1086 if err != nil { 1087 return res, err 1088 } 1089 1090 perTeamKeys := make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKey) 1091 perTeamKeys[keybase1.PerTeamKeyGeneration(1)] = perTeamKey 1092 1093 res.newState = TeamSigChainState{ 1094 inner: keybase1.TeamSigChainState{ 1095 Reader: t.reader, 1096 Id: teamID, 1097 Implicit: isImplicit, 1098 Public: team.Public, 1099 RootAncestor: teamName.RootAncestorName(), 1100 NameDepth: teamName.Depth(), 1101 NameLog: []keybase1.TeamNameLogPoint{{ 1102 LastPart: teamName.LastPart(), 1103 Seqno: 1, 1104 }}, 1105 LastSeqno: 1, 1106 LastLinkID: link.LinkID().Export(), 1107 ParentID: nil, 1108 UserLog: make(map[keybase1.UserVersion][]keybase1.UserLogPoint), 1109 SubteamLog: make(map[keybase1.TeamID][]keybase1.SubteamLogPoint), 1110 PerTeamKeys: perTeamKeys, 1111 MaxPerTeamKeyGeneration: keybase1.PerTeamKeyGeneration(1), 1112 PerTeamKeyCTime: keybase1.UnixTime(payload.Ctime), 1113 LinkIDs: make(map[keybase1.Seqno]keybase1.LinkID), 1114 StubbedLinks: make(map[keybase1.Seqno]bool), 1115 InviteMetadatas: make(map[keybase1.TeamInviteID]keybase1.TeamInviteMetadata), 1116 TlfLegacyUpgrade: make(map[keybase1.TeamApplication]keybase1.TeamLegacyTLFUpgradeChainInfo), 1117 MerkleRoots: make(map[keybase1.Seqno]keybase1.MerkleRootV2), 1118 Bots: make(map[keybase1.UserVersion]keybase1.TeamBotSettings), 1119 }} 1120 1121 t.updateMembership(&res.newState, roleUpdates, payload.SignatureMetadata()) 1122 if team.Invites != nil { 1123 if isImplicit { 1124 signerIsExplicitOwner := true 1125 additions, cancelations, err := t.sanityCheckInvites(mctx, signer.signer, signerIsExplicitOwner, 1126 *team.Invites, link.SigID(), sanityCheckInvitesOptions{ 1127 isRootTeam: true, 1128 implicitTeam: isImplicit, 1129 }) 1130 if err != nil { 1131 return res, err 1132 } 1133 t.updateInvites(&res.newState, additions, cancelations, teamSigMeta) 1134 } else { 1135 return res, fmt.Errorf("invites not allowed in root link") 1136 } 1137 } 1138 1139 // check that the signer is an owner 1140 if res.newState.getUserRole(signer.signer) != keybase1.TeamRole_OWNER { 1141 return res, fmt.Errorf("signer is not an owner: %v (%v)", signer, team.Members.Owners) 1142 } 1143 1144 if settings := team.Settings; settings != nil { 1145 err = t.parseTeamSettings(settings, &res.newState) 1146 if err != nil { 1147 return res, err 1148 } 1149 } 1150 case libkb.LinkTypeChangeMembership: 1151 err = enforce(LinkRules{ 1152 Members: TristateRequire, 1153 PerTeamKey: TristateOptional, 1154 Admin: TristateOptional, 1155 CompletedInvites: TristateOptional, 1156 BoxSummaryHash: TristateOptional, 1157 AllowInImplicitTeam: true, 1158 }) 1159 if err != nil { 1160 return res, err 1161 } 1162 1163 // Check that the signer is at least an ADMIN or is an IMPLICIT ADMIN to have permission to make this link. 1164 var signerIsExplicitOwner bool 1165 signerIsExplicitOwner, err = checkAdmin("change membership") 1166 if err != nil { 1167 return res, err 1168 } 1169 1170 roleUpdates, err := t.sanityCheckMembers(*team.Members, sanityCheckMembersOptions{ 1171 disallowOwners: prevState.IsSubteam(), 1172 allowRemovals: true, 1173 onlyOwnersOrReaders: prevState.IsImplicit(), 1174 }) 1175 if err != nil { 1176 return res, err 1177 } 1178 1179 // Only owners can add more owners. 1180 if (len(roleUpdates[keybase1.TeamRole_OWNER]) > 0) && !signerIsExplicitOwner { 1181 return res, fmt.Errorf("non-owner cannot add owners") 1182 } 1183 1184 // Only owners can remove owners. 1185 if t.roleUpdatesDemoteOwners(prevState, roleUpdates) && !signerIsExplicitOwner { 1186 return res, fmt.Errorf("non-owner cannot demote owners") 1187 } 1188 1189 if prevState.IsImplicit() { 1190 // In implicit teams there are only 3 kinds of membership changes allowed: 1191 // 1. Resolve an invite. Adds 1 user and completes 1 invite. 1192 // Though sometimes a new user is not added, due to a conflict. 1193 // 2. Accept a reset user. Adds 1 user and removes 1 user. 1194 // Where the new one has the same UID and role as the old and a greater EldestSeqno. 1195 // 3. Add/remove a bot user. The bot can be a RESTRICTEDBOT or regular BOT member. 1196 1197 // Here's a case that is not straightforward: 1198 // There is an impteam alice,leland%2,bob@twitter. 1199 // Leland resets and then proves bob@twitter. On the team chain alice accepts Leland's reset. 1200 // So she removes leland%2, adds leland%3, and completes the bob@twitter invite. 1201 1202 // Here's another: 1203 // There is an impteam leland#bob@twitter. 1204 // Leland proves bob@twitter. On the team chain leland completes the invite. 1205 // Now it's just leland. 1206 1207 // Check that the invites being completed are all active. 1208 // For non-implicit teams we are more lenient, but here we need the counts to match up. 1209 invitees := make(map[keybase1.UID]bool) 1210 parsedCompletedInvites := make(map[keybase1.TeamInviteID]keybase1.UserVersion) 1211 for inviteID, invitee := range team.CompletedInvites { 1212 _, found := prevState.FindActiveInviteMDByID(inviteID) 1213 if !found { 1214 return res, NewImplicitTeamOperationError("completed invite %v but was not active", 1215 inviteID) 1216 } 1217 uv, err := keybase1.ParseUserVersion(invitee) 1218 if err != nil { 1219 return res, err 1220 } 1221 invitees[uv.Uid] = true 1222 parsedCompletedInvites[inviteID] = uv 1223 } 1224 nCompleted := len(team.CompletedInvites) 1225 1226 // Check these properties: 1227 // - Every removal must come with an addition of a successor. Ignore role. 1228 // - Every addition must either be paired with a removal, or 1229 // resolve an invite. Ignore role when not dealing with bots. 1230 // This is a coarse check that ignores role changes. 1231 1232 type removal struct { 1233 uv keybase1.UserVersion 1234 satisfied bool 1235 } 1236 removals := make(map[keybase1.UID]removal) 1237 for _, uv := range roleUpdates[keybase1.TeamRole_NONE] { 1238 removals[uv.Uid] = removal{uv: uv} 1239 } 1240 var nCompletedExpected int 1241 additions := make(map[keybase1.UID]bool) 1242 // Every addition must either be paired with a removal or resolve an invite. 1243 for _, uv := range append(roleUpdates[keybase1.TeamRole_OWNER], roleUpdates[keybase1.TeamRole_READER]...) { 1244 removal, ok := removals[uv.Uid] 1245 if ok { 1246 if removal.uv.EldestSeqno >= uv.EldestSeqno { 1247 return res, NewImplicitTeamOperationError("replaced with older eldest seqno: %v -> %v", 1248 removal.uv.EldestSeqno, uv.EldestSeqno) 1249 } 1250 removal.satisfied = true 1251 removals[uv.Uid] = removal 1252 if invitees[uv.Uid] && uv.EldestSeqno > removal.uv.EldestSeqno { 1253 // If we are removing someone that is also a completed invite, then it must 1254 // be replacing a reset user with a new version. Expect an invite in this case. 1255 nCompletedExpected++ 1256 additions[uv.Uid] = true 1257 } 1258 } else { 1259 // This is a new user, so must be a completed invite. 1260 nCompletedExpected++ 1261 additions[uv.Uid] = true 1262 } 1263 } 1264 // All removals must have come with successor. 1265 for _, r := range removals { 1266 role := prevState.getUserRole(r.uv) 1267 if !(r.satisfied || role.IsBotLike()) { 1268 return res, NewImplicitTeamOperationError("removal without addition for %v", r.uv) 1269 } 1270 } 1271 // Completed invites that do not bring in new members mean 1272 // SBS consolidations. 1273 for _, uv := range parsedCompletedInvites { 1274 _, ok := additions[uv.Uid] 1275 if !ok { 1276 if prevState.getUserRole(uv) == keybase1.TeamRole_NONE { 1277 return res, NewImplicitTeamOperationError("trying to moot invite but there is no member for %v", uv) 1278 } 1279 nCompleted-- 1280 } 1281 } 1282 // The number of completed invites must match. 1283 if nCompletedExpected != nCompleted { 1284 return res, NewImplicitTeamOperationError("illegal membership change: %d != %d", 1285 nCompletedExpected, nCompleted) 1286 } 1287 } 1288 1289 isHighLink, err = t.roleUpdateChangedHighSet(prevState, roleUpdates) 1290 if err != nil { 1291 return res, fmt.Errorf("could not determine if high user set changed") 1292 } 1293 1294 moveState() 1295 t.updateMembership(&res.newState, roleUpdates, payload.SignatureMetadata()) 1296 1297 if err := t.completeInvites(&res.newState, team.CompletedInvites, teamSigMeta); err != nil { 1298 return res, fmt.Errorf("illegal completed_invites: %s", err) 1299 } 1300 t.obsoleteActiveInvites(&res.newState, roleUpdates, payload.SignatureMetadata()) 1301 1302 if err := t.useInvites(&res.newState, roleUpdates, team.UsedInvites); err != nil { 1303 return res, fmt.Errorf("illegal used_invites: %s", err) 1304 } 1305 1306 // Note: If someone was removed, the per-team-key should be rotated. This is not checked though. 1307 1308 if team.PerTeamKey != nil { 1309 newKey, err := t.checkPerTeamKey(*link.source, *team.PerTeamKey, &res.newState) 1310 if err != nil { 1311 return res, err 1312 } 1313 res.newState.inner.PerTeamKeys[newKey.Gen] = newKey 1314 if newKey.Gen > res.newState.inner.MaxPerTeamKeyGeneration { 1315 res.newState.inner.MaxPerTeamKeyGeneration = newKey.Gen 1316 } 1317 res.newState.inner.PerTeamKeyCTime = keybase1.UnixTime(payload.Ctime) 1318 } 1319 case libkb.LinkTypeRotateKey: 1320 err = enforce(LinkRules{ 1321 PerTeamKey: TristateRequire, 1322 Admin: TristateOptional, 1323 BoxSummaryHash: TristateOptional, 1324 AllowInImplicitTeam: true, 1325 }) 1326 if err != nil { 1327 return res, err 1328 } 1329 1330 // Check that the signer is at least a writer to have permission to make this link. 1331 if !signer.implicitAdmin { 1332 signerRole, err := prevState.GetUserRoleAtSeqno(signer.signer, prevSeqno) 1333 if err != nil { 1334 return res, err 1335 } 1336 switch signerRole { 1337 case keybase1.TeamRole_WRITER, keybase1.TeamRole_ADMIN, keybase1.TeamRole_OWNER: 1338 // ok 1339 default: 1340 return res, fmt.Errorf("link signer does not have permission to rotate key: %v is a %v", signer, signerRole) 1341 } 1342 } 1343 1344 newKey, err := t.checkPerTeamKey(*link.source, *team.PerTeamKey, prevState) 1345 if err != nil { 1346 return res, err 1347 } 1348 1349 moveState() 1350 res.newState.inner.PerTeamKeys[newKey.Gen] = newKey 1351 res.newState.inner.PerTeamKeyCTime = keybase1.UnixTime(payload.Ctime) 1352 if newKey.Gen > res.newState.inner.MaxPerTeamKeyGeneration { 1353 res.newState.inner.MaxPerTeamKeyGeneration = newKey.Gen 1354 } 1355 case libkb.LinkTypeLeave: 1356 err = enforce(LinkRules{ /* Just about everything is restricted. */ }) 1357 if err != nil { 1358 return res, err 1359 } 1360 // Key rotation should never be allowed since FullVerify sometimes does not run on leave links. 1361 1362 // Check that the signer is at least a bot. 1363 // Implicit admins cannot leave a subteam. 1364 signerRole, err := prevState.GetUserRoleAtSeqno(signer.signer, prevSeqno) 1365 if err != nil { 1366 return res, err 1367 } 1368 switch signerRole { 1369 case keybase1.TeamRole_RESTRICTEDBOT, 1370 keybase1.TeamRole_BOT, 1371 keybase1.TeamRole_READER, 1372 keybase1.TeamRole_WRITER, 1373 keybase1.TeamRole_ADMIN, 1374 keybase1.TeamRole_OWNER: 1375 // ok 1376 default: 1377 return res, fmt.Errorf("link signer does not have permission to leave: %v is a %v", signer, signerRole) 1378 } 1379 1380 // The last owner of a team should not leave. 1381 // But that's really up to them and the server. We're just reading what has happened. 1382 1383 moveState() 1384 res.newState.inform(signer.signer, keybase1.TeamRole_NONE, payload.SignatureMetadata()) 1385 case libkb.LinkTypeNewSubteam: 1386 err = enforce(LinkRules{ 1387 Subteam: TristateRequire, 1388 Admin: TristateOptional, 1389 AllowInflate: true, 1390 }) 1391 if err != nil { 1392 return res, err 1393 } 1394 1395 // Check the subteam ID 1396 subteamID, err := t.assertIsSubteamID(string(team.Subteam.ID)) 1397 if err != nil { 1398 return res, err 1399 } 1400 1401 // Check the subteam name 1402 subteamName, err := t.assertSubteamName(prevState, link.Seqno(), string(team.Subteam.Name)) 1403 if err != nil { 1404 return res, err 1405 } 1406 1407 _, err = checkAdmin("make subteam") 1408 if err != nil { 1409 return res, err 1410 } 1411 1412 moveState() 1413 1414 // informSubteam will take care of asserting that these links are inflated 1415 // in order for each subteam. 1416 err = res.newState.informSubteam(subteamID, subteamName, link.Seqno()) 1417 if err != nil { 1418 return res, fmt.Errorf("adding new subteam: %v", err) 1419 } 1420 case libkb.LinkTypeSubteamHead: 1421 isHighLink = true 1422 1423 err = enforce(LinkRules{ 1424 Name: TristateRequire, 1425 Members: TristateRequire, 1426 Parent: TristateRequire, 1427 PerTeamKey: TristateRequire, 1428 Admin: TristateOptional, 1429 Settings: TristateOptional, 1430 BoxSummaryHash: TristateOptional, 1431 FirstInChain: true, 1432 }) 1433 if err != nil { 1434 return res, err 1435 } 1436 1437 if team.Public { 1438 return res, fmt.Errorf("public subteams are not supported") 1439 } 1440 1441 // Check the subteam ID 1442 if !teamID.IsSubTeam() { 1443 return res, fmt.Errorf("malformed subteam id") 1444 } 1445 1446 // Check parent ID 1447 parentID, err := keybase1.TeamIDFromString(string(team.Parent.ID)) 1448 if err != nil { 1449 return res, fmt.Errorf("invalid parent id: %v", err) 1450 } 1451 1452 // Check the initial subteam name 1453 teamName, err := keybase1.TeamNameFromString(string(*team.Name)) 1454 if err != nil { 1455 return res, err 1456 } 1457 if teamName.IsRootTeam() { 1458 return res, fmt.Errorf("subteam has root team name: %s", teamName) 1459 } 1460 if teamName.IsImplicit() || team.Implicit { 1461 return res, NewImplicitTeamOperationError(payload.Body.Type) 1462 } 1463 1464 roleUpdates, err := t.sanityCheckMembers(*team.Members, sanityCheckMembersOptions{ 1465 disallowOwners: true, 1466 allowRemovals: false, 1467 }) 1468 if err != nil { 1469 return res, err 1470 } 1471 1472 perTeamKey, err := t.checkPerTeamKey(*link.source, *team.PerTeamKey, nil) 1473 if err != nil { 1474 return res, err 1475 } 1476 1477 perTeamKeys := make(map[keybase1.PerTeamKeyGeneration]keybase1.PerTeamKey) 1478 perTeamKeys[keybase1.PerTeamKeyGeneration(1)] = perTeamKey 1479 1480 res.newState = TeamSigChainState{ 1481 inner: keybase1.TeamSigChainState{ 1482 Reader: t.reader, 1483 Id: teamID, 1484 Implicit: false, 1485 Public: false, 1486 RootAncestor: teamName.RootAncestorName(), 1487 NameDepth: teamName.Depth(), 1488 NameLog: []keybase1.TeamNameLogPoint{{ 1489 LastPart: teamName.LastPart(), 1490 Seqno: 1, 1491 }}, 1492 LastSeqno: 1, 1493 LastLinkID: link.LinkID().Export(), 1494 ParentID: &parentID, 1495 UserLog: make(map[keybase1.UserVersion][]keybase1.UserLogPoint), 1496 SubteamLog: make(map[keybase1.TeamID][]keybase1.SubteamLogPoint), 1497 PerTeamKeys: perTeamKeys, 1498 MaxPerTeamKeyGeneration: keybase1.PerTeamKeyGeneration(1), 1499 PerTeamKeyCTime: keybase1.UnixTime(payload.Ctime), 1500 LinkIDs: make(map[keybase1.Seqno]keybase1.LinkID), 1501 StubbedLinks: make(map[keybase1.Seqno]bool), 1502 InviteMetadatas: make(map[keybase1.TeamInviteID]keybase1.TeamInviteMetadata), 1503 MerkleRoots: make(map[keybase1.Seqno]keybase1.MerkleRootV2), 1504 Bots: make(map[keybase1.UserVersion]keybase1.TeamBotSettings), 1505 }} 1506 1507 t.updateMembership(&res.newState, roleUpdates, payload.SignatureMetadata()) 1508 if settings := team.Settings; settings != nil { 1509 err = t.parseTeamSettings(settings, &res.newState) 1510 if err != nil { 1511 return res, err 1512 } 1513 } 1514 case libkb.LinkTypeRenameSubteam: 1515 err = enforce(LinkRules{ 1516 Subteam: TristateRequire, 1517 Admin: TristateOptional, 1518 AllowInflate: true, 1519 }) 1520 if err != nil { 1521 return res, err 1522 } 1523 1524 _, err = checkAdmin("rename subteam") 1525 if err != nil { 1526 return res, err 1527 } 1528 1529 // Check the subteam ID 1530 subteamID, err := t.assertIsSubteamID(string(team.Subteam.ID)) 1531 if err != nil { 1532 return res, err 1533 } 1534 1535 // Check the subteam name 1536 subteamName, err := t.assertSubteamName(prevState, link.Seqno(), string(team.Subteam.Name)) 1537 if err != nil { 1538 return res, err 1539 } 1540 1541 moveState() 1542 1543 // informSubteam will take care of asserting that these links are inflated 1544 // in order for each subteam. 1545 err = res.newState.informSubteam(subteamID, subteamName, link.Seqno()) 1546 if err != nil { 1547 return res, fmt.Errorf("adding new subteam: %v", err) 1548 } 1549 case libkb.LinkTypeRenameUpPointer: 1550 err = enforce(LinkRules{ 1551 Name: TristateRequire, 1552 Parent: TristateRequire, 1553 Admin: TristateOptional, 1554 }) 1555 if err != nil { 1556 return res, err 1557 } 1558 1559 // These links only occur in subteam. 1560 if !prevState.IsSubteam() { 1561 return res, fmt.Errorf("got %v in root team", payload.Body.Type) 1562 } 1563 1564 // Sanity check that the parent doesn't claim to have changed. 1565 parentID, err := keybase1.TeamIDFromString(string(team.Parent.ID)) 1566 if err != nil { 1567 return res, fmt.Errorf("invalid parent team id: %v", err) 1568 } 1569 if !parentID.Eq(*prevState.GetParentID()) { 1570 return res, fmt.Errorf("wrong parent team ID: %s != %s", parentID, prevState.GetParentID()) 1571 } 1572 1573 // Ideally we would assert that the name 1574 // But we may not have an up-to-date picture at this time of the parent's name. 1575 // So assert this: 1576 // - The root team name is the same. 1577 // - The depth of the new name is the same. 1578 newName, err := keybase1.TeamNameFromString(string(*team.Name)) 1579 if err != nil { 1580 return res, fmt.Errorf("invalid team name '%s': %v", *team.Name, err) 1581 } 1582 if newName.IsRootTeam() { 1583 return res, fmt.Errorf("cannot rename to root team name: %v", newName.String()) 1584 } 1585 if !newName.RootAncestorName().Eq(prevState.inner.RootAncestor) { 1586 return res, fmt.Errorf("rename cannot change root ancestor team name: %v -> %v", prevState.inner.RootAncestor, newName) 1587 } 1588 if newName.Depth() != prevState.inner.NameDepth { 1589 return res, fmt.Errorf("rename cannot change team nesting depth: %v -> %v", prevState.inner.NameDepth, newName) 1590 } 1591 1592 moveState() 1593 1594 res.newState.inner.NameLog = append(res.newState.inner.NameLog, keybase1.TeamNameLogPoint{ 1595 LastPart: newName.LastPart(), 1596 Seqno: link.Seqno(), 1597 }) 1598 case libkb.LinkTypeDeleteSubteam: 1599 err = enforce(LinkRules{ 1600 Subteam: TristateRequire, 1601 Admin: TristateOptional, 1602 AllowInflate: true, 1603 }) 1604 if err != nil { 1605 return res, err 1606 } 1607 1608 _, err = checkAdmin("delete subteam") 1609 if err != nil { 1610 return res, err 1611 } 1612 1613 // Check the subteam ID 1614 subteamID, err := t.assertIsSubteamID(string(team.Subteam.ID)) 1615 if err != nil { 1616 return res, err 1617 } 1618 1619 // Check the subteam name 1620 _, err = t.assertSubteamName(prevState, link.Seqno(), string(team.Subteam.Name)) 1621 if err != nil { 1622 return res, err 1623 } 1624 1625 moveState() 1626 1627 err = res.newState.informSubteamDelete(subteamID, link.Seqno()) 1628 if err != nil { 1629 return res, fmt.Errorf("error deleting subteam: %v", err) 1630 } 1631 case libkb.LinkTypeInvite: 1632 err = enforce(LinkRules{ 1633 Admin: TristateOptional, 1634 Invites: TristateRequire, 1635 AllowInImplicitTeam: true, 1636 }) 1637 if err != nil { 1638 return res, err 1639 } 1640 1641 signerIsExplicitOwner, err := checkAdmin("invite") 1642 if err != nil { 1643 return res, err 1644 } 1645 1646 additions, cancelations, err := t.sanityCheckInvites(mctx, signer.signer, signerIsExplicitOwner, 1647 *team.Invites, link.SigID(), sanityCheckInvitesOptions{ 1648 isRootTeam: !prevState.IsSubteam(), 1649 implicitTeam: prevState.IsImplicit(), 1650 }) 1651 if err != nil { 1652 return res, err 1653 } 1654 1655 if prevState.IsImplicit() { 1656 // Check to see if the additions were previously members of the team 1657 checkImpteamInvites := func() error { 1658 addedUIDs := make(map[keybase1.UID]bool) 1659 for _, invites := range additions { 1660 for _, invite := range invites { 1661 cat, err := invite.Type.C() 1662 if err != nil { 1663 return err 1664 } 1665 if cat == keybase1.TeamInviteCategory_KEYBASE { 1666 uv, err := invite.KeybaseUserVersion() 1667 if err != nil { 1668 return err 1669 } 1670 addedUIDs[uv.Uid] = true 1671 _, err = prevState.GetLatestUVWithUID(uv.Uid) 1672 if err == nil { 1673 // Found crypto member in previous 1674 // state, we are good! 1675 continue 1676 } 1677 _, _, found := prevState.FindActiveKeybaseInvite(uv.Uid) 1678 if found { 1679 // Found PUKless member in previous 1680 // state, still fine! 1681 continue 1682 } 1683 // Neither crypto member nor PUKless member 1684 // found, we can't allow this addition. 1685 return fmt.Errorf("Not found previous version of user %s", uv.Uid) 1686 } 1687 return fmt.Errorf("invalid invite type in implicit team: %v", cat) 1688 } 1689 } 1690 1691 var cancelledUVs []keybase1.UserVersion 1692 for _, inviteID := range cancelations { 1693 inviteMD, found := prevState.FindActiveInviteMDByID(inviteID) 1694 if !found { 1695 // This is harmless and also we might be canceling 1696 // an obsolete invite. 1697 continue 1698 } 1699 inviteUv, err := inviteMD.Invite.KeybaseUserVersion() 1700 if err != nil { 1701 return fmt.Errorf("cancelled invite is not valid keybase-type invite: %v", err) 1702 } 1703 cancelledUVs = append(cancelledUVs, inviteUv) 1704 } 1705 1706 for _, uv := range cancelledUVs { 1707 if !addedUIDs[uv.Uid] { 1708 return fmt.Errorf("cancelling invite for %v without inviting back a new version", uv) 1709 } 1710 } 1711 return nil 1712 } 1713 if err := checkImpteamInvites(); err != nil { 1714 return res, NewImplicitTeamOperationError("Error in link %q: %v", payload.Body.Type, err) 1715 } 1716 } 1717 1718 moveState() 1719 t.updateInvites(&res.newState, additions, cancelations, teamSigMeta) 1720 case libkb.LinkTypeSettings: 1721 err = enforce(LinkRules{ 1722 Admin: TristateOptional, 1723 Settings: TristateRequire, 1724 // Allow key rotation in settings link. Closing an open team 1725 // should rotate team key. 1726 PerTeamKey: TristateOptional, 1727 // At the moment the only team setting is banned in implicit teams. 1728 // But in the future there could be allowed settings that also use this link type. 1729 AllowInImplicitTeam: true, 1730 }) 1731 if err != nil { 1732 return res, err 1733 } 1734 1735 _, err = checkAdmin("change settings") 1736 if err != nil { 1737 return res, err 1738 } 1739 1740 moveState() 1741 err = t.parseTeamSettings(team.Settings, &res.newState) 1742 if err != nil { 1743 return res, err 1744 } 1745 1746 // When team is changed from open to closed, per-team-key should be rotated. But 1747 // this is not enforced. 1748 if team.PerTeamKey != nil { 1749 newKey, err := t.checkPerTeamKey(*link.source, *team.PerTeamKey, &res.newState) 1750 if err != nil { 1751 return res, err 1752 } 1753 res.newState.inner.PerTeamKeys[newKey.Gen] = newKey 1754 res.newState.inner.PerTeamKeyCTime = keybase1.UnixTime(payload.Ctime) 1755 if newKey.Gen > res.newState.inner.MaxPerTeamKeyGeneration { 1756 res.newState.inner.MaxPerTeamKeyGeneration = newKey.Gen 1757 } 1758 } 1759 case libkb.LinkTypeDeleteRoot: 1760 return res, NewTeamDeletedError() 1761 case libkb.LinkTypeDeleteUpPointer: 1762 return res, NewTeamDeletedError() 1763 case libkb.LinkTypeKBFSSettings: 1764 err = enforce(LinkRules{ 1765 Admin: TristateOptional, 1766 KBFS: TristateRequire, 1767 AllowInImplicitTeam: true, 1768 }) 1769 if err != nil { 1770 return res, err 1771 } 1772 1773 err = checkExplicitWriter("change KBFS settings") 1774 if err != nil { 1775 return res, err 1776 } 1777 1778 moveState() 1779 err = t.parseKBFSTLFUpgrade(team.KBFS, &res.newState) 1780 if err != nil { 1781 return res, err 1782 } 1783 case libkb.LinkTypeTeamBotSettings: 1784 if err = enforce(LinkRules{ 1785 Admin: TristateOptional, 1786 BotSettings: TristateRequire, 1787 AllowInImplicitTeam: true, 1788 }); err != nil { 1789 return res, err 1790 } 1791 1792 if _, err = checkAdmin("change bots"); err != nil { 1793 return res, err 1794 } 1795 1796 moveState() 1797 if err = t.parseTeamBotSettings(*team.BotSettings, &res.newState); err != nil { 1798 return res, err 1799 } 1800 case "": 1801 return res, errors.New("empty body type") 1802 default: 1803 if link.outerLink.IgnoreIfUnsupported { 1804 moveState() 1805 } else { 1806 return res, fmt.Errorf("unsupported link type: %s", payload.Body.Type) 1807 } 1808 } 1809 1810 if isHighLink { 1811 res.newState.inner.LastHighLinkID = link.LinkID().Export() 1812 res.newState.inner.LastHighSeqno = link.Seqno() 1813 } 1814 return res, nil 1815} 1816 1817func (t *teamSigchainPlayer) roleUpdateChangedHighSet(prevState *TeamSigChainState, roleUpdates chainRoleUpdates) (bool, error) { 1818 // The high set of users can be changed by promotion to Admin/Owner or 1819 // demotion from Admin/Owner or any movement between those two roles. 1820 for newRole, uvs := range roleUpdates { 1821 if newRole.IsAdminOrAbove() { 1822 return true, nil 1823 } 1824 // were any of these users previously an admin or above 1825 for _, uv := range uvs { 1826 prevRole, err := prevState.GetUserRole(uv) 1827 if err != nil { 1828 return false, err 1829 } 1830 if prevRole.IsAdminOrAbove() { 1831 return true, nil 1832 } 1833 } 1834 1835 } 1836 return false, nil 1837} 1838 1839func (t *teamSigchainPlayer) checkSeqnoToAdd(prevState *TeamSigChainState, linkSeqno keybase1.Seqno, isInflate bool) error { 1840 if linkSeqno < 1 { 1841 return fmt.Errorf("link seqno (%v) cannot be less than 1", linkSeqno) 1842 } 1843 if prevState == nil { 1844 if isInflate { 1845 return fmt.Errorf("cannot inflate link %v with no previous state", linkSeqno) 1846 } 1847 if linkSeqno != 1 { 1848 return fmt.Errorf("first team link must have seqno 1 but got %v", linkSeqno) 1849 } 1850 } else { 1851 if isInflate { 1852 if prevState.IsLinkFilled(linkSeqno) { 1853 return fmt.Errorf("link %v is already filled", linkSeqno) 1854 } 1855 } else { 1856 if linkSeqno != prevState.GetLatestSeqno()+1 { 1857 return fmt.Errorf("link had unexpected seqno %v != %v", linkSeqno, prevState.GetLatestSeqno()+1) 1858 } 1859 } 1860 if linkSeqno > prevState.GetLatestSeqno()+1 { 1861 return fmt.Errorf("link had far-future seqno %v > %v", linkSeqno, prevState.GetLatestSeqno()+1) 1862 } 1863 } 1864 return nil 1865} 1866 1867type sanityCheckInvitesOptions struct { 1868 isRootTeam bool 1869 implicitTeam bool 1870} 1871 1872func assertIsKeybaseInvite(mctx libkb.MetaContext, i SCTeamInvite) bool { 1873 typ, err := TeamInviteTypeFromString(mctx, i.Type) 1874 if err != nil { 1875 mctx.Info("bad invite type: %s", err) 1876 return false 1877 } 1878 cat, err := typ.C() 1879 if err != nil { 1880 mctx.Info("bad invite category: %s", err) 1881 return false 1882 } 1883 return cat == keybase1.TeamInviteCategory_KEYBASE 1884} 1885 1886// These signatures contain non-owners inviting owners. 1887// They slipped in before that was banned. They are excepted from the rule. 1888var hardcodedInviteRuleExceptionSigIDs = map[keybase1.SigIDMapKey]bool{ 1889 "c06e8da2959d8c8054fb10e005910716f776b3c3df9ef2eb4c4b8584f45e187f0f": true, 1890 "e800db474fa75f39503e9241990c3707121c7c414687a7b1f5ef579a625eaf820f": true, 1891 "46d9f2700b8d4287a2dc46dae00974a794b5778149214cf91fa4b69229a6abbc0f": true, 1892} 1893 1894// sanityCheckInvites sanity checks a raw SCTeamInvites section and coerces it into a 1895// format that we can use. It checks: 1896// - inviting owners is sometimes banned 1897// - invite IDs aren't repeated 1898// - <name,type> pairs aren't reused 1899// - IDs parse into proper keybase1.TeamInviteIDs 1900// - the invite type parses into proper TeamInviteType, or that it's an unknown 1901// invite that we're OK to not act upon. 1902// Implicit teams are different: 1903// - owners and readers are the only allowed roles 1904func (t *teamSigchainPlayer) sanityCheckInvites(mctx libkb.MetaContext, 1905 signer keybase1.UserVersion, signerIsExplicitOwner bool, invites SCTeamInvites, sigID keybase1.SigID, 1906 options sanityCheckInvitesOptions, 1907) (additions map[keybase1.TeamRole][]keybase1.TeamInvite, cancelations []keybase1.TeamInviteID, err error) { 1908 1909 type assignment struct { 1910 i SCTeamInvite 1911 role keybase1.TeamRole 1912 } 1913 var all []assignment 1914 additions = make(map[keybase1.TeamRole][]keybase1.TeamInvite) 1915 1916 if invites.Owners != nil && len(*invites.Owners) > 0 { 1917 additions[keybase1.TeamRole_OWNER] = nil 1918 for _, i := range *invites.Owners { 1919 if !options.isRootTeam { 1920 return nil, nil, fmt.Errorf("encountered invite of owner in non-root team") 1921 } 1922 if !signerIsExplicitOwner { 1923 if !hardcodedInviteRuleExceptionSigIDs[sigID.ToMapKey()] { 1924 return nil, nil, fmt.Errorf("encountered invite of owner by non-owner") 1925 } 1926 } 1927 if !(options.implicitTeam || assertIsKeybaseInvite(mctx, i)) { 1928 return nil, nil, fmt.Errorf("encountered a disallowed owner invite") 1929 } 1930 all = append(all, assignment{i, keybase1.TeamRole_OWNER}) 1931 } 1932 } 1933 1934 if invites.Admins != nil && len(*invites.Admins) > 0 { 1935 if options.implicitTeam { 1936 return nil, nil, NewImplicitTeamOperationError("encountered admin invite") 1937 } 1938 additions[keybase1.TeamRole_ADMIN] = nil 1939 for _, i := range *invites.Admins { 1940 all = append(all, assignment{i, keybase1.TeamRole_ADMIN}) 1941 } 1942 } 1943 1944 if invites.Writers != nil && len(*invites.Writers) > 0 { 1945 if options.implicitTeam { 1946 return nil, nil, NewImplicitTeamOperationError("encountered writer invite") 1947 } 1948 additions[keybase1.TeamRole_WRITER] = nil 1949 for _, i := range *invites.Writers { 1950 all = append(all, assignment{i, keybase1.TeamRole_WRITER}) 1951 } 1952 } 1953 1954 if invites.Readers != nil && len(*invites.Readers) > 0 { 1955 additions[keybase1.TeamRole_READER] = nil 1956 for _, i := range *invites.Readers { 1957 all = append(all, assignment{i, keybase1.TeamRole_READER}) 1958 } 1959 } 1960 1961 // Set to `true` if it was an addition and `false` if it was a deletion 1962 byID := make(map[keybase1.TeamInviteID]bool) 1963 byName := make(map[string]bool) 1964 1965 keyFunc := func(i SCTeamInvite) string { 1966 return fmt.Sprintf("%s:%s", i.Type, i.Name) 1967 } 1968 1969 if invites.Cancel != nil { 1970 for _, c := range *invites.Cancel { 1971 id, err := c.TeamInviteID() 1972 if err != nil { 1973 return nil, nil, err 1974 } 1975 if byID[id] { 1976 return nil, nil, NewInviteError(id, fmt.Errorf("ID %s appears twice as a cancellation", c)) 1977 } 1978 byID[id] = false 1979 cancelations = append(cancelations, id) 1980 } 1981 } 1982 1983 for _, invite := range all { 1984 res, err := invite.i.TeamInvite(mctx, invite.role, signer) 1985 if err != nil { 1986 return nil, nil, err 1987 } 1988 id := res.Id 1989 _, seen := byID[id] 1990 if seen { 1991 return nil, nil, NewInviteError(id, fmt.Errorf("appears twice in invite set")) 1992 } 1993 key := keyFunc(invite.i) 1994 if byName[key] { 1995 return nil, nil, NewInviteError(id, fmt.Errorf("invite %s appears twice in invite set", key)) 1996 } 1997 1998 isNewStyle, err := IsNewStyleInvite(res) 1999 if err != nil { 2000 return nil, nil, NewInviteError(id, fmt.Errorf("failed to check if invite is new-style: %s", err)) 2001 } 2002 if isNewStyle { 2003 if options.implicitTeam { 2004 return nil, nil, NewInviteError(id, fmt.Errorf("new-style in implicit team")) 2005 } 2006 if res.MaxUses == nil { 2007 return nil, nil, NewInviteError(id, fmt.Errorf("new-style but has no max-uses")) 2008 } 2009 if !res.MaxUses.IsNotNilAndValid() { 2010 return nil, nil, NewInviteError(id, fmt.Errorf("invalid max_uses %d", *res.MaxUses)) 2011 } 2012 if res.Etime != nil { 2013 if *res.Etime <= 0 { 2014 return nil, nil, NewInviteError(id, fmt.Errorf("invalid etime %d", *res.Etime)) 2015 } 2016 } 2017 } 2018 2019 // NOTE: We are allowing etime without max_uses right now in sigchain 2020 // player. It would rejected on the server though. We want to allow 2021 // this now in case we do expiring invites in the future. 2022 2023 category, categoryErr := res.Type.C() 2024 // Do not error out if there's a new invite category we don't recognize 2025 if categoryErr == nil && category == keybase1.TeamInviteCategory_INVITELINK { 2026 if res.Role.IsAdminOrAbove() { 2027 return nil, nil, NewInviteError(id, NewInvitelinkBadRoleError(res.Role)) 2028 } 2029 } 2030 2031 byName[key] = true 2032 byID[id] = true 2033 additions[res.Role] = append(additions[res.Role], res) 2034 } 2035 2036 return additions, cancelations, nil 2037} 2038 2039// A map describing an intent to change users' roles. 2040// Each item means: change that user to that role. 2041// To be clear: An omission does NOT mean to remove the existing role. 2042type chainRoleUpdates map[keybase1.TeamRole][]keybase1.UserVersion 2043 2044type sanityCheckMembersOptions struct { 2045 // At least one owner must be added 2046 requireOwners bool 2047 // Adding owners is blocked 2048 disallowOwners bool 2049 // Removals are allowed, blocked if false 2050 allowRemovals bool 2051 // Only additions of OWNER or READER are allowed. Does not affect removals. 2052 onlyOwnersOrReaders bool 2053} 2054 2055// Check that all the users are formatted correctly. 2056// Check that there are no duplicate members. 2057// Do not check that all removals are members. That should be true, but not strictly enforced when reading. 2058func (t *teamSigchainPlayer) sanityCheckMembers(members SCTeamMembers, options sanityCheckMembersOptions) (chainRoleUpdates, error) { 2059 type assignment struct { 2060 m SCTeamMember 2061 role keybase1.TeamRole 2062 } 2063 var all []assignment 2064 2065 if options.requireOwners { 2066 if members.Owners == nil { 2067 return nil, fmt.Errorf("team has no owner list") 2068 } 2069 if len(*members.Owners) < 1 { 2070 return nil, fmt.Errorf("team has no owners") 2071 } 2072 } 2073 if options.disallowOwners { 2074 if members.Owners != nil && len(*members.Owners) > 0 { 2075 return nil, fmt.Errorf("team has owners") 2076 } 2077 } 2078 if !options.allowRemovals { 2079 if members.None != nil && len(*members.None) != 0 { 2080 return nil, fmt.Errorf("team has removals in link") 2081 } 2082 } 2083 2084 // Map from roles to users. 2085 res := make(map[keybase1.TeamRole][]keybase1.UserVersion) 2086 2087 if members.Owners != nil && len(*members.Owners) > 0 { 2088 res[keybase1.TeamRole_OWNER] = nil 2089 for _, m := range *members.Owners { 2090 all = append(all, assignment{m, keybase1.TeamRole_OWNER}) 2091 } 2092 } 2093 if members.Admins != nil && len(*members.Admins) > 0 { 2094 if options.onlyOwnersOrReaders { 2095 return nil, NewImplicitTeamOperationError("encountered add admin") 2096 } 2097 res[keybase1.TeamRole_ADMIN] = nil 2098 for _, m := range *members.Admins { 2099 all = append(all, assignment{m, keybase1.TeamRole_ADMIN}) 2100 } 2101 } 2102 if members.Writers != nil && len(*members.Writers) > 0 { 2103 if options.onlyOwnersOrReaders { 2104 return nil, NewImplicitTeamOperationError("encountered add writer") 2105 } 2106 res[keybase1.TeamRole_WRITER] = nil 2107 for _, m := range *members.Writers { 2108 all = append(all, assignment{m, keybase1.TeamRole_WRITER}) 2109 } 2110 } 2111 if members.Readers != nil && len(*members.Readers) > 0 { 2112 res[keybase1.TeamRole_READER] = nil 2113 for _, m := range *members.Readers { 2114 all = append(all, assignment{m, keybase1.TeamRole_READER}) 2115 } 2116 } 2117 if members.Bots != nil && len(*members.Bots) > 0 { 2118 res[keybase1.TeamRole_BOT] = nil 2119 for _, m := range *members.Bots { 2120 all = append(all, assignment{m, keybase1.TeamRole_BOT}) 2121 } 2122 } 2123 if members.RestrictedBots != nil && len(*members.RestrictedBots) > 0 { 2124 res[keybase1.TeamRole_RESTRICTEDBOT] = nil 2125 for _, m := range *members.RestrictedBots { 2126 all = append(all, assignment{m, keybase1.TeamRole_RESTRICTEDBOT}) 2127 } 2128 } 2129 if members.None != nil && len(*members.None) > 0 { 2130 res[keybase1.TeamRole_NONE] = nil 2131 for _, m := range *members.None { 2132 all = append(all, assignment{m, keybase1.TeamRole_NONE}) 2133 } 2134 } 2135 2136 // Set of users who have already been seen. 2137 seen := make(map[keybase1.UserVersion]bool) 2138 2139 for _, pair := range all { 2140 uv := keybase1.UserVersion(pair.m) 2141 2142 if seen[uv] { 2143 return nil, fmt.Errorf("duplicate UID in members: %s", uv.Uid) 2144 } 2145 2146 res[pair.role] = append(res[pair.role], uv) 2147 2148 seen[uv] = true 2149 } 2150 2151 return res, nil 2152} 2153 2154// Whether the roleUpdates would demote any current owner to a lesser role. 2155func (t *teamSigchainPlayer) roleUpdatesDemoteOwners(prev *TeamSigChainState, roleUpdates map[keybase1.TeamRole][]keybase1.UserVersion) bool { 2156 2157 // It is OK to readmit an owner if the owner reset and is coming in at a lower permission 2158 // level. So check that case here. 2159 readmittingResetUser := func(uv keybase1.UserVersion) bool { 2160 for toRole, uvs := range roleUpdates { 2161 if toRole == keybase1.TeamRole_NONE { 2162 continue 2163 } 2164 for _, newUV := range uvs { 2165 if newUV.Uid.Equal(uv.Uid) && newUV.EldestSeqno > uv.EldestSeqno { 2166 return true 2167 } 2168 } 2169 } 2170 return false 2171 } 2172 2173 for toRole, uvs := range roleUpdates { 2174 if toRole == keybase1.TeamRole_OWNER { 2175 continue 2176 } 2177 for _, uv := range uvs { 2178 fromRole, err := prev.GetUserRole(uv) 2179 if err != nil { 2180 continue // ignore error, user not in team 2181 } 2182 if toRole == keybase1.TeamRole_NONE && fromRole == keybase1.TeamRole_OWNER && readmittingResetUser(uv) { 2183 continue 2184 } 2185 if fromRole == keybase1.TeamRole_OWNER { 2186 // This is an intent to demote an owner. 2187 return true 2188 } 2189 } 2190 } 2191 return false 2192} 2193 2194func (t *teamSigchainPlayer) checkPerTeamKey(link SCChainLink, perTeamKey SCPerTeamKey, prevState *TeamSigChainState) (res keybase1.PerTeamKey, err error) { 2195 2196 // Check that the new key doesn't conflict with keys we already have. 2197 err = prevState.checkNewPTK(perTeamKey) 2198 if err != nil { 2199 return res, err 2200 } 2201 2202 // validate signing kid 2203 sigKey, err := libkb.ImportNaclSigningKeyPairFromHex(perTeamKey.SigKID.String()) 2204 if err != nil { 2205 return res, fmt.Errorf("invalid per-team-key signing KID: %s", perTeamKey.SigKID) 2206 } 2207 2208 // validate encryption kid 2209 _, err = libkb.ImportNaclDHKeyPairFromHex(perTeamKey.EncKID.String()) 2210 if err != nil { 2211 return res, fmt.Errorf("invalid per-team-key encryption KID: %s", perTeamKey.EncKID) 2212 } 2213 2214 // verify the reverse sig 2215 // jw is the expected reverse sig contents 2216 jw, err := jsonw.Unmarshal([]byte(link.Payload)) 2217 if err != nil { 2218 return res, libkb.NewReverseSigError("per-team-key reverse sig: failed to parse payload: %s", err) 2219 } 2220 err = libkb.VerifyReverseSig(t.G(), sigKey, "body.team.per_team_key.reverse_sig", jw, perTeamKey.ReverseSig) 2221 if err != nil { 2222 return res, libkb.NewReverseSigError("per-team-key reverse sig: %s", err) 2223 } 2224 2225 return keybase1.PerTeamKey{ 2226 Gen: perTeamKey.Generation, 2227 Seqno: link.Seqno, 2228 SigKID: perTeamKey.SigKID, 2229 EncKID: perTeamKey.EncKID, 2230 }, nil 2231} 2232 2233// Update `userLog` with the membership in roleUpdates. 2234// The `NONE` list removes users. 2235// The other lists add users. 2236func (t *teamSigchainPlayer) updateMembership(stateToUpdate *TeamSigChainState, roleUpdates chainRoleUpdates, sigMeta keybase1.SignatureMetadata) { 2237 for role, uvs := range roleUpdates { 2238 for _, uv := range uvs { 2239 stateToUpdate.inform(uv, role, sigMeta) 2240 } 2241 } 2242} 2243 2244func (t *teamSigchainPlayer) updateInvites(stateToUpdate *TeamSigChainState, additions map[keybase1.TeamRole][]keybase1.TeamInvite, cancelations []keybase1.TeamInviteID, teamSigMeta keybase1.TeamSignatureMetadata) { 2245 for _, invites := range additions { 2246 for _, invite := range invites { 2247 stateToUpdate.informNewInvite(invite, teamSigMeta) 2248 } 2249 } 2250 for _, cancelation := range cancelations { 2251 stateToUpdate.informCanceledInvite(cancelation, teamSigMeta) 2252 } 2253} 2254 2255func (t *teamSigchainPlayer) completeInvites(stateToUpdate *TeamSigChainState, 2256 completed map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm, 2257 teamSigMeta keybase1.TeamSignatureMetadata) error { 2258 for id := range completed { 2259 inviteMD, found := stateToUpdate.FindActiveInviteMDByID(id) 2260 if !found { 2261 // Invite doesn't exist or we don't know about it because invite 2262 // links were stubbed. We could do a similar check here that we do 2263 // in teamSigchainPlayer.useInvites, but we haven't been doing it 2264 // in the past, so we might have links with issues like that in the 2265 // wild. 2266 continue 2267 } 2268 isNewStyle, err := IsNewStyleInvite(inviteMD.Invite) 2269 if err != nil { 2270 return err 2271 } 2272 if isNewStyle { 2273 return fmt.Errorf("`completed_invites` for a new-style invite (id: %q)", id) 2274 } 2275 stateToUpdate.informCompletedInvite(id, teamSigMeta) 2276 } 2277 return nil 2278} 2279 2280func (t *teamSigchainPlayer) obsoleteActiveInvites(stateToUpdate *TeamSigChainState, roleUpdates chainRoleUpdates, sigMeta keybase1.SignatureMetadata) { 2281 if len(stateToUpdate.inner.InviteMetadatas) == 0 { 2282 return 2283 } 2284 2285 m := make(map[keybase1.UID]struct{}) 2286 for _, uvs := range roleUpdates { 2287 for _, uv := range uvs { 2288 m[uv.Uid] = struct{}{} 2289 } 2290 } 2291 2292 for _, inviteMD := range stateToUpdate.ActiveInvites() { 2293 if inviteUv, err := inviteMD.Invite.KeybaseUserVersion(); err == nil { 2294 if _, ok := m[inviteUv.Uid]; ok { 2295 inviteMD.Status = keybase1.NewTeamInviteMetadataStatusWithObsolete() 2296 stateToUpdate.inner.InviteMetadatas[inviteMD.Invite.Id] = inviteMD 2297 } 2298 } 2299 } 2300} 2301 2302func (t *teamSigchainPlayer) useInvites(stateToUpdate *TeamSigChainState, roleUpdates chainRoleUpdates, used []SCMapInviteIDUVPair) error { 2303 if len(used) == 0 { 2304 return nil 2305 } 2306 2307 seenUVs := make(map[keybase1.UserVersion]bool) 2308 hasStubbedLinks := stateToUpdate.HasAnyStubbedLinks() 2309 for _, pair := range used { 2310 inviteID, err := pair.InviteID.TeamInviteID() 2311 if err != nil { 2312 return err 2313 } 2314 uv, err := keybase1.ParseUserVersion(pair.UV) 2315 if err != nil { 2316 return err 2317 } 2318 2319 inviteMD, foundInvite := stateToUpdate.FindActiveInviteMDByID(inviteID) 2320 if !foundInvite { 2321 if hasStubbedLinks { 2322 // We didn't find it, possibly because server stubbed it out (we're not allowed to 2323 // see it; didn't load with admin perms). 2324 continue 2325 } else { 2326 // We couldn't find the invite, and we have no stubbed links, which 2327 // means that inviteID is invalid. 2328 return fmt.Errorf("could not find active invite ID in used_invites: %s", inviteID) 2329 } 2330 } 2331 2332 isNewStyle, err := IsNewStyleInvite(inviteMD.Invite) 2333 if err != nil { 2334 return err 2335 } 2336 if !isNewStyle { 2337 return fmt.Errorf("`used_invites` for a non-new-style invite (id: %q)", inviteID) 2338 } 2339 2340 alreadyUsed := len(inviteMD.UsedInvites) 2341 // Note that we append to inviteMD.UsedInvites at the end of this for loop, so alreadyUsed 2342 // updates correctly when processing multiple invite pairs. 2343 if inviteMD.Invite.IsUsedUp(alreadyUsed) { 2344 return fmt.Errorf("invite %s is expired after %d uses", inviteID, alreadyUsed) 2345 } 2346 2347 // We explicitly don't check invite.Etime here; it's used as a hint for admins. 2348 // but not checked in the sigchain player. 2349 2350 // If we have the invite, also check if invite role matches role 2351 // added. 2352 var foundUV bool 2353 for _, updatedUV := range roleUpdates[inviteMD.Invite.Role] { 2354 if uv.Eq(updatedUV) { 2355 foundUV = true 2356 break 2357 } 2358 } 2359 if !foundUV { 2360 return fmt.Errorf("used_invite for UV %s that was not added as role %s", pair.UV, 2361 inviteMD.Invite.Role.HumanString()) 2362 } 2363 2364 if seen := seenUVs[uv]; seen { 2365 return fmt.Errorf("duplicate used_invite for UV %s", pair.UV) 2366 } 2367 seenUVs[uv] = true 2368 2369 // Because we use information from the UserLog here, useInvites should be called after 2370 // updateMembership. 2371 logPoint := len(stateToUpdate.inner.UserLog[uv]) - 1 2372 if logPoint < 0 { 2373 return fmt.Errorf("used_invite for UV %s that was not added to to the team", pair.UV) 2374 } 2375 inviteMD.UsedInvites = append(inviteMD.UsedInvites, 2376 keybase1.TeamUsedInviteLogPoint{ 2377 Uv: uv, 2378 LogPoint: logPoint, 2379 }) 2380 stateToUpdate.inner.InviteMetadatas[inviteID] = inviteMD 2381 } 2382 return nil 2383} 2384 2385// Check that the subteam name is valid and kind of is a child of this chain. 2386// Returns the parsed subteam name. 2387func (t *teamSigchainPlayer) assertSubteamName(parent *TeamSigChainState, parentSeqno keybase1.Seqno, subteamNameStr string) (keybase1.TeamName, error) { 2388 // Ideally, we would assert the team name is a direct child of this team's name. 2389 // But the middle parts of the names might be out of date. 2390 // Instead assert: 2391 // - The root team name is same. 2392 // - The subteam is 1 level deeper. 2393 // - The last part of the parent team's name matched 2394 // at the parent-seqno that this subteam name was mentioned. 2395 // (If the subteam is a.b.c.d then c should match) 2396 // The reason this is pegged at seqno instead of simply the latest parent name is 2397 // hard to explain, see TestRenameInflateSubteamAfterRenameParent. 2398 2399 subteamName, err := keybase1.TeamNameFromString(subteamNameStr) 2400 if err != nil { 2401 return subteamName, fmt.Errorf("invalid subteam team name '%s': %v", subteamNameStr, err) 2402 } 2403 2404 if !parent.inner.RootAncestor.Eq(subteamName.RootAncestorName()) { 2405 return subteamName, fmt.Errorf("subteam is of a different root team: %v != %v", 2406 subteamName.RootAncestorName(), 2407 parent.inner.RootAncestor) 2408 } 2409 2410 expectedDepth := parent.inner.NameDepth + 1 2411 if subteamName.Depth() != expectedDepth { 2412 return subteamName, fmt.Errorf("subteam name has depth %v but expected %v", 2413 subteamName.Depth(), expectedDepth) 2414 } 2415 2416 // The last part of the parent name at parentSeqno. 2417 var parentLastPart keybase1.TeamNamePart 2418 for _, point := range parent.inner.NameLog { 2419 if point.Seqno > parentSeqno { 2420 break 2421 } 2422 parentLastPart = point.LastPart 2423 } 2424 2425 subteamSecondToLastPart := subteamName.Parts[len(subteamName.Parts)-2] 2426 if !subteamSecondToLastPart.Eq(parentLastPart) { 2427 return subteamName, fmt.Errorf("subteam name has wrong name for us: %v != %v", 2428 subteamSecondToLastPart, parentLastPart) 2429 } 2430 2431 return subteamName, nil 2432} 2433 2434func (t *teamSigchainPlayer) assertIsSubteamID(subteamIDStr string) (keybase1.TeamID, error) { 2435 // Check the subteam ID 2436 subteamID, err := keybase1.TeamIDFromString(subteamIDStr) 2437 if err != nil { 2438 return subteamID, fmt.Errorf("invalid subteam id: %v", err) 2439 } 2440 if !subteamID.IsSubTeam() { 2441 return subteamID, fmt.Errorf("malformed subteam id") 2442 } 2443 return subteamID, nil 2444} 2445 2446func (t *teamSigchainPlayer) parseTeamSettings(settings *SCTeamSettings, newState *TeamSigChainState) error { 2447 if open := settings.Open; open != nil { 2448 if newState.inner.Implicit { 2449 return fmt.Errorf("implicit team cannot be open") 2450 } 2451 2452 newState.inner.Open = open.Enabled 2453 if options := open.Options; options != nil { 2454 if !open.Enabled { 2455 return fmt.Errorf("closed team shouldn't define team.settings.open.options") 2456 } 2457 2458 switch options.JoinAs { 2459 case "reader": 2460 newState.inner.OpenTeamJoinAs = keybase1.TeamRole_READER 2461 case "writer": 2462 newState.inner.OpenTeamJoinAs = keybase1.TeamRole_WRITER 2463 default: 2464 return fmt.Errorf("invalid join_as role in open team: %s", options.JoinAs) 2465 } 2466 } else if open.Enabled { 2467 return fmt.Errorf("team set to open but team.settings.open.options is missing") 2468 } 2469 } 2470 2471 return nil 2472} 2473 2474func (t *teamSigchainPlayer) parseTeamBotSettings(bots []SCTeamBot, newState *TeamSigChainState) error { 2475 2476 for _, bot := range bots { 2477 // Bots listed here must have the RESTRICTEDBOT role 2478 role, err := newState.GetUserRole(bot.Bot.ToUserVersion()) 2479 if err != nil { 2480 return err 2481 } 2482 if !role.IsRestrictedBot() { 2483 return fmt.Errorf("found bot settings for %v. Expected role RESTRICTEDBOT, found %v", bot, role) 2484 } 2485 2486 var convs, triggers []string 2487 if bot.Triggers != nil { 2488 triggers = *bot.Triggers 2489 } 2490 if bot.Convs != nil { 2491 convs = *bot.Convs 2492 } 2493 if newState.inner.Bots == nil { 2494 // If an old client cached this as nil, then just make a new map here for this link 2495 newState.inner.Bots = make(map[keybase1.UserVersion]keybase1.TeamBotSettings) 2496 } 2497 newState.inner.Bots[bot.Bot.ToUserVersion()] = keybase1.TeamBotSettings{ 2498 Cmds: bot.Cmds, 2499 Mentions: bot.Mentions, 2500 Triggers: triggers, 2501 Convs: convs, 2502 } 2503 } 2504 return nil 2505} 2506 2507func (t *teamSigchainPlayer) parseKBFSTLFUpgrade(upgrade *SCTeamKBFS, newState *TeamSigChainState) error { 2508 if upgrade.TLF != nil { 2509 newState.inner.TlfIDs = append(newState.inner.TlfIDs, upgrade.TLF.ID) 2510 } 2511 if upgrade.Keyset != nil { 2512 if newState.inner.TlfLegacyUpgrade == nil { 2513 // If an old client cached this as nil, then just make a new map here for this link 2514 newState.inner.TlfLegacyUpgrade = 2515 make(map[keybase1.TeamApplication]keybase1.TeamLegacyTLFUpgradeChainInfo) 2516 } 2517 newState.inner.TlfLegacyUpgrade[upgrade.Keyset.AppType] = keybase1.TeamLegacyTLFUpgradeChainInfo{ 2518 KeysetHash: upgrade.Keyset.KeysetHash, 2519 TeamGeneration: upgrade.Keyset.TeamGeneration, 2520 LegacyGeneration: upgrade.Keyset.LegacyGeneration, 2521 AppType: upgrade.Keyset.AppType, 2522 } 2523 } 2524 return nil 2525} 2526 2527type Tristate int 2528 2529const ( 2530 TristateDisallow Tristate = 0 // default 2531 TristateRequire Tristate = 1 2532 TristateOptional Tristate = 2 2533) 2534 2535// LinkRules describes what fields and properties are required for a link type. 2536// Default values are the strictest. 2537// Keep this in sync with `func enforce`. 2538type LinkRules struct { 2539 // Sections 2540 Name Tristate 2541 Members Tristate 2542 Parent Tristate 2543 Subteam Tristate 2544 PerTeamKey Tristate 2545 Admin Tristate 2546 Invites Tristate 2547 CompletedInvites Tristate 2548 Settings Tristate 2549 KBFS Tristate 2550 BoxSummaryHash Tristate 2551 BotSettings Tristate 2552 2553 AllowInImplicitTeam bool // whether this link is allowed in implicit team chains 2554 AllowInflate bool // whether this link is allowed to be filled later 2555 FirstInChain bool // whether this link must be the beginning of the chain 2556} 2557