Group Description: Edit/Receive

This commit is contained in:
Scott Nonnenberg 2021-06-01 17:24:28 -07:00 committed by GitHub
parent e5d365dfc4
commit 9705f464be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 859 additions and 149 deletions

View file

@ -178,6 +178,10 @@ export type GroupV2AdminApprovalRemoveOneChangeType = {
conversationId: string;
inviter?: string;
};
export type GroupV2DescriptionChangeType = {
type: 'description';
removed?: boolean;
};
export type GroupV2ChangeDetailType =
| GroupV2AccessAttributesChangeType
@ -187,9 +191,10 @@ export type GroupV2ChangeDetailType =
| GroupV2AdminApprovalAddOneChangeType
| GroupV2AdminApprovalRemoveOneChangeType
| GroupV2AvatarChangeType
| GroupV2DescriptionChangeType
| GroupV2GroupLinkAddChangeType
| GroupV2GroupLinkResetChangeType
| GroupV2GroupLinkRemoveChangeType
| GroupV2GroupLinkResetChangeType
| GroupV2MemberAddChangeType
| GroupV2MemberAddFromAdminApprovalChangeType
| GroupV2MemberAddFromInviteChangeType
@ -251,12 +256,13 @@ type UploadedAvatarType = {
export const MASTER_KEY_LENGTH = 32;
const GROUP_TITLE_MAX_ENCRYPTED_BYTES = 1024;
const GROUP_DESC_MAX_ENCRYPTED_BYTES = 8192;
export const ID_V1_LENGTH = 16;
export const ID_LENGTH = 32;
const TEMPORAL_AUTH_REJECTED_CODE = 401;
const GROUP_ACCESS_DENIED_CODE = 403;
const GROUP_NONEXISTENT_CODE = 404;
const SUPPORTED_CHANGE_EPOCH = 1;
const SUPPORTED_CHANGE_EPOCH = 2;
export const LINK_VERSION_ERROR = 'LINK_VERSION_ERROR';
const GROUP_INVITE_LINK_PASSWORD_LENGTH = 16;
@ -415,6 +421,25 @@ function buildGroupTitleBuffer(
return result;
}
function buildGroupDescriptionBuffer(
clientZkGroupCipher: ClientZkGroupCipher,
description: string
): ArrayBuffer {
const attrsBlob = new window.textsecure.protobuf.GroupAttributeBlob();
attrsBlob.descriptionText = description;
const attrsBlobPlaintext = attrsBlob.toArrayBuffer();
const result = encryptGroupBlob(clientZkGroupCipher, attrsBlobPlaintext);
if (result.byteLength > GROUP_DESC_MAX_ENCRYPTED_BYTES) {
throw new Error(
'buildGroupDescriptionBuffer: encrypted group title is too long'
);
}
return result;
}
function buildGroupProto(
attributes: Pick<
ConversationAttributesType,
@ -716,6 +741,7 @@ export async function buildUpdateAttributesChange(
>,
attributes: Readonly<{
avatar?: undefined | ArrayBuffer;
description?: string;
title?: string;
}>
): Promise<undefined | GroupChangeClass.Actions> {
@ -774,6 +800,17 @@ export async function buildUpdateAttributesChange(
);
}
const { description } = attributes;
if (typeof description === 'string') {
hasChangedSomething = true;
actions.modifyDescription = new window.textsecure.protobuf.GroupChange.Actions.ModifyDescriptionAction();
actions.modifyDescription.descriptionBytes = buildGroupDescriptionBuffer(
clientZkGroupCipher,
description
);
}
if (!hasChangedSomething) {
// This shouldn't happen. When these actions are passed to `modifyGroupV2`, a warning
// will be logged.
@ -3612,6 +3649,14 @@ function extractDiffs({
});
}
// description
if (old.description !== current.description) {
details.push({
type: 'description',
removed: !current.description,
});
}
// No disappearing message timer check here - see below
// membersV2
@ -4396,6 +4441,19 @@ async function applyGroupChange({
}
}
// modifyDescription?: GroupChangeClass.Actions.ModifyDescriptionAction;
if (actions.modifyDescription) {
const { descriptionBytes } = actions.modifyDescription;
if (descriptionBytes && descriptionBytes.content === 'descriptionText') {
result.description = descriptionBytes.descriptionText;
} else {
window.log.warn(
`applyGroupChange/${logId}: Clearing group description due to missing data.`
);
result.description = undefined;
}
}
if (ourConversationId) {
result.left = !members[ourConversationId];
}
@ -4669,6 +4727,14 @@ async function applyGroupState({
result.groupInviteLinkPassword = undefined;
}
// descriptionBytes
const { descriptionBytes } = groupState;
if (descriptionBytes && descriptionBytes.content === 'descriptionText') {
result.description = descriptionBytes.descriptionText;
} else {
result.description = undefined;
}
return {
newAttributes: result,
newProfileKeys,
@ -5219,6 +5285,29 @@ function decryptGroupChange(
actions.modifyInviteLinkPassword = undefined;
}
// modifyDescription?: GroupChangeClass.Actions.ModifyDescriptionAction;
if (
actions.modifyDescription &&
!isByteBufferEmpty(actions.modifyDescription.descriptionBytes)
) {
try {
actions.modifyDescription.descriptionBytes = window.textsecure.protobuf.GroupAttributeBlob.decode(
decryptGroupBlob(
clientZkGroupCipher,
actions.modifyDescription.descriptionBytes.toArrayBuffer()
)
);
} catch (error) {
window.log.warn(
`decryptGroupChange/${logId}: Unable to decrypt modifyDescription.descriptionBytes`,
error && error.stack ? error.stack : error
);
actions.modifyDescription.descriptionBytes = undefined;
}
} else if (actions.modifyDescription) {
actions.modifyDescription.descriptionBytes = undefined;
}
return actions;
}
@ -5240,6 +5329,26 @@ export function decryptGroupTitle(
return undefined;
}
export function decryptGroupDescription(
description: ProtoBinaryType,
secretParams: string
): string | undefined {
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
if (isByteBufferEmpty(description)) {
return undefined;
}
const blob = window.textsecure.protobuf.GroupAttributeBlob.decode(
decryptGroupBlob(clientZkGroupCipher, description.toArrayBuffer())
);
if (blob && blob.content === 'descriptionText') {
return blob.descriptionText;
}
return undefined;
}
function decryptGroupState(
groupState: GroupClass,
groupSecretParams: string,
@ -5349,6 +5458,26 @@ function decryptGroupState(
groupState.inviteLinkPassword = undefined;
}
// descriptionBytes
if (!isByteBufferEmpty(groupState.descriptionBytes)) {
try {
groupState.descriptionBytes = window.textsecure.protobuf.GroupAttributeBlob.decode(
decryptGroupBlob(
clientZkGroupCipher,
groupState.descriptionBytes.toArrayBuffer()
)
);
} catch (error) {
window.log.warn(
`decryptGroupState/${logId}: Unable to decrypt descriptionBytes. Clearing it.`,
error && error.stack ? error.stack : error
);
groupState.descriptionBytes = undefined;
}
} else {
groupState.descriptionBytes = undefined;
}
return groupState;
}