diff --git a/models/attachments_old.js b/models/attachments_old.js new file mode 100644 index 000000000..fae4b844c --- /dev/null +++ b/models/attachments_old.js @@ -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; diff --git a/models/avatars_old.js b/models/avatars_old.js new file mode 100644 index 000000000..deae4bbc6 --- /dev/null +++ b/models/avatars_old.js @@ -0,0 +1,29 @@ +AvatarsOld = new FS.Collection('avatars', { + stores: [new FS.Store.GridFS('avatars')], + filter: { + maxSize: 72000, + allow: { + contentTypes: ['image/*'], + }, + }, +}); + +function isOwner(userId, file) { + return userId && userId === file.userId; +} + +AvatarsOld.allow({ + insert: isOwner, + update: isOwner, + remove: isOwner, + download() { + return true; + }, + fetch: ['userId'], +}); + +AvatarsOld.files.before.insert((userId, doc) => { + doc.userId = userId; +}); + +export default AvatarsOld;