mirror of
https://github.com/wekan/wekan.git
synced 2026-11-04 17:10:08 -05:00
Red Strings: fix 403 when editing an existing dependency from the client
addDependency (updating an existing link), setDependencyProps and
removeDependency used updateAsync with a selector + positional `$` operator.
On the client that is rejected by collection2 ("Untrusted code may only
updateAsync documents by ID" / 403), so changing a dependency's relation type,
color or icon (or removing it) threw a ValidationError.
Rewrite all three to read-modify-write the cardDependencies array and $set it
by _id only, which is permitted from untrusted client code. The server REST
endpoints keep the positional update (server code is trusted).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
f6e9401a68
commit
1db64f4dde
1 file changed
+30
-23
+30
-23
@@ -1572,43 +1572,50 @@ Cards.helpers({
|
||||
color: options.color || (existing && existing.color),
|
||||
icon: options.icon || (existing && existing.icon),
|
||||
});
|
||||
if (existing) {
|
||||
return Cards.updateAsync(
|
||||
{ _id: this._id, 'cardDependencies.cardId': targetCardId },
|
||||
{
|
||||
$set: {
|
||||
'cardDependencies.$.type': entry.type,
|
||||
'cardDependencies.$.color': entry.color,
|
||||
'cardDependencies.$.icon': entry.icon,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
// Read-modify-write the whole array and update by _id only: the client
|
||||
// (untrusted code) may not updateAsync with a selector / positional `$`.
|
||||
const next = existing
|
||||
? deps.map(dep => (dep.cardId === targetCardId ? entry : dep))
|
||||
: [...deps, entry];
|
||||
return Cards.updateAsync(this._id, {
|
||||
$push: { cardDependencies: entry },
|
||||
$set: { cardDependencies: next },
|
||||
});
|
||||
},
|
||||
|
||||
// #3392: Update a single property (type/color/icon) of an existing dependency.
|
||||
// Updates by _id only (positional `$` selector updates are forbidden on the
|
||||
// client), so the whole array is rewritten.
|
||||
setDependencyProps(targetCardId, props = {}) {
|
||||
const modifier = {};
|
||||
['type', 'color', 'icon'].forEach(key => {
|
||||
if (props[key] !== undefined) {
|
||||
modifier[`cardDependencies.$.${key}`] = props[key];
|
||||
const deps = this.getDependencies();
|
||||
let changed = false;
|
||||
const next = deps.map(dep => {
|
||||
if (dep.cardId !== targetCardId) {
|
||||
return dep;
|
||||
}
|
||||
changed = true;
|
||||
return normalizeDependency({
|
||||
cardId: dep.cardId,
|
||||
type: props.type !== undefined ? props.type : dep.type,
|
||||
color: props.color !== undefined ? props.color : dep.color,
|
||||
icon: props.icon !== undefined ? props.icon : dep.icon,
|
||||
});
|
||||
});
|
||||
if (Object.keys(modifier).length === 0) {
|
||||
if (!changed) {
|
||||
return undefined;
|
||||
}
|
||||
return Cards.updateAsync(
|
||||
{ _id: this._id, 'cardDependencies.cardId': targetCardId },
|
||||
{ $set: modifier },
|
||||
);
|
||||
return Cards.updateAsync(this._id, {
|
||||
$set: { cardDependencies: next },
|
||||
});
|
||||
},
|
||||
|
||||
removeDependency(targetCardId) {
|
||||
// Rewrite the array and update by _id only (no selector / positional `$`
|
||||
// updates from untrusted client code).
|
||||
const next = this.getDependencies().filter(
|
||||
dep => dep.cardId !== targetCardId,
|
||||
);
|
||||
return Cards.updateAsync(this._id, {
|
||||
$pull: { cardDependencies: { cardId: targetCardId } },
|
||||
$set: { cardDependencies: next },
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
Reference in new issue
Block a user