@namespace("keybase.1") protocol teams { enum TeamRole { NONE_0, READER_1, WRITER_2, ADMIN_3, OWNER_4, BOT_5, RESTRICTEDBOT_6 } // Note: If you add to this list, take heed. // The client will try to assert RKM coverage or the new application. // First update the server and run add_new_team_app. enum TeamApplication { KBFS_1, CHAT_2, SALTPACK_3, GIT_METADATA_4, SEITAN_INVITE_TOKEN_5, STELLAR_RELAY_6, KVSTORE_7 } enum TeamStatus { NONE_0, LIVE_1, DELETED_2, ABANDONED_3 } enum AuditMode { STANDARD_0, JUST_CREATED_1, SKIP_2, // If we do not want to audit the hidden chain // (for example, when the client does not have access to it). STANDARD_NO_HIDDEN_3 } // PerTeamKeyGeneration describes the generation of the secret. // The sequence starts at 1. @typedef("int") record PerTeamKeyGeneration {} // In the future, there might be bot and admin keys enum PTKType { READER_0 } // We might eventually change the version of seedchecks that we are using. // Old clients will break on new data, but at least they can fail gracefully // rather than failing a check and thinking malice. enum PerTeamSeedCheckVersion { V1_1 } // Version1 of PerTeamSeedCheck is computed as follows: // // Let g(key,data) = HMAC-SHA512(key,data)[0:32] // f(1) = g(s_1, "Kebase-Derived-Team-Seedcheck-1\x00" + teamID) // f(2) = g(s_2, f(1)) // ... // f(i) = g(s_i, f(i-1)) // record PerTeamSeedCheck { PerTeamSeedCheckVersion version; PerTeamSeedCheckValue value; // f(i) for generation i } @typedef("bytes") record PerTeamSeedCheckValue {} @typedef("bytes") record PerTeamSeedCheckValuePostImage {} // Teamlinks that the server can see don't get PerTeamSeedCheck, since it would allow the // server to fabricate new ones. The server gets to see the postimage of these checks // after a SHA256 one-way function. record PerTeamSeedCheckPostImage { @mpackkey("h") @jsonkey("h") PerTeamSeedCheckValuePostImage value; // SHA256(f(i)) for generation i @mpackkey("v") @jsonkey("v") PerTeamSeedCheckVersion version; } record TeamApplicationKey { TeamApplication application; PerTeamKeyGeneration keyGeneration; Bytes32 key; } @typedef("bytes") record MaskB64 {} @typedef("string") record TeamInviteID {} // "max_uses" field of team invite. Value can be positive or -1, not 0. // -1 means infinite number of uses. @typedef("int") record TeamInviteMaxUses {} record ReaderKeyMask { TeamApplication application; PerTeamKeyGeneration generation; MaskB64 mask; } @lint("ignore") record PerTeamKey { PerTeamKeyGeneration gen; Seqno seqno; // 0 if in the hidden chain KID sigKID; KID encKID; } @lint("ignore") record PerTeamKeyAndCheck { PerTeamKey ptk; PerTeamSeedCheckPostImage check; } fixed PerTeamKeySeed(32); // Secret from which team keys are derived record PerTeamKeySeedItem { PerTeamKeySeed seed; PerTeamKeyGeneration generation; // This might be optional, for PTKSeeds that come from the hidden chain Seqno seqno; // This was added 2019-06, so earlier-cached teams might not have this. union { null, PerTeamSeedCheck } check; } record TeamMember { UID uid; TeamRole role; Seqno eldestSeqno; // eldest seqno of the team member record TeamMemberStatus status; // non-null for restricted bot members union { null, TeamBotSettings } botSettings; } record TeamMembers { array owners; array admins; array writers; array readers; array bots; array restrictedBots; } enum TeamMemberStatus { ACTIVE_0, RESET_1, DELETED_2 } record TeamMemberDetails { UserVersion uv; string username; FullName fullName; @lint("ignore") boolean needsPUK; TeamMemberStatus status; union { null, Time } joinTime; // last time the user joined the team } record TeamMembersDetails { array owners; array admins; array writers; array readers; array bots; array restrictedBots; } record TeamDetails { string name; TeamMembersDetails members; PerTeamKeyGeneration keyGeneration; map annotatedActiveInvites; TeamSettings settings; TeamShowcase showcase; } record TeamMemberRole { UID uid; string username; FullName fullName; TeamRole role; } record UntrustedTeamInfo { TeamName name; boolean inTeam; boolean open; string description; array publicAdmins; int numMembers; array publicMembers; } UntrustedTeamInfo getUntrustedTeamInfo(TeamName teamName); @typedef("string") record UserVersionPercentForm {} record TeamUsedInvite { TeamInviteID inviteID; UserVersionPercentForm uv; } record TeamChangeReq { array owners; array admins; array writers; array readers; array bots; map restrictedBots; array none; // If added UserVersion completes an invite, store the inviteID->UV mapping // here. map completedInvites; // If added UserVersion uses a multi-use invite, store the inviteID->UV pair // here. This only applies to multi-use invites (invites with max_uses field). // Any other invite should use completedInvites. array usedInvites; } record TeamPlusApplicationKeys { TeamID id; string name; boolean implicit; boolean public; TeamApplication application; array writers; array onlyReaders; array onlyRestrictedBots; array applicationKeys; } // Snapshot of a loaded team. // If this is changed in incompatible way, increment teams/storage.go:diskStorageVersion record TeamData { @mpackkey("v") @jsonkey("v") int subversion; // If set, all other members are invalid except for Chain.ID, Chain.Public, // Chain.LastSeqno and Chain.LastLinkID. boolean frozen; // If set, cannot be loaded ever again. All other members are invalid. boolean tombstoned; // Whether this snapshot is entirely missing secrets. // Not used. Might as well get rid of this field next time the cache is busted. boolean secretless; // Name of the team. This is tricky. // The root ancestor, final part, and the depth are always correct. // But the middle parts (b in a.b.c) do not track the sigchain. // They are kept fairly up to date by TeamLoader, but the guarantees are fuzzy. TeamName name; TeamSigChainState chain; // These secrets may not match the team chain, always verify before use. // These secrets may be behind or missing if this is a public team. // generation -> seed // generations _must_ start from 1 with no gaps @mpackkey("perTeamKeySeeds") map perTeamKeySeedsUnverified; // application -> generation -> mask // in each slot generations _must_ start from 1 with no gaps map> readerKeyMasks; // Should only be used by TeamLoader (because it is mutable, not threadsafe to read) // The latest seqno of the remote team. Given by the server out of band. // Used for caching. Seqno latestSeqnoHint; // Should only be used by TeamLoader (because it is mutable, not threadsafe to read) Time cachedAt; // Associated TLF crypt keys map> tlfCryptKeys; } // Snapshot of a fast-loaded team. record FastTeamData { // If set, all other members are invalid except for Chain.ID, Chain.Public, // and Chain.Last. boolean frozen; // If we decide to make incremental upgrades, we bump this. int subversion; // If set, cannot be loaded ever again. All other members are invalid. boolean tombstoned; // Name of the team. This is tricky. // The root ancestor, final part, and the depth are always correct. // But the middle parts (b in a.b.c) do not track the sigchain. // They are kept fairly up to date by TeamLoader, but the guarantees are fuzzy. TeamName name; // Data derived from our (limited) replay of the team chain. FastTeamSigChainState chain; // These secrets may not match the team chain, always verify before use. // These secrets may be behind or missing if this is a public team. // generation -> seed // generations _must_ start from 1 with no gaps @mpackkey("perTeamKeySeeds") map perTeamKeySeedsUnverified; // If we're counting up from 1, what's the maximum PTK generation // we get to before we hit a gap. For historical reasons, this might // be a number other than latestKeyGeneration, since we used to be happy // with situations where there were holes. Once this value is set, it shoud // be equivalent to latestKeyGeneration. @lint("ignore") PerTeamKeyGeneration maxContinuousPTKGeneration; // A rolling hash of all seeds, so that whoever rekeys needs to prove // knowledge of all keys. Entirely derivable from the above field, // but we cache it here for performance. map seedChecks; PerTeamKeyGeneration latestKeyGeneration; // application -> generation -> mask // in each slot generations _must_ start from 1 with no gaps map> readerKeyMasks; // Should only be used by TeamLoader (because it is mutable, not threadsafe to read) // The latest seqno of the remote team. Given by the server out of band. // Used for caching. Seqno latestSeqnoHint; // Should only be used by TeamLoader (because it is mutable, not threadsafe to read) Time cachedAt; // True if we've ever loaded this team for the "latest" version; sometimes // we don't, depending on the access patterns boolean loadedLatest; } enum RatchetType { // Ratchets that come as "down pointers" from the main team chean. MAIN_0, // Ratchets that come from the blinded merkle tree for this team. BLINDED_1, // When we make changes to the hidden chain ourselves, then we bump this // ratchet. SELF_2, // The server can tell us about a new hidden team chain link which was not // yet committed to the blind tree (as that is updated slowly). We trust the // server and accept it for now, but will complain later if this link does // not get added to the blind tree later within a reasonable time (or if the // tree shows a different history). UNCOMMITTED_3 } record HiddenTeamChainRatchetSet{ map ratchets; } record HiddenTeamChain { TeamID id; int subversion; boolean public; boolean frozen; boolean tombstoned; // The last link loaded to, but might have stubbed holes before then (due to FTL) Seqno last; // From 1 to lastFull are fully populated links, with all stubs (via FTL) filled in. // Might be less than last, or 0, since we didn't introduce this field until // bug Y2K-679 came in. Seqno lastFull; // Update this field when gregor notifications come in, and there is a bump // for last seqno. If this is wrong or buggy, it's a performance problem, // not a security problem. Seqno latestSeqnoHint; Seqno lastCommittedSeqno; // Used to ensure that the uncommitted updates given from the server are // committed to the blind tree within a reasonable time. map linkReceiptTimes; // For each PTK type (right now only 'reader'), say where the last seqno of // the type is in the chain. map lastPerTeamKeys; // Chain links based on outer objects. map outer; // For any link where we have it, fill in merkle sequence information, // parent chain pointer info, and also PTK information (inner data); map inner; // Maps a Reader PTK Generation to the chain seqno it showed up in; more info there as // to what the PTK is, etc. map readerPerTeamKeys; // This is the latest hidden chain tail link known for this team; // the existence of a known ratchet means the team better be at least up to this // point, and maybe beyond it. HiddenTeamChainRatchetSet ratchetSet; // Should only be used by TeamLoader (because it is mutable, not threadsafe to read) Time cachedAt; // The loader might need to pass data back to the box auditor that at the time of the // load, the hidden chain needed a rotation; this is the way todo it. boolean needRotate; // Merkle roots at each seqno map merkleRoots; } record LinkTriple { Seqno seqno; SeqType seqType; LinkID linkID; } record LinkTripleAndTime { LinkTriple triple; Time time; } record UpPointer { Seqno ourSeqno; TeamID parentID; Seqno parentSeqno; boolean deletion; } record DownPointer { TeamID id; string nameComponent; boolean isDeleted; } record Signer { Seqno e; // eldest seqno KID k; // signer KID UID u; // signer UID } record HiddenTeamChainLink { // The merkle root at the time the link was generated @jsonkey("m") @mpackkey("m") MerkleRootV2 merkleRoot; // Last known link of the main team chain @jsonkey("p") @mpackkey("p") LinkTriple parentChain; // The signer for this link @jsonkey("s") @mpackkey("s") Signer signer; // The PTK that's getting signed in. In the future we might have non-rotate // links on the hidden chain, in which the PTK map below will be empty. // But for now, there will always be data here, and always under the PTKType_READER // key. @jsonkey("k") @mpackkey("k") map ptk; } // FastTeamSigChainState is the state computed by doing a fast reply of the // team's sigchain, ignoring all but the key rotation links. record FastTeamSigChainState { @lint("ignore") TeamID ID; // Whether this is a public team. boolean public; // Name of the root ancestor. For A.B.C this is 'A'. TeamName rootAncestor; // Depth of the name. int nameDepth; union { null, LinkTriple } last; // Keyed by per-team-key generation map perTeamKeys; // Once we derive and check these seeds, we enter them here (as a cache of work). map perTeamKeySeedsVerified; // Down pointers will be filled in too, on the team, as they are needed to // construct the name of subteams. map downPointers; // The LastUpPointer we received, either a deleted pointer, or a rename pointer. // Or nil if a root team. If lastUpPointer is non-nil and deleted is true, then // this team is deleted. union { null, UpPointer } lastUpPointer; // Creation time of latest PerTeamKey, based on CTime from the // link key appeared in. UnixTime perTeamKeyCTime; // This is filled up to lastSeqno. map linkIDs; // For any link where we have it, fill in merkle sequence information. map merkleInfo; } record Audit { Time time; // The Maximum Merkle Seqno known globally at the time of the audit. @mpackkey("mms") @jsonkey("mms") Seqno maxMerkleSeqno; // The maximum Seqno in the chain at the time of the audit. @mpackkey("mcs") @jsonkey("mcs") Seqno maxChainSeqno; // The maximum hidden Seqno in the chain at the time of the audit. @mpackkey("mhs") @jsonkey("mhs") Seqno maxHiddenSeqno; // The maximum merkle Seqno probed in this audit. @mpackkey("mmp") @jsonkey("mmp") Seqno maxMerkleProbe; } record Probe { @mpackkey("i") @jsonkey("i") int index; @mpackkey("s") @jsonkey("t") Seqno teamSeqno; @mpackkey("h") @jsonkey("h") Seqno teamHiddenSeqno; } enum AuditVersion { V0_0, V1_1, V2_2, V3_3, V4_4 } record AuditHistory { @lint("ignore") TeamID ID; // Whether this is a public team. boolean public; // The Merkle sequence number signed into the head link of the team chain. Seqno priorMerkleSeqno; // Version #, or 0 if not specified AuditVersion version; // When the last audit(s) happened array audits; // Probes of before the team was created. The value in the map is the index // of the audit (each audit has a value in the audits array above). map preProbes; // Probes of after the team was created map postProbes; // For each chain tail we audit, write down the corresponding linkID map tails; // For each hidden chain tail we audit, write down the corresponding linkID map hiddenTails; // if an audit fails, we put the failed probes here so they can be retried // at the next audit. array preProbesToRetry; array postProbesToRetry; // If we just created this team, and it's implicit, then we can skip the audit // for a while (as dictated by the rootFreshness of the Audit Parameters). Time skipUntil; } enum TeamInviteCategory { NONE_0, UNKNOWN_1, KEYBASE_2, EMAIL_3, SBS_4, SEITAN_5, PHONE_6 } variant TeamInviteType switch (TeamInviteCategory c) { case UNKNOWN: string; case SBS: TeamInviteSocialNetwork; default: void; } @typedef("string") record TeamInviteSocialNetwork {} @typedef("string") record TeamInviteName {} record TeamInvite { TeamRole role; TeamInviteID id; TeamInviteType type; TeamInviteName name; UserVersion inviter; union { null, TeamInviteMaxUses } maxUses; union { null, UnixTime } etime; } record AnnotatedTeamInvite { TeamRole role; TeamInviteID id; TeamInviteType type; TeamInviteName name; UserVersion uv; UserVersion inviter; string inviterUsername; string teamName; TeamMemberStatus status; } record TeamEncryptedKBFSKeyset { int v; bytes e; bytes n; } record TeamGetLegacyTLFUpgrade { @jsonkey("encrypted_keyset") string encryptedKeyset; @jsonkey("team_generation") PerTeamKeyGeneration teamGeneration; @jsonkey("legacy_generation") int legacyGeneration; @jsonkey("app_type") TeamApplication appType; } @typedef("string") record TeamEncryptedKBFSKeysetHash {} record TeamLegacyTLFUpgradeChainInfo { TeamEncryptedKBFSKeysetHash keysetHash; PerTeamKeyGeneration teamGeneration; int legacyGeneration; TeamApplication appType; } // State of a parsed team sigchain. // Should be treated as immutable when outside TeamSigChainPlayer. // Modified internally to TeamSigChainPlayer. record TeamSigChainState { // The user who loaded this sigchain UserVersion reader; TeamID id; // Whether this is an implicit team. boolean implicit; // Whether this is a public team. boolean public; // Name of the root ancestor. For A.B.C this is 'A'. TeamName rootAncestor; // Depth of the name. int nameDepth; // Log of the last part of the team name. array nameLog; // The last link processed Seqno lastSeqno; LinkID lastLinkID; // The last high link processed Seqno lastHighSeqno; LinkID lastHighLinkID; // Present if a subteam union { null, TeamID } parentID; // For each user; the timeline of their role status. // The role checkpoints are always ordered by seqno. // The latest role of the user is the role of their last checkpoint. // When a user leaves the team a NONE checkpoint appears in their list. map> userLog; // For each subteam; the timeline of its name. // The checkpoints are always ordered by seqno. // The latest name of the subteam is the role of its last checkpoint. map> subteamLog; // Keyed by per-team-key generation map perTeamKeys; PerTeamKeyGeneration maxPerTeamKeyGeneration; // Creation time of latest PerTeamKey, based on CTime from the // link key appeared in. UnixTime perTeamKeyCTime; // This is filled up to lastSeqno. map linkIDs; // Set of links that are stubbed-out and whose contents are missing. map stubbedLinks; // All invitations that are currently active. map activeInvites; // Invitations that were removed from activeInvites because they // were obsoleted by a ChangeMembership link. map obsoleteInvites; // Invite uses. If an invite can be used multiple times, usedInvite // field is used, and this usage is saved here. map> usedInvites; // Whether this is an open team. boolean open; TeamRole openTeamJoinAs; // restricted-bot configurations map bots; // The list of TLF ID that have been associated with this team in time ascending order. array tlfIDs; // Information about KBFS TLF crypt keys for the TLF associated with this team. The latest // link per app type is what shows up here. map tlfLegacyUpgrade; // The head merkle object is the merkle root signed into the head of the team // It was rolled out in 2018-09, so isn't available for all stored copies // of TeamSigChainState. Thus, if you need it, you should check if it's there, // and if not, fetch the chainlink from the server and check against linksIDs[1]. union { null, MerkleRootV2 } headMerkle; // Merkle roots at each seqno map merkleRoots; } @typedef("string") record BoxSummaryHash {} // The team got this name at this point in time. record TeamNameLogPoint { TeamNamePart lastPart; // The seqno at which the team got this name. Seqno seqno; } // A user became this role at a point in time record UserLogPoint { // The new role. Including NONE if the user left the team. TeamRole role; // The seqno at which the user became this role, and other important // details, like the last known MerkleRoot at that time. SignatureMetadata sigMeta; } record TeamUsedInviteLog { UserVersion uv; // index into TeamSigChainState.userLog[uv][] int logPoint; } // A subteam got this name at this point in time. record SubteamLogPoint { // The new subteam name. TeamName name; // The (parent) seqno at which the subteam got this name. Seqno seqno; } @typedef("string") record TeamNamePart {} // matches the team name struct from api server record TeamName { array parts; } // Sometimes during CLKR server will hint us that users are reset. @lint("ignore") record TeamCLKRResetUser { UID uid; @jsonkey("user_eldest") Seqno userEldestSeqno; @jsonkey("member_eldest") Seqno memberEldestSeqno; } // team.clkr gregor message body @lint("ignore") record TeamCLKRMsg { @jsonkey("team_id") TeamID teamID; PerTeamKeyGeneration generation; int score; @jsonkey("reset_users") array resetUsersUntrusted; } record TeamResetUser { string username; UID uid; @jsonkey("eldest_seqno") Seqno eldestSeqno; @jsonkey("is_delete") boolean isDelete; } // team.member_out_from_reset message body record TeamMemberOutFromReset { @jsonkey("team_id") TeamID teamID; @jsonkey("team_name") string teamName; @jsonkey("reset_user") TeamResetUser resetUser; } record TeamChangeRow { TeamID id; string name; @jsonkey("key_rotated") boolean keyRotated; @jsonkey("membership_changed") boolean membershipChanged; @jsonkey("latest_seqno") Seqno latestSeqno; @jsonkey("latest_hidden_seqno") Seqno latestHiddenSeqno; @jsonkey("latest_offchain_version") Seqno latestOffchainSeqno; @jsonkey("implicit_team") boolean implicitTeam; @jsonkey("misc") boolean misc; @jsonkey("removed_reset_users") boolean removedResetUsers; } record TeamExitRow { TeamID id; } record TeamNewlyAddedRow { TeamID id; string name; } record TeamInvitee { @jsonkey("invite_id") TeamInviteID inviteID; UID uid; @jsonkey("eldest_seqno") Seqno eldestSeqno; // NOTE: This comes from gregor, but should not be used during actual SBS // resolution - always take inviteID and look at the actual invite from the // sigchain. TeamRole role; } // team.sbs gregor message body @lint("ignore") record TeamSBSMsg { @jsonkey("team_id") TeamID teamID; int score; array invitees; } record TeamAccessRequest { UID uid; @jsonkey("eldest_seqno") Seqno eldestSeqno; } @lint("ignore") record TeamOpenReqMsg { @jsonkey("team_id") TeamID teamID; array tars; } @typedef("string") record SeitanAKey {} // The secret shared with the recipient. // With this information one can accept an invite. @typedef("string") record SeitanIKey {} @typedef("string") record SeitanPubKey {} // The secret shared with the recipient. // With this information one can accept an invite. @typedef("string") record SeitanIKeyV2 {} enum SeitanKeyAndLabelVersion { V1_1, V2_2 } variant SeitanKeyAndLabel switch (SeitanKeyAndLabelVersion v) { case V1: SeitanKeyAndLabelVersion1; case V2: SeitanKeyAndLabelVersion2; default: void; } record SeitanKeyAndLabelVersion1 { SeitanIKey i; SeitanKeyLabel l; } record SeitanKeyAndLabelVersion2 { SeitanPubKey k; SeitanKeyLabel l; } enum SeitanKeyLabelType { SMS_1 } variant SeitanKeyLabel switch (SeitanKeyLabelType t) { case SMS: SeitanKeyLabelSms; default: void; } record SeitanKeyLabelSms { string f; // FullName string n; // Number } record TeamSeitanRequest { @jsonkey("invite_id") TeamInviteID inviteID; UID uid; @jsonkey("eldest_seqno") Seqno eldestSeqno; SeitanAKey akey; TeamRole role; @jsonkey("ctime") int64 unixCTime; } @lint("ignore") record TeamSeitanMsg { @jsonkey("team_id") TeamID teamID; array seitans; } record TeamOpenSweepMsg { @jsonkey("team_id") TeamID teamID; @jsonkey("reset_users") array resetUsersUntrusted; } record TeamKBFSKeyRefresher { int generation; TeamApplication appType; } /** * TeamRefreshData are needed or wanted data requirements that, if unmet, will cause * a refresh of the cache. */ record TeamRefreshers { // Load at least up to the keygen. Returns an error if the keygen is not loaded. PerTeamKeyGeneration needKeyGeneration; // Load the reader key mask for the given applications and the given // generations. Returns an error if any of the RKMs are not loaded. map> needApplicationsAtGenerations; // Load the reader key masks for the given applications at the key generation // that includes KBFS keys. map> needApplicationsAtGenerationsWithKBFS; // Refresh if the cached version does not have these members at the minimum role `wantMembersRole`. // Does not guarantee these members will be present in the returned team. This is especially // relevant in the case of deleted users, who won't be there after the refresh. // Does not work on implicit admins. // If the EldestSeqno of an item is zero, the latest EldestSeqno for that UID will be looked // up and used. But the used EldestSeqno may come from a cache. array wantMembers; // The minimum role each of wantMembers must have in order to avoid a refresh. // The default value is WRITER. So if the role is NONE, it will act like WRITER. TeamRole wantMembersRole; // Express that we need KBFS crypt keys on the team at a certain generation TeamKBFSKeyRefresher needKBFSKeyGeneration; } record LoadTeamArg { // One of these must be specified. // ID is preferred. Name will always hit the server for subteams. // If both are specified ID will be used and Name will be checked. @lint("ignore") TeamID ID; string name; // Whether to load a public or private team. boolean public; // Whether we need to be an admin. // Will fail unless we are an admin in the returned Team. // If this is false, looking at invites or listing subteams in the response // may not work even if the loading user is an admin. boolean needAdmin; // Whether we should refresh the UIDMapper with UID/Eldest pairs // needed for display of the user's reset status. This flag is expensive, // since it can incur a disk read per team member (via LevelDB cache). // So only use it when you need to display all the team's members. boolean refreshUIDMapper; TeamRefreshers refreshers; boolean forceFullReload; // Ignore local data and fetch from the server. boolean forceRepoll; // Force a sync with merkle. boolean staleOK; // If a very stale cache hit is OK. boolean allowNameLookupBurstCache; // If it's ok to hit the nameLookup burst cache boolean skipNeedHiddenRotateCheck; // don't check the last hidden rotator for device revoke AuditMode auditMode; // don't run the stochastic team audit } record FastTeamLoadArg { @lint("ignore") TeamID ID; boolean public; union { null, TeamName } assertTeamName; // All (applications X keyGenerationsNeeded) should be loaded array applications; array keyGenerationsNeeded; // Specify this flag if you need the latest generation, which will maybe // trigger a poll of the team. boolean needLatestKey; // Specify this flag if you want to force past the time-based cache, // currently set to one hour. boolean forceRefresh; // Specify this flag to avoid raising an error if the server omits the hidden chain data. // For example, restricted bots can use the FTL to obtain a team name, but cannot see the // hidden chain for such team. boolean hiddenChainIsOptional; } record FastTeamLoadRes { TeamName name; array applicationKeys; } record ImplicitRole { TeamRole role; TeamID ancestor; } record MemberInfo { @jsonkey("uid") UID userID; @jsonkey("team_id") TeamID teamID; @jsonkey("fq_name") string fqName; @jsonkey("is_implicit_team") boolean isImplicitTeam; @jsonkey("is_open_team") boolean isOpenTeam; TeamRole role; union{null, ImplicitRole} implicit; @jsonkey("member_count") int memberCount; @jsonkey("allow_profile_promote") boolean allowProfilePromote; @jsonkey("is_member_showcased") boolean isMemberShowcased; } record TeamList { array teams; } record AnnotatedMemberInfo { @jsonkey("uid") UID userID; @jsonkey("team_id") TeamID teamID; string username; @jsonkey("full_name") string fullName; @jsonkey("fq_name") string fqName; @jsonkey("is_implicit_team") boolean isImplicitTeam; @jsonkey("implicit_team_display_name") string impTeamDisplayName; @jsonkey("is_open_team") boolean isOpenTeam; TeamRole role; union{null, ImplicitRole} implicit; @lint("ignore") boolean needsPUK; @jsonkey("member_count") int memberCount; @jsonkey("member_eldest_seqno") Seqno eldestSeqno; @jsonkey("allow_profile_promote") boolean allowProfilePromote; @jsonkey("is_member_showcased") boolean isMemberShowcased; TeamMemberStatus status; } record AnnotatedTeamList { array teams; map annotatedActiveInvites; } record TeamAddMemberResult { boolean invited; union{null, User} user; boolean chatSending; } record TeamAddMembersResult { array notAdded; } record TeamJoinRequest { string name; string username; FullName fullName; UnixTime ctime; } record TeamTreeResult { array entries; } record TeamTreeEntry { TeamName name; boolean admin; // Whether the user is an admin, explicit or implicit. } record SubteamListEntry { TeamName name; TeamID teamID; int memberCount; } record SubteamListResult { array entries; } record TeamCreateResult { TeamID teamID; boolean chatSent; boolean creatorAdded; } record TeamSettings { boolean open; TeamRole joinAs; } record TeamBotSettings { boolean cmds; boolean mentions; array triggers; // chat1.ConversationID array convs; } record TeamRequestAccessResult { boolean open; } record TeamAcceptOrRequestResult { boolean wasToken; boolean wasSeitan; boolean wasTeamName; // If trying to request access via TeamName, was the team open? It means // that admin does not have to manually approve request. boolean wasOpenTeam; } record TeamShowcase { @jsonkey("is_showcased") boolean isShowcased; union { null, string } description; @jsonkey("set_by_uid") union { null, UID } setByUID; @jsonkey("any_member_showcase") boolean anyMemberShowcase; } record TeamAndMemberShowcase { TeamShowcase teamShowcase; boolean isMemberShowcased; } TeamCreateResult teamCreate(int sessionID, string name, boolean joinSubteam); TeamCreateResult teamCreateWithSettings(int sessionID, string name, boolean joinSubteam, TeamSettings settings); // A user can hope for success if and only if they are a member of // the team or an admin of any of its ancestors. TeamDetails teamGetByID(int sessionID, TeamID id); // A user can hope for success if and only if they are a member of // the team or an admin of any of its ancestors. TeamDetails teamGet(int sessionID, string name); // Get the members of the team TeamMembersDetails teamGetMembers(int sessionID, string name); TeamMembersDetails teamGetMembersByID(int sessionID, TeamID id); // List all the admins of ancestor teams. // Includes admins of the specified team only if they are also admins of ancestor teams. array teamImplicitAdmins(int sessionID, string teamName); // List known team memberships for given userAssertion or current // user if userAssertion is not given. This RPC uses fast server- // trust path and returned list is not checked. AnnotatedTeamList teamListUnverified(int sessionID, string userAssertion, boolean includeImplicitTeams); // List all team mates from all teams for current user. Results are // checked - team members returned from the server are verified // locally with team sigchains. AnnotatedTeamList teamListTeammates(int sessionID, boolean includeImplicitTeams); // List verified known team memberships for given userAssertion. // This function is slower than teamListUnverified because it loads // and checks every team membership. AnnotatedTeamList teamListVerified(int sessionID, string userAssertion, boolean includeImplicitTeams); // admin only array teamListSubteamsRecursive(int sessionID, string parentTeamName, boolean forceRepoll); TeamAddMemberResult teamAddMember(int sessionID, TeamID teamID, string email, string phone, string username, TeamRole role, union { null, TeamBotSettings } botSettings, boolean sendChatNotification, union { null, string } emailInviteMessage); // @emailInviteMessage is an argument used as a welcome message in email invitations sent from the server TeamAddMembersResult teamAddMembers(int sessionID, TeamID teamID, array assertions, TeamRole role, union { null, TeamBotSettings } botSettings, boolean sendChatNotification, union { null, string } emailInviteMessage); record UserRolePair { string assertionOrEmail; TeamRole role; union { null, TeamBotSettings } botSettings; } // @emailInviteMessage is an argument used as a welcome message in email invitations sent from the server TeamAddMembersResult teamAddMembersMultiRole(int sessionID, TeamID teamID, array users, boolean sendChatNotification, union { null, string } emailInviteMessage); // allowInaction suppresses errors that come from removing an invite that doesn't exist. Relevant for suppressing errors when calling remove while a remove is already in progress. // Note: allowInaction only applies to invite removals, just because that was what was needed. void teamRemoveMember(int sessionID, TeamID teamID, string username, string email, TeamInviteID inviteID, boolean allowInaction); record TeamMemberToRemove { string username; string email; TeamInviteID inviteID; boolean allowInaction; } record TeamRemoveMembersResult { array failures; } TeamRemoveMembersResult teamRemoveMembers(int sessionID, TeamID teamID, array users); void teamLeave(int sessionID, string name, boolean permanent); void teamEditMember(int sessionID, string name, string username, TeamRole role, union { null, TeamBotSettings } botSettings); record TeamEditMembersResult { array failures; } // Change the roles of members in a team. // Users are added individually so it is possible for a network error to result in only some users being added. // Assignments that make no change will succeed. TeamEditMembersResult teamEditMembers(int sessionID, TeamID teamID, array users); TeamBotSettings teamGetBotSettings(int sessionID, string name, string username); void teamSetBotSettings(int sessionID, string name, string username, TeamBotSettings botSettings); void teamRename(int sessionID, TeamName prevName, TeamName newName); void teamAcceptInvite(int sessionID, string token); TeamRequestAccessResult teamRequestAccess(int sessionID, string name); // Accept an invite or request to join a team, try both. TeamAcceptOrRequestResult teamAcceptInviteOrRequestAccess(int sessionID, string tokenOrName); array teamListRequests(int sessionID, union { null, string } teamName); // List TeamAccessRequests from current user. Optionally can provide // a teamname, and API server will only return request for that team, // if there is one. array teamListMyAccessRequests(int sessionID, union{ null, string } teamName); void teamIgnoreRequest(int sessionID, string name, string username); // Get a list of all recursive subteams of this team's root team that are visible to the user. // Sorted alphabetically like a tree. // Example: "a" or "a.b" -> [a, a.b, a.b.c, a.b.d, a.e.f, a.e.g] TeamTreeResult teamTree(int sessionID, TeamName name); // Get a list of all recursive subteams of this team's root team that are visible to the user. // Sorted alphabetically like a tree. // Example: "a" or "a.b" -> [a, a.b, a.b.c, a.b.d, a.e.f, a.e.g] // This list is generated without loading each team, and is therefore server-trust. SubteamListResult teamGetSubteams(int sessionID, TeamName name); void teamDelete(int sessionID, TeamID teamID); void teamSetSettings(int sessionID, TeamID teamID, TeamSettings settings); SeitanIKey teamCreateSeitanToken(int sessionID, string name, TeamRole role, SeitanKeyLabel label); SeitanIKeyV2 teamCreateSeitanTokenV2(int sessionID, string name, TeamRole role, SeitanKeyLabel label); record BulkRes { array malformed; } // emails is a list of email addresses separated by whitespace or commas. // returns a list of emails that were not invited, most likely because of // malformed email addresses. BulkRes teamAddEmailsBulk(int sessionID, string name, string emails, TeamRole role); record ImplicitTeamUserSet { array keybaseUsers; array unresolvedUsers; } /** * iTeams */ record ImplicitTeamDisplayName { boolean isPublic; ImplicitTeamUserSet writers; ImplicitTeamUserSet readers; union { null, ImplicitTeamConflictInfo } conflictInfo; } @typedef("int") record ConflictGeneration {} record ImplicitTeamConflictInfo { ConflictGeneration generation; Time time; } record LookupImplicitTeamRes { TeamID teamID; TeamName name; ImplicitTeamDisplayName displayName; TLFID tlfID; } LookupImplicitTeamRes lookupImplicitTeam(string name, boolean public); LookupImplicitTeamRes lookupOrCreateImplicitTeam(string name, boolean public); void teamReAddMemberAfterReset(int sessionID, TeamID id, string username); /** * loadTeamPlusApplicationKeys loads team information for applications like KBFS and Chat. * If refreshers are non-empty, then force a refresh of the cache if the requirements * of the refreshers aren't met. If OfflineAvailability is set to BEST_EFFORT, and the * client is currently offline (or thinks it's offline), then the refreshers are overridden * and ignored, and stale data might still be returned. */ TeamPlusApplicationKeys loadTeamPlusApplicationKeys(int sessionID, TeamID id, TeamApplication application, TeamRefreshers refreshers, boolean includeKBFSKeys, OfflineAvailability oa); TeamID getTeamRootID(TeamID id); TeamShowcase getTeamShowcase(TeamID teamID); TeamAndMemberShowcase getTeamAndMemberShowcase(TeamID teamID); void setTeamShowcase(TeamID teamID, union { null, boolean } isShowcased, union { null, string } description, union { null, boolean } anyMemberShowcase); void setTeamMemberShowcase(TeamID teamID, boolean isShowcased); record TeamOperation { boolean manageMembers; boolean manageSubteams; boolean createChannel; boolean chat; boolean deleteChannel; boolean renameChannel; boolean renameTeam; boolean editChannelDescription; boolean editTeamDescription; boolean setTeamShowcase; boolean setMemberShowcase; boolean setRetentionPolicy; boolean setMinWriterRole; boolean changeOpenTeam; boolean leaveTeam; boolean joinTeam; boolean setPublicityAny; boolean listFirst; boolean changeTarsDisabled; boolean deleteChatHistory; boolean deleteOtherMessages; boolean deleteTeam; boolean pinMessage; boolean manageBots; } record ProfileTeamLoadRes { long loadTimeNsec; } TeamOperation canUserPerform(string name); enum RotationType { VISIBLE_0, HIDDEN_1, CLKR_2 } void teamRotateKey(TeamID teamID, RotationType rt); TeamDebugRes teamDebug(TeamID teamID); record TeamDebugRes { TeamSigChainState chain; } boolean getTarsDisabled(TeamID teamID); void setTarsDisabled(TeamID teamID, boolean disabled); // List of teams for that "Add to team..." screen on the profile screen. // All return values are server-trust so only use where safe. array teamProfileAddList(int sessionID, string username); record TeamProfileAddEntry { TeamID teamID; TeamName teamName; boolean open; // Reason for disabling selection. // "" if enabled // Examples: "modalduality is already a member.", "Only admins can add people." string disabledReason; } void uploadTeamAvatar(string teamname, string filename, union { null, ImageCropRect } crop, boolean sendChatNotification); bytes tryDecryptWithTeamKey(TeamID teamID, bytes encryptedData, BoxNonce nonce, BoxPublicKey peersPublicKey, PerTeamKeyGeneration minGeneration); /** FindNextMerkleRootAfterTeamRemoval finds the first Merkle root that contains the user being removed from the team at that given seqno in the team's chain. You should pass in a previous Merkle root as a starting point for the binary search. */ NextMerkleRootRes findNextMerkleRootAfterTeamRemoval(UID uid, TeamID team, boolean isPublic, Seqno teamSigchainSeqno, MerkleRootV2 prev); /** FindNextMerkleRootAfterTeamRemovalBySigningKey find the first Merkle root that contains the user with the given signing key being removed from the given team. If there are several such instances, we will return just the last one. When anyRoleAllowed is false, the team removal is any drop in permissions from Writer (or above) to Reader (or below). */ NextMerkleRootRes findNextMerkleRootAfterTeamRemovalBySigningKey(UID uid, KID signingKey, TeamID team, boolean isPublic, boolean anyRoleAllowed); /** ProfileTeamLoad loads a team and then throws it on the ground, for the purposes of profiling the team load machinery. */ ProfileTeamLoadRes profileTeamLoad(LoadTeamArg arg); /** Gets a TeamID from a team name string. Returns an error if the current user can't read the team. */ TeamID getTeamID(string teamName); /** Gets a TeamName from a team id string. Returns an error if the current user can't read the team. */ TeamName getTeamName(TeamID teamID); FastTeamLoadRes ftl(FastTeamLoadArg arg); record MemberEmail { string email; string role; } record MemberUsername { string username; string role; } record TeamRolePair { TeamRole role; @jsonkey("implicit_role") TeamRole implicitRole; } record TeamRoleMapAndVersion { map teams; @jsonkey("user_team_version") UserTeamVersion version; } record TeamRoleMapStored { TeamRoleMapAndVersion data; Time cachedAt; } TeamRoleMapAndVersion getTeamRoleMap(); @typedef("int") record UserTeamVersion {} record UserTeamVersionUpdate { UserTeamVersion version; } record AnnotatedTeamMemberDetails { TeamMemberDetails details; TeamRole role; } record AnnotatedTeam { TeamID teamID; string name; SubteamListResult transitiveSubteamsUnverified; array members; array invites; array joinRequests; boolean tarsDisabled; TeamSettings settings; TeamShowcase showcase; } AnnotatedTeam getAnnotatedTeam(TeamID teamID); record AnnotatedSubteamMemberDetails { TeamName teamName; TeamID teamID; TeamMemberDetails details; TeamRole role; } array getUserSubteamMemberships(TeamID teamID, string username); }