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