github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/teams/member_set.go (about)

     1  package teams
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/keybase/client/go/libkb"
     7  	"github.com/keybase/client/go/protocol/keybase1"
     8  	"golang.org/x/net/context"
     9  )
    10  
    11  type storeMemberKind int
    12  
    13  const (
    14  	storeMemberKindNone = iota
    15  	storeMemberKindRecipient
    16  	storeMemberKindRestrictedBotRecipient
    17  )
    18  
    19  type member struct {
    20  	version    keybase1.UserVersion
    21  	perUserKey keybase1.PerUserKey
    22  }
    23  
    24  type MemberMap map[keybase1.UserVersion]keybase1.PerUserKey
    25  
    26  type memberSet struct {
    27  	Owners         []member
    28  	Admins         []member
    29  	Writers        []member
    30  	Readers        []member
    31  	Bots           []member
    32  	RestrictedBots []member
    33  	None           []member
    34  
    35  	// the per-user-keys of everyone in the lists above
    36  	recipients              MemberMap
    37  	restrictedBotRecipients MemberMap
    38  	restrictedBotSettings   map[keybase1.UserVersion]keybase1.TeamBotSettings
    39  }
    40  
    41  func newMemberSet() *memberSet {
    42  	return &memberSet{
    43  		recipients:              make(MemberMap),
    44  		restrictedBotRecipients: make(MemberMap),
    45  		restrictedBotSettings:   make(map[keybase1.UserVersion]keybase1.TeamBotSettings),
    46  	}
    47  }
    48  
    49  func (m MemberMap) Eq(n MemberMap) bool {
    50  	if m == nil && n == nil {
    51  		return true
    52  	}
    53  	if m == nil || n == nil {
    54  		return false
    55  	}
    56  	if len(m) != len(n) {
    57  		return false
    58  	}
    59  	for k, v := range m {
    60  		if n[k] != v {
    61  			return false
    62  		}
    63  	}
    64  	return true
    65  }
    66  
    67  func newMemberSetChange(ctx context.Context, g *libkb.GlobalContext, req keybase1.TeamChangeReq) (*memberSet, error) {
    68  	set := newMemberSet()
    69  	if err := set.loadMembers(ctx, g, req, true /* forcePoll*/); err != nil {
    70  		return nil, err
    71  	}
    72  	for uv, settings := range req.RestrictedBots {
    73  		set.restrictedBotSettings[uv] = settings
    74  	}
    75  	return set, nil
    76  }
    77  
    78  func (m *memberSet) recipientUids() []keybase1.UID {
    79  	uids := make([]keybase1.UID, 0, len(m.recipients))
    80  	for uv := range m.recipients {
    81  		uids = append(uids, uv.Uid)
    82  	}
    83  	return uids
    84  }
    85  
    86  func (m *memberSet) restrictedBotRecipientUids() []keybase1.UID {
    87  	uids := make([]keybase1.UID, 0, len(m.restrictedBotRecipients))
    88  	for uv := range m.restrictedBotRecipients {
    89  		uids = append(uids, uv.Uid)
    90  	}
    91  	return uids
    92  }
    93  
    94  func (m *memberSet) appendMemberSet(other *memberSet) {
    95  	m.Owners = append(m.Owners, other.Owners...)
    96  	m.Admins = append(m.Admins, other.Admins...)
    97  	m.Writers = append(m.Writers, other.Writers...)
    98  	m.Readers = append(m.Readers, other.Readers...)
    99  	m.Bots = append(m.Bots, other.Bots...)
   100  	m.RestrictedBots = append(m.RestrictedBots, other.RestrictedBots...)
   101  	m.None = append(m.None, other.None...)
   102  
   103  	for k, v := range other.recipients {
   104  		m.recipients[k] = v
   105  	}
   106  	for k, v := range other.restrictedBotRecipients {
   107  		m.restrictedBotRecipients[k] = v
   108  	}
   109  	for k, v := range other.restrictedBotSettings {
   110  		m.restrictedBotSettings[k] = v
   111  	}
   112  }
   113  
   114  func (m *memberSet) nonAdmins() []member {
   115  	var ret []member
   116  	ret = append(ret, m.RestrictedBots...)
   117  	ret = append(ret, m.Bots...)
   118  	ret = append(ret, m.Readers...)
   119  	ret = append(ret, m.Writers...)
   120  	return ret
   121  }
   122  
   123  func (m *memberSet) adminAndOwnerRecipients() MemberMap {
   124  	ret := MemberMap{}
   125  	for _, owner := range m.Owners {
   126  		ret[owner.version] = owner.perUserKey
   127  	}
   128  	for _, admin := range m.Admins {
   129  		ret[admin.version] = admin.perUserKey
   130  	}
   131  	return ret
   132  }
   133  
   134  func (m *memberSet) loadMembers(ctx context.Context, g *libkb.GlobalContext, req keybase1.TeamChangeReq, forcePoll bool) error {
   135  	var err error
   136  	m.Owners, err = m.loadGroup(ctx, g, req.Owners, storeMemberKindRecipient, forcePoll)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	m.Admins, err = m.loadGroup(ctx, g, req.Admins, storeMemberKindRecipient, forcePoll)
   141  	if err != nil {
   142  		return err
   143  	}
   144  	m.Writers, err = m.loadGroup(ctx, g, req.Writers, storeMemberKindRecipient, forcePoll)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	m.Readers, err = m.loadGroup(ctx, g, req.Readers, storeMemberKindRecipient, forcePoll)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	// regular bots do get the PTK, store them as a regular recipient
   153  	m.Bots, err = m.loadGroup(ctx, g, req.Bots, storeMemberKindRecipient, forcePoll)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	// restricted bots are not recipients of of the PTK
   158  	m.RestrictedBots, err = m.loadGroup(ctx, g, req.RestrictedBotUVs(), storeMemberKindRestrictedBotRecipient, forcePoll)
   159  	if err != nil {
   160  		return err
   161  	}
   162  	m.None, err = m.loadGroup(ctx, g, req.None, storeMemberKindNone, false)
   163  	return err
   164  }
   165  
   166  func (m *memberSet) loadGroup(ctx context.Context, g *libkb.GlobalContext,
   167  	group []keybase1.UserVersion, storeMemberKind storeMemberKind, forcePoll bool) ([]member, error) {
   168  
   169  	var members []member
   170  	for _, uv := range group {
   171  		mem, err := m.addMember(ctx, g, uv, storeMemberKind, forcePoll)
   172  		if _, reset := err.(libkb.AccountResetError); reset {
   173  			switch storeMemberKind {
   174  			case storeMemberKindNone:
   175  				// If caller doesn't care about keys, it probably expects
   176  				// reset users to be passed through as well. This is used
   177  				// in reading reset users in impteams.
   178  				members = append(members, member{version: uv})
   179  			default:
   180  				g.Log.CDebugf(ctx, "Skipping reset account %s in team load", uv.String())
   181  			}
   182  			continue
   183  		}
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  		members = append(members, mem)
   188  	}
   189  	return members, nil
   190  }
   191  
   192  func loadUPAK2(ctx context.Context, g *libkb.GlobalContext, uid keybase1.UID, forcePoll bool) (ret *keybase1.UserPlusKeysV2AllIncarnations, err error) {
   193  	defer g.CTrace(ctx, fmt.Sprintf("loadUPAK2(%s)", uid), &err)()
   194  
   195  	arg := libkb.NewLoadUserArg(g).WithNetContext(ctx).WithUID(uid).WithPublicKeyOptional()
   196  	if forcePoll {
   197  		arg = arg.WithForcePoll(true)
   198  	}
   199  	upak, _, err := g.GetUPAKLoader().LoadV2(arg)
   200  	return upak, err
   201  }
   202  
   203  func parseSocialAssertion(m libkb.MetaContext, username string) (typ string, name string, err error) {
   204  	assertion, err := libkb.ParseAssertionURL(m.G().MakeAssertionContext(m), username, false)
   205  	if err != nil {
   206  		return "", "", err
   207  	}
   208  	if assertion.IsKeybase() {
   209  		return "", "", fmt.Errorf("invalid user assertion %q, keybase assertion should be handled earlier", username)
   210  	}
   211  	typ, name = assertion.ToKeyValuePair()
   212  
   213  	return typ, name, nil
   214  }
   215  
   216  func memberFromUPAK(ctx context.Context, requestedUV keybase1.UserVersion, upak *keybase1.UserPlusKeysV2AllIncarnations) (member, error) {
   217  	if upak == nil {
   218  		return member{}, fmt.Errorf("got nil upak")
   219  	}
   220  	if upak.Current.EldestSeqno != requestedUV.EldestSeqno {
   221  		return member{}, libkb.NewAccountResetError(requestedUV, upak.Current.EldestSeqno)
   222  	}
   223  	key := upak.Current.GetLatestPerUserKey()
   224  	if key == nil || key.Gen <= 0 {
   225  		return member{}, fmt.Errorf("user has no per-user key: %s", requestedUV.String())
   226  	}
   227  	return member{
   228  		version:    NewUserVersion(upak.Current.Uid, upak.Current.EldestSeqno),
   229  		perUserKey: *key,
   230  	}, nil
   231  }
   232  
   233  func loadMember(ctx context.Context, g *libkb.GlobalContext, uv keybase1.UserVersion, forcePoll bool) (mem member, err error) {
   234  	defer g.CTrace(ctx, fmt.Sprintf("loadMember(%s, forcePoll=%t)", uv, forcePoll), &err)()
   235  
   236  	upak, err := loadUPAK2(ctx, g, uv.Uid, forcePoll)
   237  	if err != nil {
   238  		if _, reset := err.(libkb.NoKeyError); reset {
   239  			err = libkb.NewAccountResetError(uv, keybase1.Seqno(0))
   240  		}
   241  		return mem, err
   242  	}
   243  
   244  	mem, err = memberFromUPAK(ctx, uv, upak)
   245  	if err != nil {
   246  		return mem, err
   247  	}
   248  
   249  	return mem, nil
   250  }
   251  
   252  func (m *memberSet) addMember(ctx context.Context, g *libkb.GlobalContext, uv keybase1.UserVersion, storeMemberKind storeMemberKind, forcePoll bool) (mem member, err error) {
   253  	mem, err = loadMember(ctx, g, uv, forcePoll)
   254  	if err != nil {
   255  		return mem, err
   256  	}
   257  	m.storeMember(ctx, g, mem, storeMemberKind)
   258  	return mem, nil
   259  }
   260  
   261  func (m *memberSet) storeMember(ctx context.Context, g *libkb.GlobalContext, mem member, storeMemberKind storeMemberKind) {
   262  	switch storeMemberKind {
   263  	case storeMemberKindRecipient:
   264  		m.recipients[mem.version] = mem.perUserKey
   265  	case storeMemberKindRestrictedBotRecipient:
   266  		m.restrictedBotRecipients[mem.version] = mem.perUserKey
   267  	}
   268  }
   269  
   270  type MemberChecker interface {
   271  	IsMember(context.Context, keybase1.UserVersion) bool
   272  	MemberRole(context.Context, keybase1.UserVersion) (keybase1.TeamRole, error)
   273  }
   274  
   275  func (m *memberSet) removeExistingMembers(ctx context.Context, checker MemberChecker) {
   276  	for uv := range m.recipients {
   277  		if checker.IsMember(ctx, uv) {
   278  			existingRole, err := checker.MemberRole(ctx, uv)
   279  			// If we were previously a RESTRICTEDBOT, we now need to be boxed
   280  			// for the PTK so we skip removal.
   281  			if err == nil && existingRole.IsRestrictedBot() {
   282  				continue
   283  			}
   284  			delete(m.recipients, uv)
   285  		}
   286  	}
   287  	for uv := range m.restrictedBotRecipients {
   288  		if checker.IsMember(ctx, uv) {
   289  			delete(m.restrictedBotRecipients, uv)
   290  			delete(m.restrictedBotSettings, uv)
   291  		}
   292  	}
   293  }
   294  
   295  // AddRemainingRecipients adds everyone in existing to m.recipients or
   296  // m.restrictedBotRecipients that isn't in m.None.
   297  func (m *memberSet) AddRemainingRecipients(ctx context.Context, g *libkb.GlobalContext, existing keybase1.TeamMembers) (err error) {
   298  
   299  	defer g.CTrace(ctx, "memberSet#AddRemainingRecipients", &err)()
   300  
   301  	// make a map of the None members
   302  	filtered := make(map[keybase1.UserVersion]bool)
   303  	for _, n := range m.None {
   304  		filtered[n.version] = true
   305  	}
   306  
   307  	existingRestrictedBots := make(map[keybase1.UserVersion]bool)
   308  	for _, uv := range existing.RestrictedBots {
   309  		existingRestrictedBots[uv] = true
   310  	}
   311  
   312  	auv := existing.AllUserVersions()
   313  	forceUserPoll := true
   314  	if len(auv) > 50 {
   315  		forceUserPoll = false
   316  	}
   317  
   318  	type request struct {
   319  		uv              keybase1.UserVersion
   320  		storeMemberKind storeMemberKind
   321  	}
   322  	var requests []request
   323  	for _, uv := range auv {
   324  		if filtered[uv] {
   325  			continue
   326  		}
   327  		if _, ok := m.recipients[uv]; ok {
   328  			continue
   329  		}
   330  		if _, ok := m.restrictedBotRecipients[uv]; ok {
   331  			continue
   332  		}
   333  
   334  		var storeMemberKind storeMemberKind
   335  		if _, ok := existingRestrictedBots[uv]; ok {
   336  			storeMemberKind = storeMemberKindRestrictedBotRecipient
   337  		} else {
   338  			storeMemberKind = storeMemberKindRecipient
   339  		}
   340  
   341  		requests = append(requests, request{
   342  			uv:              uv,
   343  			storeMemberKind: storeMemberKind,
   344  		})
   345  	}
   346  
   347  	// for UPAK Batcher API
   348  	getArg := func(idx int) *libkb.LoadUserArg {
   349  		if idx >= len(requests) {
   350  			return nil
   351  		}
   352  		// Because we pass WithPublicKeyOptional, users who have reset do not cause an error when loading the UPAK, but are ignored later
   353  		// in processResult.
   354  		arg := libkb.NewLoadUserByUIDArg(ctx, g, requests[idx].uv.Uid).WithPublicKeyOptional().WithForcePoll(forceUserPoll)
   355  		return &arg
   356  	}
   357  	// for UPAK Batcher API
   358  	processResult := func(idx int, upak *keybase1.UserPlusKeysV2AllIncarnations) error {
   359  		mem, err := memberFromUPAK(ctx, requests[idx].uv, upak)
   360  		switch err.(type) {
   361  		case nil:
   362  		case libkb.AccountResetError:
   363  			return nil
   364  		default:
   365  			return err
   366  		}
   367  		m.storeMember(ctx, g, mem, requests[idx].storeMemberKind)
   368  		return nil
   369  	}
   370  	err = g.GetUPAKLoader().Batcher(ctx, getArg, processResult, 0)
   371  	if err != nil {
   372  		return err
   373  	}
   374  
   375  	return nil
   376  }
   377  
   378  func (m *memberSet) nameSeqList(members []member) (*[]SCTeamMember, error) {
   379  	if len(members) == 0 {
   380  		return nil, nil
   381  	}
   382  	res := make([]SCTeamMember, len(members))
   383  	for i, m := range members {
   384  		res[i] = SCTeamMember(m.version)
   385  	}
   386  	return &res, nil
   387  }
   388  
   389  // can return nil
   390  func (m *memberSet) Section() (res *SCTeamMembers, err error) {
   391  	if m.empty() {
   392  		return nil, nil
   393  	}
   394  
   395  	res = &SCTeamMembers{}
   396  	res.Owners, err = m.nameSeqList(m.Owners)
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  	res.Admins, err = m.nameSeqList(m.Admins)
   401  	if err != nil {
   402  		return nil, err
   403  	}
   404  	res.Writers, err = m.nameSeqList(m.Writers)
   405  	if err != nil {
   406  		return nil, err
   407  	}
   408  	res.Readers, err = m.nameSeqList(m.Readers)
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  	res.Bots, err = m.nameSeqList(m.Bots)
   413  	if err != nil {
   414  		return nil, err
   415  	}
   416  	res.RestrictedBots, err = m.nameSeqList(m.RestrictedBots)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	res.None, err = m.nameSeqList(m.None)
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  	return res, nil
   425  }
   426  
   427  func (m *memberSet) HasRemoval() bool {
   428  	return len(m.None) > 0
   429  }
   430  
   431  func (m *memberSet) HasAdditions() bool {
   432  	return (len(m.Owners) + len(m.Admins) + len(m.Writers) + len(m.Readers) + len(m.Bots) + len(m.RestrictedBots)) > 0
   433  }
   434  
   435  func (m *memberSet) empty() bool {
   436  	return len(m.Owners) == 0 && len(m.Admins) == 0 && len(m.Writers) == 0 && len(m.Readers) == 0 && len(m.Bots) == 0 && len(m.RestrictedBots) == 0 && len(m.None) == 0
   437  }