mirror of
https://github.com/wekan/wekan.git
synced 2026-11-07 02:52:33 -05:00
Admin Panel / Settings / Visibility / Rules gets "Boards may show their Rules to members, read-only", off by default (Settings rulesReadOnlyForMembers, site admin only). With it on, a board admin ticks "Show these Rules to board members, read-only" on the board's Rules page (rulesVisibleToMembers). Members of that board then get Rules in Board Settings and see the rules list and each rule's details, with no control that changes a rule; the methods that change rules keep their board-admin checks, so nothing is granted. Without both ticks only board and site admins see the Rules page, which until now drew its list for any member who opened the address. models/lib/rulesReadOnly.js decides 'edit', 'read' or 'none', and client/lib/rulesAccess.js is the one place the Rules page, Board Settings and rule details ask. tests/rulesReadOnly.test.cjs covers both switches, each alone, a non-member, and that a reader's page has no edit control. boardMenuOrder now leaves the reader's alternative branch out of the admin's sequence and pins it alone; instanceFeatures counts the fourth site-admin method; roleHelperRegressions follows the canAdmin check to rulesAccess.js. The new strings are English placeholders until the next translation pass. Thanks to justinr1234 and xet7 ! Fixes #2217.
91 lines
4.1 KiB
JavaScript
91 lines
4.1 KiB
JavaScript
// #6736: Admin Panel / Settings / Visibility / Features. Site admin only.
|
|
// The decisions are models/lib/instanceFeatures.js; this stores them and the
|
|
// pilot users who preview disabled features (a flag on the user, published
|
|
// only to that user, so nobody learns who the pilots are).
|
|
import { Meteor } from 'meteor/meteor';
|
|
import { check, Match } from 'meteor/check';
|
|
import Settings from '/models/settings';
|
|
const { featureSettingsModifier, parsePilotUsernames } = require('/models/lib/instanceFeatures');
|
|
const { cardFieldStatesValue } = require('/models/lib/cardFieldVisibility');
|
|
const { cleanOptionalApis } = require('/models/lib/optionalApis');
|
|
|
|
async function requireSiteAdmin() {
|
|
const user = await Meteor.userAsync();
|
|
if (user?.isAdmin !== true) throw new Meteor.Error('error-notAuthorized');
|
|
return user;
|
|
}
|
|
|
|
Meteor.methods({
|
|
// The usernames of the current pilot users, for the page's text box.
|
|
async getFeaturePilotUsernames() {
|
|
await requireSiteAdmin();
|
|
const users = await Meteor.users.find({ featurePreview: true },
|
|
{ fields: { username: 1 }, sort: { username: 1 }, limit: 500 }).fetchAsync();
|
|
return users.map(user => user.username).filter(Boolean);
|
|
},
|
|
|
|
// Saves every feature's decision, the two policies and the pilot users.
|
|
// Unknown usernames are returned rather than silently ignored.
|
|
// Admin Panel / Settings / Visibility / Optional APIs: which compatible
|
|
// APIs (Trello, Jira, GitHub, Gitea) answer. Site admins only; unknown keys
|
|
// are dropped and every value is a boolean (models/lib/optionalApis.js).
|
|
async saveOptionalApis(input) {
|
|
await requireSiteAdmin();
|
|
check(input, Object);
|
|
const setting = await Settings.findOneAsync({});
|
|
if (!setting) throw new Meteor.Error('settings-not-found');
|
|
const optionalApis = cleanOptionalApis(input);
|
|
await Settings.updateAsync(setting._id, { $set: { optionalApis } });
|
|
return optionalApis;
|
|
},
|
|
|
|
// #2217: Admin Panel / Settings / Visibility - boards may show their Rules
|
|
// to members, read-only (models/lib/rulesReadOnly.js). Site admins only.
|
|
async saveRulesReadOnlyForMembers(enabled) {
|
|
await requireSiteAdmin();
|
|
check(enabled, Boolean);
|
|
const setting = await Settings.findOneAsync({});
|
|
if (!setting) throw new Meteor.Error('settings-not-found');
|
|
await Settings.updateAsync(setting._id, { $set: { rulesReadOnlyForMembers: enabled } });
|
|
return enabled;
|
|
},
|
|
|
|
async saveInstanceFeatures(input) {
|
|
await requireSiteAdmin();
|
|
check(input, {
|
|
states: Object,
|
|
approvalRequired: Boolean,
|
|
previewAdmins: Boolean,
|
|
pilotUsernames: Match.Optional(String),
|
|
// The card fields every board shows (models/lib/cardFieldVisibility.js).
|
|
cardFields: Match.Optional(Object),
|
|
});
|
|
const { pilotUsernames, cardFields, ...decisions } = input;
|
|
const modifier = featureSettingsModifier(decisions);
|
|
if (modifier.error) throw new Meteor.Error('invalid-features', modifier.error);
|
|
if (cardFields !== undefined) {
|
|
const value = cardFieldStatesValue(cardFields);
|
|
if (value.error) throw new Meteor.Error('invalid-card-fields', value.error);
|
|
modifier.$set.cardFieldStates = value.cardFieldStates;
|
|
}
|
|
const setting = await Settings.findOneAsync({});
|
|
if (!setting) throw new Meteor.Error('settings-not-found');
|
|
await Settings.updateAsync(setting._id, modifier);
|
|
|
|
let unknownUsernames = [];
|
|
if (typeof pilotUsernames === 'string') {
|
|
const names = parsePilotUsernames(pilotUsernames);
|
|
const pilots = names.length ? await Meteor.users.find({ username: { $in: names } },
|
|
{ fields: { _id: 1, username: 1 } }).fetchAsync() : [];
|
|
const ids = pilots.map(user => user._id);
|
|
unknownUsernames = names.filter(name => !pilots.some(user => user.username === name));
|
|
await Meteor.users.updateAsync({ featurePreview: true, _id: { $nin: ids } },
|
|
{ $unset: { featurePreview: '' } }, { multi: true });
|
|
if (ids.length) {
|
|
await Meteor.users.updateAsync({ _id: { $in: ids } }, { $set: { featurePreview: true } }, { multi: true });
|
|
}
|
|
}
|
|
return { saved: true, unknownUsernames };
|
|
},
|
|
});
|