github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 }