Fix New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly. Part 1.

Thanks to nazim-oss and xet7 !

Related #6060
This commit is contained in:
Lauri Ojansivu 2026-01-14 23:43:11 +02:00
parent 2f59e42024
commit eabb6a239d
25 changed files with 562 additions and 291 deletions

View file

@ -176,7 +176,8 @@ Attachments = new FilesCollection({
if (Meteor.isServer) {
Attachments.allow({
insert(userId, fileObj) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId));
// ReadOnly users cannot upload attachments
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(fileObj.boardId));
},
update(userId, fileObj, fields) {
// Only allow updates to specific fields that don't affect security
@ -190,7 +191,8 @@ if (Meteor.isServer) {
return false;
}
return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId));
// ReadOnly users cannot update attachments
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(fileObj.boardId));
},
remove(userId, fileObj) {
// Additional security check: ensure the file belongs to the board the user has access to
@ -209,7 +211,8 @@ if (Meteor.isServer) {
return false;
}
return allowIsBoardMember(userId, board);
// ReadOnly users cannot delete attachments
return allowIsBoardMemberWithWriteAccess(userId, board);
},
fetch: ['meta', 'boardId'],
});

View file

@ -82,7 +82,8 @@ CardComments.attachSchema(
CardComments.allow({
insert(userId, doc) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly users cannot add comments. Only members who can comment are allowed.
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
},
update(userId, doc) {
return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));

View file

@ -518,7 +518,7 @@ Cards.attachSchema(
);
// Centralized update policy for Cards
// Security: deny any direct client updates to 'vote' fields; require membership otherwise
// Security: deny any direct client updates to 'vote' fields; require write access otherwise
canUpdateCard = function(userId, doc, fields) {
if (!userId) return false;
const fieldNames = fields || [];
@ -530,19 +530,22 @@ canUpdateCard = function(userId, doc, fields) {
if (_.some(fieldNames, f => typeof f === 'string' && (f === 'poker' || f.indexOf('poker.') === 0))) {
return false;
}
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly users cannot edit cards
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
};
Cards.allow({
insert(userId, doc) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly users cannot create cards
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
update(userId, doc, fields) {
return canUpdateCard(userId, doc, fields);
},
remove(userId, doc) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly users cannot delete cards
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
fetch: ['boardId'],
});

View file

@ -70,13 +70,16 @@ ChecklistItems.attachSchema(
ChecklistItems.allow({
insert(userId, doc) {
return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId));
// ReadOnly users cannot create checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
},
update(userId, doc) {
return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId));
// ReadOnly users cannot edit checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
},
remove(userId, doc) {
return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId));
// ReadOnly users cannot delete checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
},
fetch: ['userId', 'cardId'],
});

View file

@ -170,13 +170,16 @@ Checklists.helpers({
Checklists.allow({
insert(userId, doc) {
return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId));
// ReadOnly users cannot create checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
},
update(userId, doc) {
return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId));
// ReadOnly users cannot edit checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
},
remove(userId, doc) {
return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId));
// ReadOnly users cannot delete checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
},
fetch: ['userId', 'cardId'],
});

View file

@ -181,13 +181,16 @@ Lists.attachSchema(
Lists.allow({
insert(userId, doc) {
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly and CommentOnly users cannot create lists
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
update(userId, doc) {
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly and CommentOnly users cannot edit lists
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly and CommentOnly users cannot delete lists
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
fetch: ['boardId'],
});

View file

@ -132,13 +132,16 @@ Swimlanes.attachSchema(
Swimlanes.allow({
insert(userId, doc) {
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly and CommentOnly users cannot create swimlanes
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
update(userId, doc) {
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly and CommentOnly users cannot edit swimlanes
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
// ReadOnly and CommentOnly users cannot delete swimlanes
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
},
fetch: ['boardId'],
});

View file

@ -271,6 +271,13 @@ Users.attachSchema(
type: Boolean,
optional: true,
},
'profile.showActivities': {
/**
* does the user want to show activities in card details?
*/
type: Boolean,
optional: true,
},
'profile.customFieldsGrid': {
/**
* has user at card Custom Fields have Grid (false) or one per row (true) layout?
@ -875,6 +882,16 @@ if (Meteor.isClient) {
return board && board.hasCommentOnly(this._id);
},
isReadOnly() {
const board = Utils.getCurrentBoard();
return board && board.hasReadOnly(this._id);
},
isReadAssignedOnly() {
const board = Utils.getCurrentBoard();
return board && board.hasReadAssignedOnly(this._id);
},
isNotWorker() {
const board = Utils.getCurrentBoard();
return board && board.hasMember(this._id) && !board.hasWorker(this._id);
@ -1206,6 +1223,11 @@ Users.helpers({
return profile.cardMaximized || false;
},
hasShowActivities() {
const profile = this.profile || {};
return profile.showActivities || false;
},
hasHiddenMinicardLabelText() {
const profile = this.profile || {};
return profile.hiddenMinicardLabelText || false;
@ -1753,6 +1775,14 @@ Users.mutations({
};
},
toggleShowActivities(value = false) {
return {
$set: {
'profile.showActivities': !value,
},
};
},
toggleLabelText(value = false) {
return {
$set: {