Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW][ENTERPRISE] Push Notification Data Privacy #18254

Merged
merged 23 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
87f2bde
Started implementation
pierre-lehnen-rc Jul 6, 2020
1ac4ba7
Enterprise settings
pierre-lehnen-rc Jul 6, 2020
3118960
Merge branch 'develop' into enterprise-settings
pierre-lehnen-rc Jul 6, 2020
e1a5656
Fixed api calls to include the enterprise fields on the settings quer…
pierre-lehnen-rc Jul 6, 2020
3ddc299
Fixed initial value of enterprise settings
pierre-lehnen-rc Jul 9, 2020
faeffbd
Added setting to only send the message id on push notifications
pierre-lehnen-rc Jul 9, 2020
79bc9c5
Add new endpoint to receive the notification data
pierre-lehnen-rc Jul 13, 2020
41fb87d
Update app/settings/server/functions/settings.ts
pierre-lehnen-rc Jul 14, 2020
60d2a32
Fixed argument names
pierre-lehnen-rc Jul 14, 2020
00a48a2
Return settings' enterprise info on public-settings/get
pierre-lehnen-rc Jul 14, 2020
894fddc
Fixed broken code
pierre-lehnen-rc Jul 15, 2020
e7172fd
Fixed some issues with license loading
pierre-lehnen-rc Jul 15, 2020
2b83da3
Update settings if a license becomes invalid
pierre-lehnen-rc Jul 16, 2020
21b4837
Merge branch 'develop' into enterprise-settings
sampaiodiego Jul 17, 2020
aed7787
Code quality
pierre-lehnen-rc Jul 20, 2020
3aa9054
Add push-privacy to list of modules
pierre-lehnen-rc Jul 20, 2020
1c6dd7e
Skip some extra data when using idOnly
pierre-lehnen-rc Jul 20, 2020
a07e57b
Validate if the user can access the room before loading the message data
pierre-lehnen-rc Jul 20, 2020
2fb6f63
Merge branch 'enterprise-settings' of github.com:RocketChat/Rocket.Ch…
pierre-lehnen-rc Jul 20, 2020
0fbad0a
Merge branch 'develop' into enterprise-settings
sampaiodiego Jul 21, 2020
83eded7
Always return enterprise fields
sampaiodiego Jul 21, 2020
6895e95
Merge branch 'develop' into enterprise-settings
sampaiodiego Jul 21, 2020
0e2c449
Improve checkins
sampaiodiego Jul 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions app/api/server/v1/push.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';
import { Match, check } from 'meteor/check';

import { appTokensCollection } from '../../../push/server';
import { API } from '../api';
import PushNotification from '../../../push-notifications/server/lib/PushNotification';
import { canAccessRoom } from '../../../authorization/server/functions/canAccessRoom';
import { Users, Messages, Rooms } from '../../../models/server';

API.v1.addRoute('push.token', { authRequired: true }, {
post() {
Expand Down Expand Up @@ -63,3 +67,35 @@ API.v1.addRoute('push.token', { authRequired: true }, {
return API.v1.success();
},
});

API.v1.addRoute('push.get', { authRequired: true }, {
get() {
const params = this.requestParams();
check(params, Match.ObjectIncluding({
id: String,
}));

const receiver = Users.findOneById(this.userId);
if (!receiver) {
throw new Error('error-user-not-found');
}

const message = Messages.findOneById(params.id);
if (!message) {
throw new Error('error-message-not-found');
}

const room = Rooms.findOneById(message.rid);
if (!room) {
throw new Error('error-room-not-found');
}

if (!canAccessRoom(room, receiver)) {
throw new Error('error-not-allowed');
}

const data = PushNotification.getNotificationForMessageId({ receiver, room, message });

return API.v1.success({ data });
},
});
27 changes: 15 additions & 12 deletions app/api/server/v1/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ import _ from 'underscore';
import { Settings } from '../../../models/server';
import { hasPermission } from '../../../authorization';
import { API } from '../api';
import { SettingsEvents } from '../../../settings/server';

const fetchSettings = (query, sort, offset, count, fields) => {
const settings = Settings.find(query, {
sort: sort || { _id: 1 },
skip: offset,
limit: count,
fields: Object.assign({ _id: 1, value: 1, enterprise: 1, invalidValue: 1, modules: 1 }, fields),
}).fetch();

SettingsEvents.emit('fetch-settings', settings);
return settings;
};

// settings endpoints
API.v1.addRoute('settings.public', { authRequired: false }, {
Expand All @@ -20,12 +33,7 @@ API.v1.addRoute('settings.public', { authRequired: false }, {

ourQuery = Object.assign({}, query, ourQuery);

const settings = Settings.find(ourQuery, {
sort: sort || { _id: 1 },
skip: offset,
limit: count,
fields: Object.assign({ _id: 1, value: 1 }, fields),
}).fetch();
const settings = fetchSettings(ourQuery, sort, offset, count, fields);

return API.v1.success({
settings,
Expand Down Expand Up @@ -94,12 +102,7 @@ API.v1.addRoute('settings', { authRequired: true }, {

ourQuery = Object.assign({}, query, ourQuery);

const settings = Settings.find(ourQuery, {
sort: sort || { _id: 1 },
skip: offset,
limit: count,
fields: Object.assign({ _id: 1, value: 1 }, fields),
}).fetch();
const settings = fetchSettings(ourQuery, sort, offset, count, fields);

return API.v1.success({
settings,
Expand Down
10 changes: 9 additions & 1 deletion app/lib/server/startup/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -1283,10 +1283,18 @@ settings.addGroup('Push', function() {
type: 'boolean',
public: true,
});
return this.add('Push_show_message', true, {
this.add('Push_show_message', true, {
type: 'boolean',
public: true,
});
this.add('Push_request_content_from_server', true, {
type: 'boolean',
enterprise: true,
invalidValue: false,
modules: [
'push-privacy',
],
});
});
});

Expand Down
8 changes: 6 additions & 2 deletions app/models/server/models/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class Settings extends Base {
filter._id = { $in: ids };
}

return this.find(filter, { fields: { _id: 1, value: 1, editor: 1 } });
return this.find(filter, { fields: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1 } });
}

findNotHiddenPublicUpdatedAfter(updatedAt) {
Expand All @@ -70,7 +70,7 @@ export class Settings extends Base {
},
};

return this.find(filter, { fields: { _id: 1, value: 1, editor: 1 } });
return this.find(filter, { fields: { _id: 1, value: 1, editor: 1, enterprise: 1, invalidValue: 1, modules: 1 } });
}

findNotHiddenPrivate() {
Expand Down Expand Up @@ -105,6 +105,10 @@ export class Settings extends Base {
return this.find({ wizard: { $exists: true, $ne: null } });
}

findEnterpriseSettings() {
return this.find({ enterprise: true });
}

// UPDATE
updateValueById(_id, value) {
const query = {
Expand Down
78 changes: 62 additions & 16 deletions app/push-notifications/server/lib/PushNotification.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,19 @@ import { Meteor } from 'meteor/meteor';
import { Push } from '../../../push/server';
import { settings } from '../../../settings/server';
import { metrics } from '../../../metrics/server';
import { Users } from '../../../models/server';
import { RocketChatAssets } from '../../../assets/server';
import { replaceMentionedUsernamesWithFullNames, parseMessageTextPerUser } from '../../../lib/server/functions/notifications';
import { callbacks } from '../../../callbacks/server';
import { getPushData } from '../../../lib/server/functions/notifications/mobile';

export class PushNotification {
getNotificationId(roomId) {
const serverId = settings.get('uniqueID');
return this.hash(`${ serverId }|${ roomId }`); // hash
}

hash(str) {
let hash = 0;
let i = str.length;

while (i) {
hash = ((hash << 5) - hash) + str.charCodeAt(--i);
hash &= hash; // Convert to 32bit integer
}
return hash;
}

send({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category }) {
getNotificationConfig({ rid, uid: userId, mid: messageId, roomName, username, message, payload, badge = 1, category, idOnly = false }) {
let title;
if (roomName && roomName !== '') {
title = `${ roomName }`;
Expand All @@ -36,13 +29,14 @@ export class PushNotification {
badge,
sound: 'default',
priority: 10,
title,
text: message,
title: idOnly ? '' : title,
text: idOnly ? '' : message,
payload: {
host: Meteor.absoluteUrl(),
rid,
...idOnly || { rid },
messageId,
...payload,
notificationType: idOnly ? 'message-id-only' : 'message',
...idOnly || payload,
},
userId,
notId: this.getNotificationId(rid),
Expand All @@ -58,9 +52,61 @@ export class PushNotification {
};
}

return config;
}

hash(str) {
let hash = 0;
let i = str.length;

while (i) {
hash = ((hash << 5) - hash) + str.charCodeAt(--i);
hash &= hash; // Convert to 32bit integer
}
return hash;
}

send({ rid, uid, mid, roomName, username, message, payload, badge = 1, category }) {
const idOnly = settings.get('Push_request_content_from_server');
const config = this.getNotificationConfig({ rid, uid, mid, roomName, username, message, payload, badge, category, idOnly });

metrics.notificationsSent.inc({ notification_type: 'mobile' });
return Push.send(config);
}

getNotificationForMessageId({ receiver, message, room }) {
const sender = Users.findOne(message.u._id, { fields: { username: 1, name: 1 } });
if (!sender) {
throw new Error('Message sender not found');
}

let notificationMessage = callbacks.run('beforeSendMessageNotifications', message.msg);
if (message.mentions?.length > 0 && settings.get('UI_Use_Real_Name')) {
notificationMessage = replaceMentionedUsernamesWithFullNames(message.msg, message.mentions);
}
notificationMessage = parseMessageTextPerUser(notificationMessage, message, receiver);

const pushData = Promise.await(getPushData({
room,
message,
userId: receiver._id,
receiverUsername: receiver.username,
senderUsername: sender.username,
senderName: sender.name,
notificationMessage,
}));

return {
message,
notification: this.getNotificationConfig({
...pushData,
rid: message.rid,
uid: message.u._id,
mid: message._id,
idOnly: false,
}),
};
}
}

export default new PushNotification();
20 changes: 9 additions & 11 deletions app/settings/client/lib/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,18 @@ class Settings extends SettingsBase {
return this.dict.get(_id);
}

private _storeSettingValue(record: { _id: string; value: SettingValue }, initialLoad: boolean): void {
Meteor.settings[record._id] = record.value;
this.dict.set(record._id, record.value);
this.load(record._id, record.value, initialLoad);
}

init(): void {
let initialLoad = true;
this.collection.find().observe({
added: (record: {_id: string; value: SettingValue}) => {
Meteor.settings[record._id] = record.value;
this.dict.set(record._id, record.value);
this.load(record._id, record.value, initialLoad);
},
changed: (record: {_id: string; value: SettingValue}) => {
Meteor.settings[record._id] = record.value;
this.dict.set(record._id, record.value);
this.load(record._id, record.value, initialLoad);
},
removed: (record: {_id: string}) => {
added: (record: { _id: string; value: SettingValue }) => this._storeSettingValue(record, initialLoad),
changed: (record: { _id: string; value: SettingValue }) => this._storeSettingValue(record, initialLoad),
removed: (record: { _id: string }) => {
delete Meteor.settings[record._id];
this.dict.set(record._id, null);
this.load(record._id, undefined, initialLoad);
Expand Down
Loading