From 9c399624cc987f95e309e91ce0fb2b9775eb9b46 Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Thu, 13 Sep 2018 12:57:07 -0700 Subject: [PATCH] Support for blocking groups on mobile, via group/blocked syncs --- _locales/en/messages.json | 4 ++ js/background.js | 7 ++++ js/models/blockedNumbers.js | 37 ++++++++++++++++--- js/views/conversation_view.js | 8 ++++ libtextsecure/message_receiver.js | 32 ++++++++++++++++ protos/SignalService.proto | 2 + .../conversation/GroupNotification.md | 6 --- .../conversation/GroupNotification.tsx | 8 ++-- 8 files changed, 89 insertions(+), 15 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 3b8f46c86..695183e5d 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1222,6 +1222,10 @@ "message": "Unblock this contact to send a message.", "description": "Brief message shown when trying to message a blocked number" }, + "unblockGroupToSend": { + "message": "Unblock this group to send a message.", + "description": "Brief message shown when trying to message a blocked group" + }, "youChangedTheTimer": { "message": "You set the disappearing message timer to $time$", "description": diff --git a/js/background.js b/js/background.js index cfc50976b..7215afa66 100644 --- a/js/background.js +++ b/js/background.js @@ -802,6 +802,7 @@ } function onConfiguration(ev) { storage.put('read-receipt-setting', ev.configuration.readReceipts); + ev.confirm(); } async function onContactReceived(ev) { @@ -919,6 +920,12 @@ updates.left = true; } + if (details.blocked === true) { + storage.addBlockedGroup(id); + } else if (details.blocked === false) { + storage.removeBlockedGroup(id); + } + await wrapDeferred(conversation.save(updates)); const { expireTimer } = details; const isValidExpireTimer = typeof expireTimer === 'number'; diff --git a/js/models/blockedNumbers.js b/js/models/blockedNumbers.js index 54b5ebc20..10ef67f0f 100644 --- a/js/models/blockedNumbers.js +++ b/js/models/blockedNumbers.js @@ -4,27 +4,54 @@ (function() { 'use strict'; + const BLOCKED_NUMBERS_ID = 'blocked'; + const BLOCKED_GROUPS_ID = 'blocked-groups'; + storage.isBlocked = number => { - const numbers = storage.get('blocked', []); + const numbers = storage.get(BLOCKED_NUMBERS_ID, []); return _.include(numbers, number); }; storage.addBlockedNumber = number => { - const numbers = storage.get('blocked', []); + const numbers = storage.get(BLOCKED_NUMBERS_ID, []); if (_.include(numbers, number)) { return; } window.log.info('adding', number, 'to blocked list'); - storage.put('blocked', numbers.concat(number)); + storage.put(BLOCKED_NUMBERS_ID, numbers.concat(number)); }; storage.removeBlockedNumber = number => { - const numbers = storage.get('blocked', []); + const numbers = storage.get(BLOCKED_NUMBERS_ID, []); if (!_.include(numbers, number)) { return; } window.log.info('removing', number, 'from blocked list'); - storage.put('blocked', _.without(numbers, number)); + storage.put(BLOCKED_NUMBERS_ID, _.without(numbers, number)); + }; + + storage.isGroupBlocked = groupId => { + const groupIds = storage.get(BLOCKED_GROUPS_ID, []); + + return _.include(groupIds, groupId); + }; + storage.addBlockedGroup = groupId => { + const groupIds = storage.get(BLOCKED_GROUPS_ID, []); + if (_.include(groupIds, groupId)) { + return; + } + + window.log.info(`adding groupId(${groupId}) to blocked list`); + storage.put(BLOCKED_GROUPS_ID, groupIds.concat(groupId)); + }; + storage.removeBlockedGroup = groupId => { + const groupIds = storage.get(BLOCKED_GROUPS_ID, []); + if (!_.include(groupIds, groupId)) { + return; + } + + window.log.info(`removing group(${groupId} from blocked list`); + storage.put(BLOCKED_GROUPS_ID, _.without(groupIds, groupId)); }; })(); diff --git a/js/views/conversation_view.js b/js/views/conversation_view.js index 6f5eb040b..1646bedd9 100644 --- a/js/views/conversation_view.js +++ b/js/views/conversation_view.js @@ -29,6 +29,11 @@ return { toastMessage: i18n('unblockToSend') }; }, }); + Whisper.BlockedGroupToast = Whisper.ToastView.extend({ + render_attributes() { + return { toastMessage: i18n('unblockGroupToSend') }; + }, + }); Whisper.LeftGroupToast = Whisper.ToastView.extend({ render_attributes() { return { toastMessage: i18n('youLeftTheGroup') }; @@ -1436,6 +1441,9 @@ if (this.model.isPrivate() && storage.isBlocked(this.model.id)) { toast = new Whisper.BlockedToast(); } + if (!this.model.isPrivate() && storage.isGroupBlocked(this.model.id)) { + toast = new Whisper.BlockedGroupToast(); + } if (!this.model.isPrivate() && this.model.get('left')) { toast = new Whisper.LeftGroupToast(); } diff --git a/libtextsecure/message_receiver.js b/libtextsecure/message_receiver.js index e0a2c7ad7..f60c79adf 100644 --- a/libtextsecure/message_receiver.js +++ b/libtextsecure/message_receiver.js @@ -727,6 +727,16 @@ MessageReceiver.prototype.extend({ } return p.then(() => this.processDecrypted(envelope, msg, this.number).then(message => { + const groupId = message.group && message.group.id; + if (groupId && this.isGroupBlocked(groupId)) { + window.log.warn( + `Message ${this.getEnvelopeId( + envelope + )} ignored; destined for blocked group` + ); + return this.removeFromCache(envelope); + } + const ev = new Event('sent'); ev.confirm = this.removeFromCache.bind(this, envelope); ev.data = { @@ -751,6 +761,16 @@ MessageReceiver.prototype.extend({ } return p.then(() => this.processDecrypted(envelope, msg, envelope.source).then(message => { + const groupId = message.group && message.group.id; + if (groupId && this.isGroupBlocked(groupId)) { + window.log.warn( + `Message ${this.getEnvelopeId( + envelope + )} ignored; destined for blocked group` + ); + return this.removeFromCache(envelope); + } + const ev = new Event('message'); ev.confirm = this.removeFromCache.bind(this, envelope); ev.data = { @@ -991,10 +1011,22 @@ MessageReceiver.prototype.extend({ handleBlocked(envelope, blocked) { window.log.info('Setting these numbers as blocked:', blocked.numbers); textsecure.storage.put('blocked', blocked.numbers); + + const groupIds = _.map(blocked.groupIds, groupId => groupId.toBinary()); + window.log.info( + 'Setting these groups as blocked:', + groupIds.map(groupId => `group(${groupId})`) + ); + textsecure.storage.put('blocked-groups', groupIds); + + return this.removeFromCache(envelope); }, isBlocked(number) { return textsecure.storage.get('blocked', []).indexOf(number) >= 0; }, + isGroupBlocked(groupId) { + return textsecure.storage.get('blocked-groups', []).indexOf(groupId) >= 0; + }, handleAttachment(attachment) { // eslint-disable-next-line no-param-reassign attachment.id = attachment.id.toString(); diff --git a/protos/SignalService.proto b/protos/SignalService.proto index bc55e4129..e1beb304c 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -208,6 +208,7 @@ message SyncMessage { message Blocked { repeated string numbers = 1; + repeated bytes groupIds = 2; } message Request { @@ -303,4 +304,5 @@ message GroupDetails { optional bool active = 5 [default = true]; optional uint32 expireTimer = 6; optional string color = 7; + optional bool blocked = 8; } diff --git a/ts/components/conversation/GroupNotification.md b/ts/components/conversation/GroupNotification.md index 5acbd50fc..2303766ae 100644 --- a/ts/components/conversation/GroupNotification.md +++ b/ts/components/conversation/GroupNotification.md @@ -126,12 +126,6 @@ { type: 'remove', isMe: true, - contacts: [ - { - phoneNumber: '(202) 555-1000', - profileName: 'Mr. Fire', - }, - ], }, ]} i18n={util.i18n} diff --git a/ts/components/conversation/GroupNotification.tsx b/ts/components/conversation/GroupNotification.tsx index 4f0d94739..c71e656be 100644 --- a/ts/components/conversation/GroupNotification.tsx +++ b/ts/components/conversation/GroupNotification.tsx @@ -71,14 +71,14 @@ export class GroupNotification extends React.Component { /> ); case 'remove': - if (!contacts || !contacts.length) { - throw new Error('Group update is missing contacts'); - } - if (isMe) { return i18n('youLeftTheGroup'); } + if (!contacts || !contacts.length) { + throw new Error('Group update is missing contacts'); + } + return (