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  }