github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/teams/chain_parse.go (about) 1 package teams 2 3 import ( 4 "encoding/hex" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "regexp" 9 10 "github.com/keybase/client/go/libkb" 11 "github.com/keybase/client/go/protocol/chat1" 12 "github.com/keybase/client/go/protocol/keybase1" 13 "github.com/keybase/client/go/teams/hidden" 14 ) 15 16 type SCTeamName string 17 type SCTeamID string 18 type SCTeamInviteID string 19 type SCTeamInviteIDShort string 20 type SCTeamBoxSummaryHash string 21 22 // SCTeamEntropy is used to render stubbed out links unguessable. 23 // Basically, we shove a random 18-byte string into sensitive links. 24 type SCTeamEntropy string 25 26 func (s SCTeamID) ToTeamID() (keybase1.TeamID, error) { return keybase1.TeamIDFromString(string(s)) } 27 28 // An (uid%eldest_seqno) pair. 29 // The uid is adorned with "%n" at the end where n is the eldest seqno. 30 // Just UID is fine as well (implicit %1), but marshaling will always add %1. 31 type SCTeamMember keybase1.UserVersion 32 33 type SCMapInviteIDToUV map[keybase1.TeamInviteID]keybase1.UserVersionPercentForm 34 type SCMapInviteIDUVPair struct { 35 InviteID SCTeamInviteID `json:"id"` 36 UV keybase1.UserVersionPercentForm `json:"uv"` 37 } 38 39 type SCTeamSection struct { 40 ID SCTeamID `json:"id"` 41 Name *SCTeamName `json:"name,omitempty"` 42 Members *SCTeamMembers `json:"members,omitempty"` 43 Parent *SCTeamParent `json:"parent,omitempty"` 44 Subteam *SCSubteam `json:"subteam,omitempty"` 45 PerTeamKey *SCPerTeamKey `json:"per_team_key,omitempty"` 46 Admin *SCTeamAdmin `json:"admin,omitempty"` 47 Invites *SCTeamInvites `json:"invites,omitempty"` 48 CompletedInvites SCMapInviteIDToUV `json:"completed_invites,omitempty"` 49 UsedInvites []SCMapInviteIDUVPair `json:"used_invites,omitempty"` 50 Implicit bool `json:"is_implicit,omitempty"` 51 Public bool `json:"is_public,omitempty"` 52 Entropy SCTeamEntropy `json:"entropy,omitempty"` 53 Settings *SCTeamSettings `json:"settings,omitempty"` 54 KBFS *SCTeamKBFS `json:"kbfs,omitempty"` 55 BoxSummaryHash *SCTeamBoxSummaryHash `json:"box_summary_hash,omitempty"` 56 Ratchets []hidden.SCTeamRatchet `json:"ratchets,omitempty"` 57 BotSettings *[]SCTeamBot `json:"bot_settings,omitempty"` 58 } 59 60 type SCTeamMembers struct { 61 Owners *[]SCTeamMember `json:"owner,omitempty"` 62 Admins *[]SCTeamMember `json:"admin,omitempty"` 63 Writers *[]SCTeamMember `json:"writer,omitempty"` 64 Readers *[]SCTeamMember `json:"reader,omitempty"` 65 Bots *[]SCTeamMember `json:"bot,omitempty"` 66 RestrictedBots *[]SCTeamMember `json:"restricted_bot,omitempty"` 67 None *[]SCTeamMember `json:"none,omitempty"` 68 } 69 70 type SCTeamInvites struct { 71 Owners *[]SCTeamInvite `json:"owner,omitempty"` 72 Admins *[]SCTeamInvite `json:"admin,omitempty"` 73 Writers *[]SCTeamInvite `json:"writer,omitempty"` 74 Readers *[]SCTeamInvite `json:"reader,omitempty"` 75 Cancel *[]SCTeamInviteID `json:"cancel,omitempty"` 76 } 77 78 type SCTeamInvite struct { 79 Type string `json:"type"` 80 Name keybase1.TeamInviteName `json:"name"` 81 ID SCTeamInviteID `json:"id"` 82 Etime *keybase1.UnixTime `json:"etime,omitempty"` // UnixTime 83 MaxUses *keybase1.TeamInviteMaxUses `json:"max_uses,omitempty"` 84 } 85 86 type SCTeamParent struct { 87 ID SCTeamID `json:"id"` 88 Seqno keybase1.Seqno `json:"seqno"` 89 SeqType keybase1.SeqType `json:"seq_type"` 90 } 91 92 type SCSubteam struct { 93 ID SCTeamID `json:"id"` 94 Name SCTeamName `json:"name"` 95 } 96 97 type SCTeamAdmin struct { 98 TeamID SCTeamID `json:"team_id"` 99 Seqno keybase1.Seqno `json:"seqno"` 100 SeqType keybase1.SeqType `json:"seq_type"` 101 } 102 103 type SCPerTeamKey struct { 104 Generation keybase1.PerTeamKeyGeneration `json:"generation"` 105 EncKID keybase1.KID `json:"encryption_kid"` 106 SigKID keybase1.KID `json:"signing_kid"` 107 ReverseSig string `json:"reverse_sig"` 108 SeedCheck string `json:"seed_check,omitempty"` 109 } 110 111 type SCTeamSettings struct { 112 Open *SCTeamSettingsOpen `json:"open,omitempty"` 113 } 114 115 type SCTeamSettingsOpenOptions struct { 116 JoinAs string `json:"join_as"` 117 } 118 119 type SCTeamSettingsOpen struct { 120 Enabled bool `json:"enabled"` 121 Options *SCTeamSettingsOpenOptions `json:"options,omitempty"` 122 } 123 124 type SCTeamKBFS struct { 125 TLF *SCTeamKBFSTLF `json:"tlf,omitempty"` 126 Keyset *SCTeamKBFSLegacyUpgrade `json:"legacy_tlf_upgrade,omitempty"` 127 } 128 129 type SCTeamKBFSTLF struct { 130 ID keybase1.TLFID `json:"id"` 131 } 132 133 type SCTeamKBFSLegacyUpgrade struct { 134 AppType keybase1.TeamApplication `json:"app_type"` 135 TeamGeneration keybase1.PerTeamKeyGeneration `json:"team_generation"` 136 LegacyGeneration int `json:"legacy_generation"` 137 KeysetHash keybase1.TeamEncryptedKBFSKeysetHash `json:"encrypted_keyset_hash"` 138 } 139 140 type SCTeamBotUV struct { 141 UID keybase1.UID `json:"uid"` 142 EldestSeqno keybase1.Seqno `json:"eldest_seqno"` 143 } 144 145 type SCTeamBot struct { 146 Bot SCTeamBotUV `json:"bot"` 147 // Should the bot be summoned for !-commands 148 Cmds bool `json:"cmds"` 149 // Should the bot be summoned for @-mentions 150 Mentions bool `json:"mentions"` 151 // Phrases that should trigger the bot to be keyed for content. Will be 152 // check as a valid regex. 153 Triggers *[]string `json:"triggers,omitempty"` 154 // Conversations the bot can participate in, `nil` indicates all 155 Convs *[]string `json:"convs,omitempty"` 156 } 157 158 func ToSCTeamBotUV(uv keybase1.UserVersion) SCTeamBotUV { 159 return SCTeamBotUV{ 160 UID: uv.Uid, 161 EldestSeqno: uv.EldestSeqno, 162 } 163 } 164 165 func (u SCTeamBotUV) ToUserVersion() keybase1.UserVersion { 166 return keybase1.UserVersion{ 167 Uid: u.UID, 168 EldestSeqno: u.EldestSeqno, 169 } 170 } 171 172 // Len returns total count of all created invites and all canceled invites. 173 func (i SCTeamInvites) Len() int { 174 size := 0 175 // ヾ( []*[])ノ 176 for _, ptr := range []*[]SCTeamInvite{i.Owners, i.Admins, i.Writers, i.Readers} { 177 if ptr != nil { 178 size += len(*ptr) 179 } 180 } 181 if i.Cancel != nil { 182 size += len(*i.Cancel) 183 } 184 return size 185 } 186 187 // NewInviteCount returns count of all created invites. 188 func (i SCTeamInvites) NewInviteCount() int { 189 size := 0 190 for _, ptr := range []*[]SCTeamInvite{i.Owners, i.Admins, i.Writers, i.Readers} { 191 if ptr != nil { 192 size += len(*ptr) 193 } 194 } 195 return size 196 } 197 198 // CanceledInviteCount returns count of canceled invites. 199 func (i SCTeamInvites) CanceledInviteCount() int { 200 if i.Cancel != nil { 201 return len(*i.Cancel) 202 } 203 return 0 204 } 205 206 // HasNewInvites returns true if SCTeamInvites creates any invites. 207 func (i SCTeamInvites) HasNewInvites() bool { 208 for _, ptr := range []*[]SCTeamInvite{i.Owners, i.Admins, i.Writers, i.Readers} { 209 if ptr != nil && len(*ptr) > 0 { 210 return true 211 } 212 } 213 return false 214 } 215 216 func (a SCTeamAdmin) SigChainLocation() keybase1.SigChainLocation { 217 return keybase1.SigChainLocation{ 218 Seqno: a.Seqno, 219 SeqType: a.SeqType, 220 } 221 } 222 223 func (s *SCTeamMember) UnmarshalJSON(b []byte) (err error) { 224 uv, err := keybase1.ParseUserVersion(keybase1.UserVersionPercentForm(keybase1.Unquote(b))) 225 if err != nil { 226 return err 227 } 228 *s = SCTeamMember(uv) 229 return nil 230 } 231 232 func (s *SCTeamMember) MarshalJSON() (b []byte, err error) { 233 return keybase1.Quote(keybase1.UserVersion(*s).PercentForm().String()), nil 234 } 235 236 func makeSCMapInviteIDUVMap(pairs []keybase1.TeamUsedInvite) (ret []SCMapInviteIDUVPair) { 237 if len(pairs) > 0 { 238 ret = make([]SCMapInviteIDUVPair, len(pairs)) 239 for i, v := range pairs { 240 ret[i] = SCMapInviteIDUVPair{ 241 InviteID: SCTeamInviteID(v.InviteID), 242 UV: v.Uv, 243 } 244 } 245 } 246 247 return ret 248 } 249 250 // Non-team-specific stuff below the line 251 // ------------------------- 252 253 type SCChainLink struct { 254 Seqno keybase1.Seqno `json:"seqno"` 255 Sig string `json:"sig"` 256 SigV2Payload string `json:"s2"` 257 Payload string `json:"payload_json"` // String containing json of a SCChainLinkPayload 258 UID keybase1.UID `json:"uid"` // UID of the signer 259 EldestSeqno keybase1.Seqno `json:"eldest_seqno"` // Eldest seqn of the signer 260 Version libkb.SigVersion `json:"version"` 261 } 262 263 func (link *SCChainLink) UnmarshalPayload() (res SCChainLinkPayload, err error) { 264 if len(link.Payload) == 0 { 265 return res, errors.New("empty payload") 266 } 267 err = json.Unmarshal([]byte(link.Payload), &res) 268 return res, err 269 } 270 271 type SCChainLinkPayload struct { 272 Body SCPayloadBody `json:"body,omitempty"` 273 Ctime int `json:"ctime,omitempty"` // UnixTime 274 ExpireIn int `json:"expire_in,omitempty"` 275 Prev *string `json:"prev,omitempty"` 276 SeqType keybase1.SeqType `json:"seq_type,omitempty"` 277 Seqno keybase1.Seqno `json:"seqno,omitempty"` 278 Tag string `json:"tag,omitempty"` 279 IgnoreIfUnsupported libkb.SigIgnoreIfUnsupported `json:"ignore_if_unsupported,omitempty"` 280 } 281 282 func (s SCChainLinkPayload) SigChainLocation() keybase1.SigChainLocation { 283 return keybase1.SigChainLocation{ 284 Seqno: s.Seqno, 285 SeqType: s.SeqType, 286 } 287 } 288 289 type SCMerkleRootSection struct { 290 Ctime int `json:"ctime"` // UnixTime 291 Seqno keybase1.Seqno `json:"seqno"` 292 HashMeta keybase1.HashMeta `json:"hash_meta"` 293 } 294 295 func (sr SCMerkleRootSection) ToMerkleRootV2() keybase1.MerkleRootV2 { 296 return keybase1.MerkleRootV2{ 297 Seqno: sr.Seqno, 298 HashMeta: sr.HashMeta, 299 } 300 } 301 302 type SCPayloadBody struct { 303 Key *SCKeySection `json:"key,omitempty"` 304 Type string `json:"type,omitempty"` 305 MerkleRoot SCMerkleRootSection `json:"merkle_root"` 306 Version libkb.SigVersion `json:"version"` 307 308 Team *SCTeamSection `json:"team,omitempty"` 309 } 310 311 type SCKeySection struct { 312 KID keybase1.KID `json:"kid"` 313 UID keybase1.UID `json:"uid"` 314 Username string `json:"username,omitempty"` 315 EldestKID keybase1.KID `json:"eldest_kid"` 316 Host string `json:"host,omitempty"` 317 } 318 319 // Parse a chain link from a string. Just parses, does not validate. 320 func ParseTeamChainLink(link string) (res SCChainLink, err error) { 321 err = json.Unmarshal([]byte(link), &res) 322 return res, err 323 } 324 325 func (s SCChainLinkPayload) TeamAdmin() *SCTeamAdmin { 326 t := s.Body.Team 327 if t == nil { 328 return nil 329 } 330 return t.Admin 331 } 332 333 func (s SCChainLinkPayload) Ratchets() []hidden.SCTeamRatchet { 334 t := s.Body.Team 335 if t == nil { 336 return nil 337 } 338 return t.Ratchets 339 } 340 341 func (s SCChainLinkPayload) TeamID() (keybase1.TeamID, error) { 342 t := s.Body.Team 343 if t == nil { 344 return keybase1.TeamID(""), errors.New("no team section") 345 } 346 return t.ID.ToTeamID() 347 } 348 349 func (i SCTeamInviteID) TeamInviteID() (keybase1.TeamInviteID, error) { 350 return keybase1.TeamInviteIDFromString(string(i)) 351 } 352 353 func (i SCTeamInviteID) Eq(i2 keybase1.TeamInviteID) bool { 354 tmp, err := i.TeamInviteID() 355 if err != nil { 356 return false 357 } 358 return tmp.Eq(i2) 359 } 360 361 func (i SCTeamInviteID) ToShortInviteID() (SCTeamInviteIDShort, error) { 362 decoded, err := hex.DecodeString(string(i)) 363 if err != nil { 364 return "", err 365 } 366 return SCTeamInviteIDShort(libkb.Base30.EncodeToString(decoded)), nil 367 } 368 369 func (i SCTeamInviteIDShort) ToInviteID() (SCTeamInviteID, error) { 370 decoded, err := libkb.Base30.DecodeString(string(i)) 371 if err != nil { 372 return "", err 373 } 374 return SCTeamInviteID(hex.EncodeToString(decoded)), nil 375 } 376 377 func (i SCTeamInvite) TeamInvite(mctx libkb.MetaContext, r keybase1.TeamRole, inviter keybase1.UserVersion) (keybase1.TeamInvite, error) { 378 id, err := i.ID.TeamInviteID() 379 if err != nil { 380 return keybase1.TeamInvite{}, err 381 } 382 typ, err := TeamInviteTypeFromString(mctx, i.Type) 383 if err != nil { 384 return keybase1.TeamInvite{}, err 385 } 386 return keybase1.TeamInvite{ 387 Id: id, 388 Role: r, 389 Type: typ, 390 Name: i.Name, 391 Inviter: inviter, 392 MaxUses: i.MaxUses, 393 Etime: i.Etime, 394 }, nil 395 } 396 397 func CreateTeamSettings(open bool, joinAs keybase1.TeamRole) (SCTeamSettings, error) { 398 if !open { 399 return SCTeamSettings{ 400 Open: &SCTeamSettingsOpen{ 401 Enabled: false, 402 }, 403 }, nil 404 } 405 406 var roleStr string 407 switch joinAs { 408 case keybase1.TeamRole_READER: 409 roleStr = "reader" 410 case keybase1.TeamRole_WRITER: 411 roleStr = "writer" 412 default: 413 return SCTeamSettings{}, fmt.Errorf("%v is not a valid joinAs role for open team", joinAs) 414 } 415 416 return SCTeamSettings{ 417 Open: &SCTeamSettingsOpen{ 418 Enabled: true, 419 Options: &SCTeamSettingsOpenOptions{ 420 JoinAs: roleStr, 421 }, 422 }, 423 }, nil 424 } 425 426 func CreateTeamBotSettings(bots map[keybase1.UserVersion]keybase1.TeamBotSettings) ([]SCTeamBot, error) { 427 var res []SCTeamBot 428 for bot, botSettings := range bots { 429 // Sanity check the triggers are valid 430 for _, trigger := range botSettings.Triggers { 431 if _, err := regexp.Compile(trigger); err != nil { 432 return nil, err 433 } 434 } 435 // Sanity check the conversation IDs are well formed 436 for _, convID := range botSettings.Convs { 437 if _, err := chat1.MakeConvID(convID); err != nil { 438 return nil, err 439 } 440 } 441 var convs, triggers *[]string 442 if len(botSettings.Triggers) > 0 { 443 triggers = &(botSettings.Triggers) 444 } 445 if len(botSettings.Convs) > 0 { 446 convs = &(botSettings.Convs) 447 } 448 res = append(res, SCTeamBot{ 449 Bot: ToSCTeamBotUV(bot), 450 Cmds: botSettings.Cmds, 451 Mentions: botSettings.Mentions, 452 Triggers: triggers, 453 Convs: convs, 454 }) 455 } 456 return res, nil 457 } 458 459 func (n SCTeamName) LastPart() (string, error) { 460 x, err := keybase1.TeamNameFromString(string(n)) 461 if err != nil { 462 return "", err 463 } 464 return string(x.LastPart()), nil 465 } 466 467 func (h SCTeamBoxSummaryHash) BoxSummaryHash() keybase1.BoxSummaryHash { 468 return keybase1.BoxSummaryHash(string(h)) 469 }