attachments_old.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. const storeName = 'attachments';
  2. const defaultStoreOptions = {
  3. beforeWrite: fileObj => {
  4. if (!fileObj.isImage()) {
  5. return {
  6. type: 'application/octet-stream',
  7. };
  8. }
  9. return {};
  10. },
  11. };
  12. let store;
  13. store = new FS.Store.GridFS(storeName, {
  14. // XXX Add a new store for cover thumbnails so we don't load big images in
  15. // the general board view
  16. // If the uploaded document is not an image we need to enforce browser
  17. // download instead of execution. This is particularly important for HTML
  18. // files that the browser will just execute if we don't serve them with the
  19. // appropriate `application/octet-stream` MIME header which can lead to user
  20. // data leaks. I imagine other formats (like PDF) can also be attack vectors.
  21. // See https://github.com/wekan/wekan/issues/99
  22. // XXX Should we use `beforeWrite` option of CollectionFS instead of
  23. // collection-hooks?
  24. // We should use `beforeWrite`.
  25. ...defaultStoreOptions,
  26. });
  27. AttachmentsOld = new FS.Collection('attachments', {
  28. stores: [store],
  29. });
  30. if (Meteor.isServer) {
  31. Meteor.startup(() => {
  32. AttachmentsOld.files._ensureIndex({ cardId: 1 });
  33. });
  34. AttachmentsOld.allow({
  35. insert(userId, doc) {
  36. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  37. },
  38. update(userId, doc) {
  39. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  40. },
  41. remove(userId, doc) {
  42. return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
  43. },
  44. // We authorize the attachment download either:
  45. // - if the board is public, everyone (even unconnected) can download it
  46. // - if the board is private, only board members can download it
  47. download(userId, doc) {
  48. const board = Boards.findOne(doc.boardId);
  49. if (board.isPublic()) {
  50. return true;
  51. } else {
  52. return board.hasMember(userId);
  53. }
  54. },
  55. fetch: ['boardId'],
  56. });
  57. }
  58. // XXX Enforce a schema for the AttachmentsOld CollectionFS
  59. if (Meteor.isServer) {
  60. AttachmentsOld.files.after.insert((userId, doc) => {
  61. // If the attachment doesn't have a source field
  62. // or its source is different than import
  63. if (!doc.source || doc.source !== 'import') {
  64. // Add activity about adding the attachment
  65. Activities.insert({
  66. userId,
  67. type: 'card',
  68. activityType: 'addAttachment',
  69. attachmentId: doc._id,
  70. // this preserves the name so that notifications can be meaningful after
  71. // this file is removed
  72. attachmentName: doc.original.name,
  73. boardId: doc.boardId,
  74. cardId: doc.cardId,
  75. listId: doc.listId,
  76. swimlaneId: doc.swimlaneId,
  77. });
  78. } else {
  79. // Don't add activity about adding the attachment as the activity
  80. // be imported and delete source field
  81. AttachmentsOld.update(
  82. {
  83. _id: doc._id,
  84. },
  85. {
  86. $unset: {
  87. source: '',
  88. },
  89. },
  90. );
  91. }
  92. });
  93. AttachmentsOld.files.before.remove((userId, doc) => {
  94. Activities.insert({
  95. userId,
  96. type: 'card',
  97. activityType: 'deleteAttachment',
  98. attachmentId: doc._id,
  99. // this preserves the name so that notifications can be meaningful after
  100. // this file is removed
  101. attachmentName: doc.original.name,
  102. boardId: doc.boardId,
  103. cardId: doc.cardId,
  104. listId: doc.listId,
  105. swimlaneId: doc.swimlaneId,
  106. });
  107. });
  108. }
  109. export default AttachmentsOld;