Limit number of GV2 banned members
This commit is contained in:
parent
6a671e73f9
commit
c5a3ffddf9
9 changed files with 389 additions and 37 deletions
|
@ -46,7 +46,8 @@ message MemberPendingAdminApproval {
|
||||||
}
|
}
|
||||||
|
|
||||||
message MemberBanned {
|
message MemberBanned {
|
||||||
bytes userId = 1;
|
bytes userId = 1;
|
||||||
|
uint64 timestamp = 2; // ms since epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
message AccessControl {
|
message AccessControl {
|
||||||
|
|
|
@ -202,6 +202,7 @@ function GroupV2Detail({
|
||||||
{
|
{
|
||||||
action: () => blockGroupLinkRequests(detail.uuid),
|
action: () => blockGroupLinkRequests(detail.uuid),
|
||||||
text: i18n('PendingRequests--block--confirm'),
|
text: i18n('PendingRequests--block--confirm'),
|
||||||
|
style: 'affirmative',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
i18n={i18n}
|
i18n={i18n}
|
||||||
|
|
143
ts/groups.ts
143
ts/groups.ts
|
@ -33,6 +33,7 @@ import type {
|
||||||
GroupV2MemberType,
|
GroupV2MemberType,
|
||||||
GroupV2PendingAdminApprovalType,
|
GroupV2PendingAdminApprovalType,
|
||||||
GroupV2PendingMemberType,
|
GroupV2PendingMemberType,
|
||||||
|
GroupV2BannedMemberType,
|
||||||
MessageAttributesType,
|
MessageAttributesType,
|
||||||
} from './model-types.d';
|
} from './model-types.d';
|
||||||
import {
|
import {
|
||||||
|
@ -727,7 +728,9 @@ export async function buildAddMembersChange(
|
||||||
addPendingMembers.push(addPendingMemberAction);
|
addPendingMembers.push(addPendingMemberAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
const doesMemberNeedUnban = conversation.bannedMembersV2?.includes(uuid);
|
const doesMemberNeedUnban = conversation.bannedMembersV2?.find(
|
||||||
|
bannedMember => bannedMember.uuid === uuid
|
||||||
|
);
|
||||||
if (doesMemberNeedUnban) {
|
if (doesMemberNeedUnban) {
|
||||||
const uuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, uuid);
|
const uuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, uuid);
|
||||||
|
|
||||||
|
@ -976,6 +979,67 @@ export function buildAccessControlMembersChange(
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function _maybeBuildAddBannedMemberActions({
|
||||||
|
clientZkGroupCipher,
|
||||||
|
group,
|
||||||
|
ourUuid,
|
||||||
|
uuid,
|
||||||
|
}: {
|
||||||
|
clientZkGroupCipher: ClientZkGroupCipher;
|
||||||
|
group: Pick<ConversationAttributesType, 'bannedMembersV2'>;
|
||||||
|
ourUuid: UUIDStringType;
|
||||||
|
uuid: UUIDStringType;
|
||||||
|
}): Pick<
|
||||||
|
Proto.GroupChange.IActions,
|
||||||
|
'addMembersBanned' | 'deleteMembersBanned'
|
||||||
|
> {
|
||||||
|
const doesMemberNeedBan =
|
||||||
|
!group.bannedMembersV2?.find(member => member.uuid === uuid) &&
|
||||||
|
uuid !== ourUuid;
|
||||||
|
if (!doesMemberNeedBan) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
// Sort current banned members by decreasing timestamp
|
||||||
|
const sortedBannedMembers = [...(group.bannedMembersV2 ?? [])].sort(
|
||||||
|
(a, b) => {
|
||||||
|
return b.timestamp - a.timestamp;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// All members after the limit have to be deleted and are older than the
|
||||||
|
// rest of the list.
|
||||||
|
const deletedBannedMembers = sortedBannedMembers.slice(
|
||||||
|
Math.max(0, getGroupSizeHardLimit() - 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
let deleteMembersBanned = null;
|
||||||
|
if (deletedBannedMembers.length > 0) {
|
||||||
|
deleteMembersBanned = deletedBannedMembers.map(bannedMember => {
|
||||||
|
const deleteMemberBannedAction =
|
||||||
|
new Proto.GroupChange.Actions.DeleteMemberBannedAction();
|
||||||
|
|
||||||
|
deleteMemberBannedAction.deletedUserId = encryptUuid(
|
||||||
|
clientZkGroupCipher,
|
||||||
|
bannedMember.uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
return deleteMemberBannedAction;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const addMemberBannedAction =
|
||||||
|
new Proto.GroupChange.Actions.AddMemberBannedAction();
|
||||||
|
|
||||||
|
const uuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, uuid);
|
||||||
|
addMemberBannedAction.added = new Proto.MemberBanned();
|
||||||
|
addMemberBannedAction.added.userId = uuidCipherTextBuffer;
|
||||||
|
|
||||||
|
return {
|
||||||
|
addMembersBanned: [addMemberBannedAction],
|
||||||
|
deleteMembersBanned,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// TODO AND-1101
|
// TODO AND-1101
|
||||||
export function buildDeletePendingAdminApprovalMemberChange({
|
export function buildDeletePendingAdminApprovalMemberChange({
|
||||||
group,
|
group,
|
||||||
|
@ -1005,16 +1069,19 @@ export function buildDeletePendingAdminApprovalMemberChange({
|
||||||
deleteMemberPendingAdminApproval,
|
deleteMemberPendingAdminApproval,
|
||||||
];
|
];
|
||||||
|
|
||||||
const doesMemberNeedBan =
|
const { addMembersBanned, deleteMembersBanned } =
|
||||||
!group.bannedMembersV2?.includes(uuid) && uuid !== ourUuid;
|
_maybeBuildAddBannedMemberActions({
|
||||||
if (doesMemberNeedBan) {
|
clientZkGroupCipher,
|
||||||
const addMemberBannedAction =
|
group,
|
||||||
new Proto.GroupChange.Actions.AddMemberBannedAction();
|
ourUuid,
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
|
||||||
addMemberBannedAction.added = new Proto.MemberBanned();
|
if (addMembersBanned) {
|
||||||
addMemberBannedAction.added.userId = uuidCipherTextBuffer;
|
actions.addMembersBanned = addMembersBanned;
|
||||||
|
}
|
||||||
actions.addMembersBanned = [addMemberBannedAction];
|
if (deleteMembersBanned) {
|
||||||
|
actions.deleteMembersBanned = deleteMembersBanned;
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
@ -1098,7 +1165,9 @@ export function buildAddMember({
|
||||||
actions.version = (group.revision || 0) + 1;
|
actions.version = (group.revision || 0) + 1;
|
||||||
actions.addMembers = [addMember];
|
actions.addMembers = [addMember];
|
||||||
|
|
||||||
const doesMemberNeedUnban = group.bannedMembersV2?.includes(uuid);
|
const doesMemberNeedUnban = group.bannedMembersV2?.find(
|
||||||
|
member => member.uuid === uuid
|
||||||
|
);
|
||||||
if (doesMemberNeedUnban) {
|
if (doesMemberNeedUnban) {
|
||||||
const clientZkGroupCipher = getClientZkGroupCipher(group.secretParams);
|
const clientZkGroupCipher = getClientZkGroupCipher(group.secretParams);
|
||||||
const uuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, uuid);
|
const uuidCipherTextBuffer = encryptUuid(clientZkGroupCipher, uuid);
|
||||||
|
@ -1166,17 +1235,19 @@ export function buildDeleteMemberChange({
|
||||||
actions.version = (group.revision || 0) + 1;
|
actions.version = (group.revision || 0) + 1;
|
||||||
actions.deleteMembers = [deleteMember];
|
actions.deleteMembers = [deleteMember];
|
||||||
|
|
||||||
const doesMemberNeedBan =
|
const { addMembersBanned, deleteMembersBanned } =
|
||||||
!group.bannedMembersV2?.includes(uuid) && uuid !== ourUuid;
|
_maybeBuildAddBannedMemberActions({
|
||||||
|
clientZkGroupCipher,
|
||||||
|
group,
|
||||||
|
ourUuid,
|
||||||
|
uuid,
|
||||||
|
});
|
||||||
|
|
||||||
if (doesMemberNeedBan) {
|
if (addMembersBanned) {
|
||||||
const addMemberBannedAction =
|
actions.addMembersBanned = addMembersBanned;
|
||||||
new Proto.GroupChange.Actions.AddMemberBannedAction();
|
}
|
||||||
|
if (deleteMembersBanned) {
|
||||||
addMemberBannedAction.added = new Proto.MemberBanned();
|
actions.deleteMembersBanned = deleteMembersBanned;
|
||||||
addMemberBannedAction.added.userId = uuidCipherTextBuffer;
|
|
||||||
|
|
||||||
actions.addMembersBanned = [addMemberBannedAction];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
@ -4362,8 +4433,8 @@ async function applyGroupChange({
|
||||||
> = fromPairs(
|
> = fromPairs(
|
||||||
(result.pendingAdminApprovalV2 || []).map(member => [member.uuid, member])
|
(result.pendingAdminApprovalV2 || []).map(member => [member.uuid, member])
|
||||||
);
|
);
|
||||||
const bannedMembers: Record<UUIDStringType, UUIDStringType> = fromPairs(
|
const bannedMembers = new Map<UUIDStringType, GroupV2BannedMemberType>(
|
||||||
(result.bannedMembersV2 || []).map(uuid => [uuid, uuid])
|
(result.bannedMembersV2 || []).map(member => [member.uuid, member])
|
||||||
);
|
);
|
||||||
|
|
||||||
// version?: number;
|
// version?: number;
|
||||||
|
@ -4787,28 +4858,28 @@ async function applyGroupChange({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actions.addMembersBanned && actions.addMembersBanned.length > 0) {
|
if (actions.addMembersBanned && actions.addMembersBanned.length > 0) {
|
||||||
actions.addMembersBanned.forEach(uuid => {
|
actions.addMembersBanned.forEach(member => {
|
||||||
if (bannedMembers[uuid]) {
|
if (bannedMembers.has(member.uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`applyGroupChange/${logId}: Attempt to add banned member failed; was already in banned list.`
|
`applyGroupChange/${logId}: Attempt to add banned member failed; was already in banned list.`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bannedMembers[uuid] = uuid;
|
bannedMembers.set(member.uuid, member);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (actions.deleteMembersBanned && actions.deleteMembersBanned.length > 0) {
|
if (actions.deleteMembersBanned && actions.deleteMembersBanned.length > 0) {
|
||||||
actions.deleteMembersBanned.forEach(uuid => {
|
actions.deleteMembersBanned.forEach(uuid => {
|
||||||
if (!bannedMembers[uuid]) {
|
if (!bannedMembers.has(uuid)) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`applyGroupChange/${logId}: Attempt to remove banned member failed; was not in banned list.`
|
`applyGroupChange/${logId}: Attempt to remove banned member failed; was not in banned list.`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete bannedMembers[uuid];
|
bannedMembers.delete(uuid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4820,7 +4891,7 @@ async function applyGroupChange({
|
||||||
result.membersV2 = values(members);
|
result.membersV2 = values(members);
|
||||||
result.pendingMembersV2 = values(pendingMembers);
|
result.pendingMembersV2 = values(pendingMembers);
|
||||||
result.pendingAdminApprovalV2 = values(pendingAdminApprovalMembers);
|
result.pendingAdminApprovalV2 = values(pendingAdminApprovalMembers);
|
||||||
result.bannedMembersV2 = values(bannedMembers);
|
result.bannedMembersV2 = Array.from(bannedMembers.values());
|
||||||
|
|
||||||
return {
|
return {
|
||||||
newAttributes: result,
|
newAttributes: result,
|
||||||
|
@ -5181,7 +5252,7 @@ type DecryptedGroupChangeActions = {
|
||||||
modifyAnnouncementsOnly?: {
|
modifyAnnouncementsOnly?: {
|
||||||
announcementsOnly: boolean;
|
announcementsOnly: boolean;
|
||||||
};
|
};
|
||||||
addMembersBanned?: ReadonlyArray<UUIDStringType>;
|
addMembersBanned?: ReadonlyArray<GroupV2BannedMemberType>;
|
||||||
deleteMembersBanned?: ReadonlyArray<UUIDStringType>;
|
deleteMembersBanned?: ReadonlyArray<UUIDStringType>;
|
||||||
} & Pick<
|
} & Pick<
|
||||||
Proto.GroupChange.IActions,
|
Proto.GroupChange.IActions,
|
||||||
|
@ -5720,10 +5791,13 @@ function decryptGroupChange(
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return normalizeUuid(
|
const uuid = normalizeUuid(
|
||||||
decryptUuid(clientZkGroupCipher, item.added.userId),
|
decryptUuid(clientZkGroupCipher, item.added.userId),
|
||||||
'addMembersBanned.added.userId'
|
'addMembersBanned.added.userId'
|
||||||
);
|
);
|
||||||
|
const timestamp = normalizeTimestamp(item.added.timestamp);
|
||||||
|
|
||||||
|
return { uuid, timestamp };
|
||||||
})
|
})
|
||||||
.filter(isNotNil);
|
.filter(isNotNil);
|
||||||
}
|
}
|
||||||
|
@ -5804,7 +5878,7 @@ type DecryptedGroupState = {
|
||||||
descriptionBytes?: Proto.GroupAttributeBlob;
|
descriptionBytes?: Proto.GroupAttributeBlob;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
announcementsOnly?: boolean;
|
announcementsOnly?: boolean;
|
||||||
membersBanned?: Array<UUIDStringType>;
|
membersBanned?: Array<GroupV2BannedMemberType>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function decryptGroupState(
|
function decryptGroupState(
|
||||||
|
@ -5951,10 +6025,13 @@ function decryptGroupState(
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return normalizeUuid(
|
const uuid = normalizeUuid(
|
||||||
decryptUuid(clientZkGroupCipher, item.userId),
|
decryptUuid(clientZkGroupCipher, item.userId),
|
||||||
'membersBanned.added.userId'
|
'membersBanned.added.userId'
|
||||||
);
|
);
|
||||||
|
const timestamp = item.timestamp?.toNumber() ?? 0;
|
||||||
|
|
||||||
|
return { uuid, timestamp };
|
||||||
})
|
})
|
||||||
.filter(isNotNil);
|
.filter(isNotNil);
|
||||||
} else {
|
} else {
|
||||||
|
|
7
ts/model-types.d.ts
vendored
7
ts/model-types.d.ts
vendored
|
@ -351,7 +351,7 @@ export type ConversationAttributesType = {
|
||||||
membersV2?: Array<GroupV2MemberType>;
|
membersV2?: Array<GroupV2MemberType>;
|
||||||
pendingMembersV2?: Array<GroupV2PendingMemberType>;
|
pendingMembersV2?: Array<GroupV2PendingMemberType>;
|
||||||
pendingAdminApprovalV2?: Array<GroupV2PendingAdminApprovalType>;
|
pendingAdminApprovalV2?: Array<GroupV2PendingAdminApprovalType>;
|
||||||
bannedMembersV2?: Array<UUIDStringType>;
|
bannedMembersV2?: Array<GroupV2BannedMemberType>;
|
||||||
groupInviteLinkPassword?: string;
|
groupInviteLinkPassword?: string;
|
||||||
previousGroupV1Id?: string;
|
previousGroupV1Id?: string;
|
||||||
previousGroupV1Members?: Array<string>;
|
previousGroupV1Members?: Array<string>;
|
||||||
|
@ -390,6 +390,11 @@ export type GroupV2PendingMemberType = {
|
||||||
role: MemberRoleEnum;
|
role: MemberRoleEnum;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GroupV2BannedMemberType = {
|
||||||
|
uuid: UUIDStringType;
|
||||||
|
timestamp: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type GroupV2PendingAdminApprovalType = {
|
export type GroupV2PendingAdminApprovalType = {
|
||||||
uuid: UUIDStringType;
|
uuid: UUIDStringType;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
|
|
@ -435,7 +435,7 @@ export class ConversationModel extends window.Backbone
|
||||||
}
|
}
|
||||||
|
|
||||||
const uuid = UUID.checkedLookup(id).toString();
|
const uuid = UUID.checkedLookup(id).toString();
|
||||||
return bannedMembersV2.some(item => item === uuid);
|
return bannedMembersV2.some(member => member.uuid === uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
isMemberAwaitingApproval(id: string): boolean {
|
isMemberAwaitingApproval(id: string): boolean {
|
||||||
|
@ -3555,7 +3555,7 @@ export class ConversationModel extends window.Backbone
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.get('bannedMembersV2') || [];
|
return (this.get('bannedMembersV2') || []).map(member => member.uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMembers(
|
getMembers(
|
||||||
|
|
92
ts/sql/migrations/53-gv2-banned-members.ts
Normal file
92
ts/sql/migrations/53-gv2-banned-members.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { Database } from 'better-sqlite3';
|
||||||
|
|
||||||
|
import type { LoggerType } from '../../types/Logging';
|
||||||
|
import type { UUIDStringType } from '../../types/UUID';
|
||||||
|
import { jsonToObject } from '../util';
|
||||||
|
import type { EmptyQuery } from '../util';
|
||||||
|
import type { ConversationType } from '../Interface';
|
||||||
|
|
||||||
|
export default function updateToSchemaVersion53(
|
||||||
|
currentVersion: number,
|
||||||
|
db: Database,
|
||||||
|
logger: LoggerType
|
||||||
|
): void {
|
||||||
|
if (currentVersion >= 53) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
type LegacyConversationType = {
|
||||||
|
id: string;
|
||||||
|
groupId: string;
|
||||||
|
bannedMembersV2?: Array<UUIDStringType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateConversationStmt = db.prepare(
|
||||||
|
`
|
||||||
|
UPDATE conversations SET
|
||||||
|
json = json_patch(json, $jsonPatch)
|
||||||
|
WHERE id = $id;
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
const upgradeConversation = (convo: ConversationType): boolean => {
|
||||||
|
const legacy = convo as unknown as LegacyConversationType;
|
||||||
|
|
||||||
|
const logId = `(${legacy.id}) groupv2(${legacy.groupId})`;
|
||||||
|
|
||||||
|
if (!legacy.bannedMembersV2?.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonPatch: Pick<ConversationType, 'bannedMembersV2'> = {
|
||||||
|
bannedMembersV2: legacy.bannedMembersV2.map(uuid => ({
|
||||||
|
uuid,
|
||||||
|
timestamp: 0,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`updateToSchemaVersion53: Updating ${logId} with ` +
|
||||||
|
`${legacy.bannedMembersV2.length} banned members`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateConversationStmt.run({
|
||||||
|
id: legacy.id,
|
||||||
|
jsonPatch: JSON.stringify(jsonPatch),
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
const allConversations = db
|
||||||
|
.prepare<EmptyQuery>(
|
||||||
|
`
|
||||||
|
SELECT json, profileLastFetchedAt
|
||||||
|
FROM conversations
|
||||||
|
WHERE type = 'group'
|
||||||
|
ORDER BY id ASC;
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.all()
|
||||||
|
.map(({ json }) => jsonToObject<ConversationType>(json));
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
'updateToSchemaVersion53: About to iterate through ' +
|
||||||
|
`${allConversations.length} conversations`
|
||||||
|
);
|
||||||
|
|
||||||
|
let updated = 0;
|
||||||
|
for (const convo of allConversations) {
|
||||||
|
updated += upgradeConversation(convo) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`updateToSchemaVersion53: Updated ${updated} conversations`);
|
||||||
|
|
||||||
|
db.pragma('user_version = 53');
|
||||||
|
})();
|
||||||
|
logger.info('updateToSchemaVersion53: success!');
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import updateToSchemaVersion49 from './49-fix-preview-index';
|
||||||
import updateToSchemaVersion50 from './50-fix-messages-unread-index';
|
import updateToSchemaVersion50 from './50-fix-messages-unread-index';
|
||||||
import updateToSchemaVersion51 from './51-centralize-conversation-jobs';
|
import updateToSchemaVersion51 from './51-centralize-conversation-jobs';
|
||||||
import updateToSchemaVersion52 from './52-optimize-stories';
|
import updateToSchemaVersion52 from './52-optimize-stories';
|
||||||
|
import updateToSchemaVersion53 from './53-gv2-banned-members';
|
||||||
|
|
||||||
function updateToSchemaVersion1(
|
function updateToSchemaVersion1(
|
||||||
currentVersion: number,
|
currentVersion: number,
|
||||||
|
@ -1919,6 +1920,7 @@ export const SCHEMA_VERSIONS = [
|
||||||
updateToSchemaVersion50,
|
updateToSchemaVersion50,
|
||||||
updateToSchemaVersion51,
|
updateToSchemaVersion51,
|
||||||
updateToSchemaVersion52,
|
updateToSchemaVersion52,
|
||||||
|
updateToSchemaVersion53,
|
||||||
];
|
];
|
||||||
|
|
||||||
export function updateSchema(db: Database, logger: LoggerType): void {
|
export function updateSchema(db: Database, logger: LoggerType): void {
|
||||||
|
|
118
ts/test-both/groups/add_banned_member_test.ts
Normal file
118
ts/test-both/groups/add_banned_member_test.ts
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import { UUID } from '../../types/UUID';
|
||||||
|
import { _maybeBuildAddBannedMemberActions } from '../../groups';
|
||||||
|
import { getClientZkGroupCipher, decryptUuid } from '../../util/zkgroup';
|
||||||
|
import { updateRemoteConfig } from '../helpers/RemoteConfigStub';
|
||||||
|
|
||||||
|
const HARD_LIMIT_KEY = 'global.groupsv2.groupSizeHardLimit';
|
||||||
|
|
||||||
|
describe('group add banned member', () => {
|
||||||
|
const uuid = UUID.generate().toString();
|
||||||
|
const ourUuid = UUID.generate().toString();
|
||||||
|
const existing = Array.from({ length: 10 }, (_, index) => ({
|
||||||
|
uuid: UUID.generate().toString(),
|
||||||
|
timestamp: index,
|
||||||
|
}));
|
||||||
|
const secretParams =
|
||||||
|
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAd/rq8//fR' +
|
||||||
|
'4RzhvN3G9KcKlQoj7cguQFjTOqLV6JUSbrURzeILsUmsymGJmHt3kpBJ2zosqp4ex' +
|
||||||
|
'sg+qwF1z6YdB/rxKnxKRLZZP/V0F7bERslYILy2lUh3Sh3iA98yO4CGfzjjFVo1SI' +
|
||||||
|
'7U8XApLeVNQHJo7nkflf/JyBrqPft5gEucbKW/h+S3OYjfQ5zl2Cpw3XrV7N6OKEu' +
|
||||||
|
'tLUWPHQuJx11A4xDPrmtAOnGy2NBxoOybDNlWipeNbn1WQJqOjMF7YA80oEm+5qnM' +
|
||||||
|
'kEYcFVqbYaSzPcMhg3mQ0SYfQpxYgSOJpwp9f/8EDnwJV4ISPBOo2CiaSqVfnd8Dw' +
|
||||||
|
'ZOc58gQA==';
|
||||||
|
const clientZkGroupCipher = getClientZkGroupCipher(secretParams);
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await updateRemoteConfig([
|
||||||
|
{ name: HARD_LIMIT_KEY, value: '5', enabled: true },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add banned member without deleting', () => {
|
||||||
|
const actions = _maybeBuildAddBannedMemberActions({
|
||||||
|
clientZkGroupCipher,
|
||||||
|
uuid,
|
||||||
|
ourUuid,
|
||||||
|
group: {
|
||||||
|
bannedMembersV2: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(actions.addMembersBanned?.length, 1);
|
||||||
|
assert.strictEqual(
|
||||||
|
decryptUuid(
|
||||||
|
clientZkGroupCipher,
|
||||||
|
actions.addMembersBanned?.[0]?.added?.userId ?? new Uint8Array(0)
|
||||||
|
),
|
||||||
|
uuid
|
||||||
|
);
|
||||||
|
assert.strictEqual(actions.deleteMembersBanned, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add banned member while deleting the oldest', () => {
|
||||||
|
const actions = _maybeBuildAddBannedMemberActions({
|
||||||
|
clientZkGroupCipher,
|
||||||
|
uuid,
|
||||||
|
ourUuid,
|
||||||
|
group: {
|
||||||
|
bannedMembersV2: [...existing],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleted = actions.deleteMembersBanned?.map(({ deletedUserId }) => {
|
||||||
|
return decryptUuid(
|
||||||
|
clientZkGroupCipher,
|
||||||
|
deletedUserId ?? new Uint8Array(0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(actions.addMembersBanned?.length, 1);
|
||||||
|
assert.strictEqual(
|
||||||
|
decryptUuid(
|
||||||
|
clientZkGroupCipher,
|
||||||
|
actions.addMembersBanned?.[0]?.added?.userId ?? new Uint8Array(0)
|
||||||
|
),
|
||||||
|
uuid
|
||||||
|
);
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
deleted,
|
||||||
|
existing
|
||||||
|
.slice(0, 6)
|
||||||
|
.reverse()
|
||||||
|
.map(member => member.uuid)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not ban ourselves', () => {
|
||||||
|
const actions = _maybeBuildAddBannedMemberActions({
|
||||||
|
clientZkGroupCipher,
|
||||||
|
uuid: ourUuid,
|
||||||
|
ourUuid,
|
||||||
|
group: {
|
||||||
|
bannedMembersV2: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isUndefined(actions.addMembersBanned);
|
||||||
|
assert.isUndefined(actions.deleteMembersBanned);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not ban already banned person', () => {
|
||||||
|
const actions = _maybeBuildAddBannedMemberActions({
|
||||||
|
clientZkGroupCipher,
|
||||||
|
uuid,
|
||||||
|
ourUuid,
|
||||||
|
group: {
|
||||||
|
bannedMembersV2: [{ uuid, timestamp: 1 }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.isUndefined(actions.addMembersBanned);
|
||||||
|
assert.isUndefined(actions.deleteMembersBanned);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1670,4 +1670,60 @@ describe('SQL migrations test', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('updateToSchemaVersion53', () => {
|
||||||
|
it('remaps bannedMembersV2 to array of objects', () => {
|
||||||
|
updateToVersion(52);
|
||||||
|
|
||||||
|
const UUID_A = generateGuid();
|
||||||
|
const UUID_B = generateGuid();
|
||||||
|
const UUID_C = generateGuid();
|
||||||
|
|
||||||
|
const noMembers = { id: 'a', groupId: 'gv2a' };
|
||||||
|
const emptyMembers = {
|
||||||
|
id: 'b',
|
||||||
|
groupId: 'gv2b',
|
||||||
|
bannedMembersV2: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const nonEmptyMembers = {
|
||||||
|
id: 'c',
|
||||||
|
groupId: 'gv2c',
|
||||||
|
bannedMembersV2: [UUID_A, UUID_B],
|
||||||
|
};
|
||||||
|
|
||||||
|
db.exec(
|
||||||
|
`
|
||||||
|
INSERT INTO conversations
|
||||||
|
(id, type, uuid, json)
|
||||||
|
VALUES
|
||||||
|
('a', 'group', '${UUID_A}', '${JSON.stringify(noMembers)}'),
|
||||||
|
('b', 'group', '${UUID_B}', '${JSON.stringify(emptyMembers)}'),
|
||||||
|
('c', 'group', '${UUID_C}', '${JSON.stringify(nonEmptyMembers)}');
|
||||||
|
`
|
||||||
|
);
|
||||||
|
|
||||||
|
updateToVersion(53);
|
||||||
|
|
||||||
|
const entries: Array<{ id: string; json: string }> = db
|
||||||
|
.prepare('SELECT id, json FROM conversations ORDER BY id')
|
||||||
|
.all();
|
||||||
|
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
entries.map(({ id, json }) => ({ id, ...JSON.parse(json) })),
|
||||||
|
[
|
||||||
|
{ id: 'a', groupId: 'gv2a' },
|
||||||
|
{ id: 'b', groupId: 'gv2b', bannedMembersV2: [] },
|
||||||
|
{
|
||||||
|
id: 'c',
|
||||||
|
groupId: 'gv2c',
|
||||||
|
bannedMembersV2: [
|
||||||
|
{ uuid: UUID_A, timestamp: 0 },
|
||||||
|
{ uuid: UUID_B, timestamp: 0 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue