|
@@ -0,0 +1,118 @@
|
|
|
+import { ReactiveCache } from '/imports/reactiveCache';
|
|
|
+
|
|
|
+const storeName = 'attachments';
|
|
|
+const defaultStoreOptions = {
|
|
|
+ beforeWrite: fileObj => {
|
|
|
+ if (!fileObj.isImage()) {
|
|
|
+ return {
|
|
|
+ type: 'application/octet-stream',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return {};
|
|
|
+ },
|
|
|
+};
|
|
|
+let store;
|
|
|
+store = new FS.Store.GridFS(storeName, {
|
|
|
+ // XXX Add a new store for cover thumbnails so we don't load big images in
|
|
|
+ // the general board view
|
|
|
+ // If the uploaded document is not an image we need to enforce browser
|
|
|
+ // download instead of execution. This is particularly important for HTML
|
|
|
+ // files that the browser will just execute if we don't serve them with the
|
|
|
+ // appropriate `application/octet-stream` MIME header which can lead to user
|
|
|
+ // data leaks. I imagine other formats (like PDF) can also be attack vectors.
|
|
|
+ // See https://github.com/wekan/wekan/issues/99
|
|
|
+ // XXX Should we use `beforeWrite` option of CollectionFS instead of
|
|
|
+ // collection-hooks?
|
|
|
+ // We should use `beforeWrite`.
|
|
|
+ ...defaultStoreOptions,
|
|
|
+});
|
|
|
+AttachmentsOld = new FS.Collection('attachments', {
|
|
|
+ stores: [store],
|
|
|
+});
|
|
|
+
|
|
|
+if (Meteor.isServer) {
|
|
|
+ Meteor.startup(() => {
|
|
|
+ AttachmentsOld.files._ensureIndex({ cardId: 1 });
|
|
|
+ });
|
|
|
+
|
|
|
+ AttachmentsOld.allow({
|
|
|
+ insert(userId, doc) {
|
|
|
+ return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
|
|
|
+ },
|
|
|
+ update(userId, doc) {
|
|
|
+ return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
|
|
|
+ },
|
|
|
+ remove(userId, doc) {
|
|
|
+ return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
|
|
|
+ },
|
|
|
+ // We authorize the attachment download either:
|
|
|
+ // - if the board is public, everyone (even unconnected) can download it
|
|
|
+ // - if the board is private, only board members can download it
|
|
|
+ download(userId, doc) {
|
|
|
+ const board = ReactiveCache.getBoard(doc.boardId);
|
|
|
+ if (board.isPublic()) {
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return board.hasMember(userId);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ fetch: ['boardId'],
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+// XXX Enforce a schema for the AttachmentsOld CollectionFS
|
|
|
+
|
|
|
+if (Meteor.isServer) {
|
|
|
+ AttachmentsOld.files.after.insert((userId, doc) => {
|
|
|
+ // If the attachment doesn't have a source field
|
|
|
+ // or its source is different than import
|
|
|
+ if (!doc.source || doc.source !== 'import') {
|
|
|
+ // Add activity about adding the attachment
|
|
|
+ Activities.insert({
|
|
|
+ userId,
|
|
|
+ type: 'card',
|
|
|
+ activityType: 'addAttachment',
|
|
|
+ attachmentId: doc._id,
|
|
|
+ // this preserves the name so that notifications can be meaningful after
|
|
|
+ // this file is removed
|
|
|
+ attachmentName: doc.original.name,
|
|
|
+ boardId: doc.boardId,
|
|
|
+ cardId: doc.cardId,
|
|
|
+ listId: doc.listId,
|
|
|
+ swimlaneId: doc.swimlaneId,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // Don't add activity about adding the attachment as the activity
|
|
|
+ // be imported and delete source field
|
|
|
+ AttachmentsOld.update(
|
|
|
+ {
|
|
|
+ _id: doc._id,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ $unset: {
|
|
|
+ source: '',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ );
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ AttachmentsOld.files.before.remove((userId, doc) => {
|
|
|
+ Activities.insert({
|
|
|
+ userId,
|
|
|
+ type: 'card',
|
|
|
+ activityType: 'deleteAttachment',
|
|
|
+ attachmentId: doc._id,
|
|
|
+ // this preserves the name so that notifications can be meaningful after
|
|
|
+ // this file is removed
|
|
|
+ attachmentName: doc.original.name,
|
|
|
+ boardId: doc.boardId,
|
|
|
+ cardId: doc.cardId,
|
|
|
+ listId: doc.listId,
|
|
|
+ swimlaneId: doc.swimlaneId,
|
|
|
+ });
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+export default AttachmentsOld;
|