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] GDPR - Right to access and Data Portability #9906

Merged
merged 26 commits into from
Apr 21, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4726593
Implemented basic JSON generation
Hudell Feb 20, 2018
0945340
Added new data to message json
Hudell Feb 21, 2018
6759890
Merge branch 'develop' into right-to-access
Hudell Feb 21, 2018
5ad3755
Changed export operation structure
Hudell Feb 21, 2018
e3c8f0a
Changed code to use forEach instead of fetch
Hudell Feb 21, 2018
cdec24a
Split the operation into two independent commands
Hudell Feb 21, 2018
b41d67c
File download, zip generation, admin settings
Hudell Feb 23, 2018
3c45bad
Merge branch 'develop' into right-to-access
Hudell Feb 23, 2018
0b73219
Merge branch 'develop' into right-to-access
Hudell Feb 26, 2018
41fa0db
Use syncedcron to process data downloads
Hudell Feb 26, 2018
e8e006e
Added download URL
Hudell Feb 26, 2018
a726286
Sending emails when the download file is ready
Hudell Feb 26, 2018
500e00e
Merge branch 'develop' into right-to-access
Hudell Feb 26, 2018
2a04ef7
Merge branch 'develop' into right-to-access
Hudell Feb 27, 2018
649f1ac
Allow usage of GridFS as storage for the finished file.
Hudell Feb 27, 2018
8122ab1
Merge branch 'develop' into right-to-access
Hudell Feb 27, 2018
b013ff1
Lint
Hudell Feb 27, 2018
3580f6c
Merge branch 'develop' into right-to-access
Hudell Feb 28, 2018
a606f73
Added support for Google and Amazon as storage types
Hudell Feb 28, 2018
4f10a4f
Split the options to download and export data
Hudell Mar 5, 2018
3883fcf
Merge branch 'develop' into right-to-access
Hudell Mar 5, 2018
3b76b64
Merge branch 'develop' into data-portability
Hudell Mar 5, 2018
dd2cccc
Merge branch 'data-portability' into right-to-access
Hudell Mar 5, 2018
79743a8
Removed commented code
Hudell Mar 5, 2018
a1086d7
Merge branch 'develop' into right-to-access
Hudell Apr 17, 2018
30fca01
Merge branch 'develop' into right-to-access
Hudell Apr 18, 2018
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
1 change: 1 addition & 0 deletions .meteor/packages
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ rocketchat:ui-master
rocketchat:ui-message
rocketchat:ui-sidenav
rocketchat:ui-vrecord
rocketchat:user-data-download
rocketchat:version
rocketchat:videobridge
rocketchat:webrtc
Expand Down
1 change: 1 addition & 0 deletions .meteor/versions
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
rocketchat:[email protected]
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"@google-cloud/storage": "^1.6.0",
"@google-cloud/vision": "^0.15.2",
"adm-zip": "^0.4.7",
"archiver": "^2.1.1",
"atlassian-crowd": "^0.5.0",
"autolinker": "^1.6.2",
"aws-sdk": "^2.199.0",
Expand Down
16 changes: 16 additions & 0 deletions packages/rocketchat-file-upload/server/config/FileSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ const FileSystemUploads = new FileUploadClass({
res.end();
return;
}
},

copy(file, out) {
const filePath = this.store.getFilePath(file._id, file);
try {
const stat = Meteor.wrapAsync(fs.stat)(filePath);

if (stat && stat.isFile()) {
file = FileUpload.addExtensionTo(file);

this.store.getReadStream(file._id, file).pipe(out);
}
} catch (e) {
out.end();
return;
}
}
});

Expand Down
17 changes: 16 additions & 1 deletion packages/rocketchat-file-upload/server/config/GridFS.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const getByteRange = function(header) {
return null;
};


// code from: https://github.com/jalik/jalik-ufs/blob/master/ufs-server.js#L310
const readFromGridFS = function(storeName, fileId, file, req, res) {
const store = UploadFS.getStore(storeName);
Expand Down Expand Up @@ -123,6 +122,18 @@ const readFromGridFS = function(storeName, fileId, file, req, res) {
}
};

const copyFromGridFS = function(storeName, fileId, file, out) {
const store = UploadFS.getStore(storeName);
const rs = store.getReadStream(fileId, file);

[rs, out].forEach(stream => stream.on('error', function(err) {
store.onReadError.call(store, err, fileId, file);
out.end();
}));

rs.pipe(out);
};

FileUpload.configureUploadsStore('GridFS', 'GridFS:Uploads', {
collectionName: 'rocketchat_uploads'
});
Expand All @@ -147,6 +158,10 @@ new FileUploadClass({
res.setHeader('Content-Length', file.size);

return readFromGridFS(file.store, file._id, file, req, res);
},

copy(file, out) {
copyFromGridFS(file.store, file._id, file, out);
}
});

Expand Down
21 changes: 19 additions & 2 deletions packages/rocketchat-file-upload/server/lib/FileUpload.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* globals UploadFS */

import fs from 'fs';
import path from 'path';
import stream from 'stream';
import mime from 'mime-type/with-db';
import Future from 'fibers/future';
Expand Down Expand Up @@ -229,16 +230,32 @@ Object.assign(FileUpload, {
}
res.writeHead(404);
res.end();
},

copy(file, targetFolder) {
const store = this.getStoreByName(file.store);
const targetFile = path.join(targetFolder, `${ file._id }-${ file.name }`);

const out = fs.createWriteStream(targetFile);

file = FileUpload.addExtensionTo(file);

if (store.copy) {
store.copy(file, out);
return true;
}

return false;
}
});


export class FileUploadClass {
constructor({ name, model, store, get, insert, getStore }) {
constructor({ name, model, store, get, insert, getStore, copy }) {
this.name = name;
this.model = model || this.getModelFromName();
this._store = store || UploadFS.getStore(name);
this.get = get;
this.copy = copy;

if (insert) {
this.insert = insert;
Expand Down
13 changes: 13 additions & 0 deletions packages/rocketchat-i18n/i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@
"Domain_removed": "Domain Removed",
"Domains": "Domains",
"Domains_allowed_to_embed_the_livechat_widget": "Comma-separated list of domains allowed to embed the livechat widget. Leave blank to allow all domains.",
"Download_My_Data" : "Download My Data",
"Download_Snippet": "Download",
"Drop_to_upload_file": "Drop to upload file",
"Dry_run": "Dry run",
Expand Down Expand Up @@ -2070,6 +2071,18 @@
"User_uploaded_file": "Uploaded a file",
"User_uploaded_image": "Uploaded an image",
"User_Presence": "User Presence",
"UserDataDownload" : "User Data Download",
"UserData_EnableDownload" : "Enable User Data Download",
"UserData_FileSystemPath" : "System Path (Exported Files)",
"UserData_FileSystemZipPath" : "System Path (Compressed File)",
"UserData_ProcessingFrequency" : "Processing Frequency (Minutes)",
"UserData_MessageLimitPerRequest" : "Message Limit per Request",
"UserDataDownload_EmailSubject" : "Your Data File is Ready to Download",
"UserDataDownload_EmailBody" : "Your data file is now ready to download. Click <a href=\"__download_link__\">here</a> to download it.",
"UserDataDownload_Requested" : "Download File Requested",
"UserDataDownload_Requested_Text" : "Your data file will be generated. A link to download it will be sent to your email address when ready.",
"UserDataDownload_RequestExisted_Text" : "Your data file is already being generated. A link to download it will be sent to your email address when ready.",
"UserDataDownload_CompletedRequestExisted_Text" : "Your data file was already generated. Check your email account for the download link.",
"Username": "Username",
"Username_and_message_must_not_be_empty": "Username and message must not be empty.",
"Username_cant_be_empty": "The username cannot be empty",
Expand Down
1 change: 1 addition & 0 deletions packages/rocketchat-lib/package.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ Package.onUse(function(api) {
api.addFiles('server/models/Subscriptions.js', 'server');
api.addFiles('server/models/Uploads.js', 'server');
api.addFiles('server/models/Users.js', 'server');
api.addFiles('server/models/ExportOperations.js', 'server');

api.addFiles('server/oauth/oauth.js', 'server');
api.addFiles('server/oauth/google.js', 'server');
Expand Down
77 changes: 77 additions & 0 deletions packages/rocketchat-lib/server/models/ExportOperations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import _ from 'underscore';

RocketChat.models.ExportOperations = new class ModelExportOperations extends RocketChat.models._Base {
constructor() {
super('exportOperations');

this.tryEnsureIndex({ 'userId': 1 });
this.tryEnsureIndex({ 'status': 1 });
}

// FIND
findById(id) {
const query = {_id: id};

return this.find(query);
}

findLastOperationByUser(userId, options = {}) {
const query = {
userId
};

options.sort = {'createdAt' : -1};
return this.findOne(query, options);
}

findPendingByUser(userId, options) {
const query = {
userId,
status: {
$in: ['pending', 'exporting']
}
};

return this.find(query, options);
}

findAllPending(options) {
const query = {
status: { $in: ['pending', 'exporting'] }
};

return this.find(query, options);
}

// UPDATE
updateOperation(data) {
const update = {
$set: {
roomList: data.roomList,
status: data.status,
fileList: data.fileList,
generatedFile: data.generatedFile
}
};

return this.update(data._id, update);
}


// INSERT
create(data) {
const exportOperation = {
createdAt: new Date
};

_.extend(exportOperation, data);

return this.insert(exportOperation);
}


// REMOVE
removeById(_id) {
return this.remove(_id);
}
};
8 changes: 8 additions & 0 deletions packages/rocketchat-lib/server/models/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base {
return this.findOne(query);
}

findByRoomId(roomId, options) {
const query = {
rid: roomId
};

return this.find(query, options);
}

getLastVisibleMessageSentWithNoTypeByRoomId(rid, messageId) {
const query = {
rid,
Expand Down
5 changes: 5 additions & 0 deletions packages/rocketchat-lib/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class ModelUsers extends RocketChat.models._Base {
return this.findOne(query, options);
}

findOneById(userId) {
const query = {_id: userId};

return this.findOne(query);
}

// FIND
findById(userId) {
Expand Down
12 changes: 12 additions & 0 deletions packages/rocketchat-ui-account/client/accountPreferences.html
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,18 @@ <h1>{{_ "Sound"}}</h1>
</div>
</div>
</div>

{{#if userDataDownloadEnabled}}
<div class="section">
<h1>{{_ "My Data"}}</h1>
<div class="section-content border-component-color">
<div class="input-line">
<label><button class="button download-my-data"><i class="icon-download secondary-font-color"></i> <span>{{_ "Download_My_Data"}}</span></button></label>
</div>

</div>
</div>
{{/if}}
</fieldset>
</form>
</div>
Expand Down
52 changes: 52 additions & 0 deletions packages/rocketchat-ui-account/client/accountPreferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ Template.accountPreferences.helpers({
showRoles() {
return RocketChat.settings.get('UI_DisplayRoles');
},
userDataDownloadEnabled() {
return RocketChat.settings.get('UserData_EnableDownload') !== false;
},
notificationsSoundVolume() {
return RocketChat.getUserPreference(Meteor.user(), 'notificationsSoundVolume');
}
Expand Down Expand Up @@ -195,6 +198,51 @@ Template.accountPreferences.onCreated(function() {
}
});
};

this.downloadMyData = function() {
Meteor.call('requestDataDownload', {}, function(error, results) {
if (results) {
if (results.requested) {
modal.open({
title: t('UserDataDownload_Requested'),
text: t('UserDataDownload_Requested_Text'),
type: 'success'
});

return true;
}

if (results.exportOperation) {
if (results.exportOperation.status === 'completed') {
modal.open({
title: t('UserDataDownload_Requested'),
text: t('UserDataDownload_CompletedRequestExisted_Text'),
type: 'success'
});

return true;
}

modal.open({
title: t('UserDataDownload_Requested'),
text: t('UserDataDownload_RequestExisted_Text'),
type: 'success'
});
return true;
}

modal.open({
title: t('UserDataDownload_Requested'),
type: 'success'
});
return true;
}

if (error) {
return handleError(error);
}
});
};
});

Template.accountPreferences.onRendered(function() {
Expand All @@ -214,6 +262,10 @@ Template.accountPreferences.events({
'click .enable-notifications'() {
KonchatNotification.getDesktopPermission();
},
'click .download-my-data'(e, t) {
e.preventDefault();
t.downloadMyData();
},
'click .test-notifications'(e) {
e.preventDefault();
KonchatNotification.notify({
Expand Down
19 changes: 19 additions & 0 deletions packages/rocketchat-user-data-download/package.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Package.describe({
name: 'rocketchat:user-data-download',
version: '1.0.0',
summary: 'Adds setting to allow the user to download all their data stored in the servers.',
git: ''
});

Package.onUse(function(api) {
api.use([
'ecmascript',
'rocketchat:file',
'rocketchat:lib',
'webapp'
]);

api.addFiles('server/startup/settings.js', 'server');
api.addFiles('server/lib/requests.js', 'server');
api.addFiles('server/cronProcessDownloads.js', 'server');
});
Loading